moved autocommproc, autoflagproc and autoflipproc to gtk
[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 HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161                   Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165                    /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176                            char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178                         int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
186
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219
220 #ifdef WIN32
221        extern void ConsoleCreate();
222 #endif
223
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
227
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
233
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
239 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
247 int chattingPartner;
248
249 /* States for ics_getting_history */
250 #define H_FALSE 0
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
256
257 /* whosays values for GameEnds */
258 #define GE_ICS 0
259 #define GE_ENGINE 1
260 #define GE_PLAYER 2
261 #define GE_FILE 3
262 #define GE_XBOARD 4
263 #define GE_ENGINE1 5
264 #define GE_ENGINE2 6
265
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
268
269 /* Different types of move when calling RegisterMove */
270 #define CMAIL_MOVE   0
271 #define CMAIL_RESIGN 1
272 #define CMAIL_DRAW   2
273 #define CMAIL_ACCEPT 3
274
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
279
280 /* Telnet protocol constants */
281 #define TN_WILL 0373
282 #define TN_WONT 0374
283 #define TN_DO   0375
284 #define TN_DONT 0376
285 #define TN_IAC  0377
286 #define TN_ECHO 0001
287 #define TN_SGA  0003
288 #define TN_PORT 23
289
290 /* [AS] */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
292 {
293     assert( dst != NULL );
294     assert( src != NULL );
295     assert( count > 0 );
296
297     strncpy( dst, src, count );
298     dst[ count-1 ] = '\0';
299     return dst;
300 }
301
302 /* Some compiler can't cast u64 to double
303  * This function do the job for us:
304
305  * We use the highest bit for cast, this only
306  * works if the highest bit is not
307  * in use (This should not happen)
308  *
309  * We used this for all compiler
310  */
311 double
312 u64ToDouble(u64 value)
313 {
314   double r;
315   u64 tmp = value & u64Const(0x7fffffffffffffff);
316   r = (double)(s64)tmp;
317   if (value & u64Const(0x8000000000000000))
318        r +=  9.2233720368547758080e18; /* 2^63 */
319  return r;
320 }
321
322 /* Fake up flags for now, as we aren't keeping track of castling
323    availability yet. [HGM] Change of logic: the flag now only
324    indicates the type of castlings allowed by the rule of the game.
325    The actual rights themselves are maintained in the array
326    castlingRights, as part of the game history, and are not probed
327    by this function.
328  */
329 int
330 PosFlags(index)
331 {
332   int flags = F_ALL_CASTLE_OK;
333   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
334   switch (gameInfo.variant) {
335   case VariantSuicide:
336     flags &= ~F_ALL_CASTLE_OK;
337   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
338     flags |= F_IGNORE_CHECK;
339   case VariantLosers:
340     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
341     break;
342   case VariantAtomic:
343     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
344     break;
345   case VariantKriegspiel:
346     flags |= F_KRIEGSPIEL_CAPTURE;
347     break;
348   case VariantCapaRandom:
349   case VariantFischeRandom:
350     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
351   case VariantNoCastle:
352   case VariantShatranj:
353   case VariantCourier:
354     flags &= ~F_ALL_CASTLE_OK;
355     break;
356   default:
357     break;
358   }
359   return flags;
360 }
361
362 FILE *gameFileFP, *debugFP;
363
364 /*
365     [AS] Note: sometimes, the sscanf() function is used to parse the input
366     into a fixed-size buffer. Because of this, we must be prepared to
367     receive strings as long as the size of the input buffer, which is currently
368     set to 4K for Windows and 8K for the rest.
369     So, we must either allocate sufficiently large buffers here, or
370     reduce the size of the input buffer in the input reading part.
371 */
372
373 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
374 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
375 char thinkOutput1[MSG_SIZ*10];
376
377 ChessProgramState first, second;
378
379 /* premove variables */
380 int premoveToX = 0;
381 int premoveToY = 0;
382 int premoveFromX = 0;
383 int premoveFromY = 0;
384 int premovePromoChar = 0;
385 int gotPremove = 0;
386 Boolean alarmSounded;
387 /* end premove variables */
388
389 char *ics_prefix = "$";
390 int ics_type = ICS_GENERIC;
391
392 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
393 int pauseExamForwardMostMove = 0;
394 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
395 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
396 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
397 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
398 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
399 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
400 int whiteFlag = FALSE, blackFlag = FALSE;
401 int userOfferedDraw = FALSE;
402 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
403 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
404 int cmailMoveType[CMAIL_MAX_GAMES];
405 long ics_clock_paused = 0;
406 ProcRef icsPR = NoProc, cmailPR = NoProc;
407 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
408 GameMode gameMode = BeginningOfGame;
409 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
410 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
411 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
412 int hiddenThinkOutputState = 0; /* [AS] */
413 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
414 int adjudicateLossPlies = 6;
415 char white_holding[64], black_holding[64];
416 TimeMark lastNodeCountTime;
417 long lastNodeCount=0;
418 int have_sent_ICS_logon = 0;
419 int movesPerSession;
420 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
421 long timeControl_2; /* [AS] Allow separate time controls */
422 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
423 long timeRemaining[2][MAX_MOVES];
424 int matchGame = 0;
425 TimeMark programStartTime;
426 char ics_handle[MSG_SIZ];
427 int have_set_title = 0;
428
429 /* animateTraining preserves the state of appData.animate
430  * when Training mode is activated. This allows the
431  * response to be animated when appData.animate == TRUE and
432  * appData.animateDragging == TRUE.
433  */
434 Boolean animateTraining;
435
436 GameInfo gameInfo;
437
438 AppData appData;
439
440 Board boards[MAX_MOVES];
441 /* [HGM] Following 7 needed for accurate legality tests: */
442 signed char  epStatus[MAX_MOVES];
443 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
444 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
445 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
446 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int   initialRulePlies, FENrulePlies;
448 char  FENepStatus;
449 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
450 int loadFlag = 0;
451 int shuffleOpenings;
452 int mute; // mute all sounds
453
454 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
455     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
456         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
457     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
458         BlackKing, BlackBishop, BlackKnight, BlackRook }
459 };
460
461 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
462     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
463         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
464     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
465         BlackKing, BlackKing, BlackKnight, BlackRook }
466 };
467
468 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
469     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
470         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
471     { BlackRook, BlackMan, BlackBishop, BlackQueen,
472         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
473 };
474
475 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
476     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
477         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
478     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
479         BlackKing, BlackBishop, BlackKnight, BlackRook }
480 };
481
482 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
483     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
484         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
485     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
486         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
487 };
488
489
490 #if (BOARD_SIZE>=10)
491 ChessSquare ShogiArray[2][BOARD_SIZE] = {
492     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
493         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
494     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
495         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
496 };
497
498 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
500         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
502         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
506     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
510 };
511
512 ChessSquare GreatArray[2][BOARD_SIZE] = {
513     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
514         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
515     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
516         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
517 };
518
519 ChessSquare JanusArray[2][BOARD_SIZE] = {
520     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
521         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
522     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
523         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
524 };
525
526 #ifdef GOTHIC
527 ChessSquare GothicArray[2][BOARD_SIZE] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
529         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
531         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
532 };
533 #else // !GOTHIC
534 #define GothicArray CapablancaArray
535 #endif // !GOTHIC
536
537 #ifdef FALCON
538 ChessSquare FalconArray[2][BOARD_SIZE] = {
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
540         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
542         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
543 };
544 #else // !FALCON
545 #define FalconArray CapablancaArray
546 #endif // !FALCON
547
548 #else // !(BOARD_SIZE>=10)
549 #define XiangqiPosition FIDEArray
550 #define CapablancaArray FIDEArray
551 #define GothicArray FIDEArray
552 #define GreatArray FIDEArray
553 #endif // !(BOARD_SIZE>=10)
554
555 #if (BOARD_SIZE>=12)
556 ChessSquare CourierArray[2][BOARD_SIZE] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
560         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
561 };
562 #else // !(BOARD_SIZE>=12)
563 #define CourierArray CapablancaArray
564 #endif // !(BOARD_SIZE>=12)
565
566
567 Board initialPosition;
568
569
570 /* Convert str to a rating. Checks for special cases of "----",
571
572    "++++", etc. Also strips ()'s */
573 int
574 string_to_rating(str)
575   char *str;
576 {
577   while(*str && !isdigit(*str)) ++str;
578   if (!*str)
579     return 0;   /* One of the special "no rating" cases */
580   else
581     return atoi(str);
582 }
583
584 void
585 ClearProgramStats()
586 {
587     /* Init programStats */
588     programStats.movelist[0] = 0;
589     programStats.depth = 0;
590     programStats.nr_moves = 0;
591     programStats.moves_left = 0;
592     programStats.nodes = 0;
593     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
594     programStats.score = 0;
595     programStats.got_only_move = 0;
596     programStats.got_fail = 0;
597     programStats.line_is_book = 0;
598 }
599
600 void
601 InitBackEnd1()
602 {
603     int matched, min, sec;
604
605     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
606
607     GetTimeMark(&programStartTime);
608     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
609
610     ClearProgramStats();
611     programStats.ok_to_send = 1;
612     programStats.seen_stat = 0;
613
614     /*
615      * Initialize game list
616      */
617     ListNew(&gameList);
618
619
620     /*
621      * Internet chess server status
622      */
623     if (appData.icsActive) {
624         appData.matchMode = FALSE;
625         appData.matchGames = 0;
626 #if ZIPPY
627         appData.noChessProgram = !appData.zippyPlay;
628 #else
629         appData.zippyPlay = FALSE;
630         appData.zippyTalk = FALSE;
631         appData.noChessProgram = TRUE;
632 #endif
633         if (*appData.icsHelper != NULLCHAR) {
634             appData.useTelnet = TRUE;
635             appData.telnetProgram = appData.icsHelper;
636         }
637     } else {
638         appData.zippyTalk = appData.zippyPlay = FALSE;
639     }
640
641     /* [AS] Initialize pv info list [HGM] and game state */
642     {
643         int i, j;
644
645         for( i=0; i<MAX_MOVES; i++ ) {
646             pvInfoList[i].depth = -1;
647             epStatus[i]=EP_NONE;
648             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
649         }
650     }
651
652     /*
653      * Parse timeControl resource
654      */
655     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
656                           appData.movesPerSession)) {
657         char buf[MSG_SIZ];
658         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
659         DisplayFatalError(buf, 0, 2);
660     }
661
662     /*
663      * Parse searchTime resource
664      */
665     if (*appData.searchTime != NULLCHAR) {
666         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
667         if (matched == 1) {
668             searchTime = min * 60;
669         } else if (matched == 2) {
670             searchTime = min * 60 + sec;
671         } else {
672             char buf[MSG_SIZ];
673             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
674             DisplayFatalError(buf, 0, 2);
675         }
676     }
677
678     /* [AS] Adjudication threshold */
679     adjudicateLossThreshold = appData.adjudicateLossThreshold;
680
681     first.which = "first";
682     second.which = "second";
683     first.maybeThinking = second.maybeThinking = FALSE;
684     first.pr = second.pr = NoProc;
685     first.isr = second.isr = NULL;
686     first.sendTime = second.sendTime = 2;
687     first.sendDrawOffers = 1;
688     if (appData.firstPlaysBlack) {
689         first.twoMachinesColor = "black\n";
690         second.twoMachinesColor = "white\n";
691     } else {
692         first.twoMachinesColor = "white\n";
693         second.twoMachinesColor = "black\n";
694     }
695     first.program = appData.firstChessProgram;
696     second.program = appData.secondChessProgram;
697     first.host = appData.firstHost;
698     second.host = appData.secondHost;
699     first.dir = appData.firstDirectory;
700     second.dir = appData.secondDirectory;
701     first.other = &second;
702     second.other = &first;
703     first.initString = appData.initString;
704     second.initString = appData.secondInitString;
705     first.computerString = appData.firstComputerString;
706     second.computerString = appData.secondComputerString;
707     first.useSigint = second.useSigint = TRUE;
708     first.useSigterm = second.useSigterm = TRUE;
709     first.reuse = appData.reuseFirst;
710     second.reuse = appData.reuseSecond;
711     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
712     second.nps = appData.secondNPS;
713     first.useSetboard = second.useSetboard = FALSE;
714     first.useSAN = second.useSAN = FALSE;
715     first.usePing = second.usePing = FALSE;
716     first.lastPing = second.lastPing = 0;
717     first.lastPong = second.lastPong = 0;
718     first.usePlayother = second.usePlayother = FALSE;
719     first.useColors = second.useColors = TRUE;
720     first.useUsermove = second.useUsermove = FALSE;
721     first.sendICS = second.sendICS = FALSE;
722     first.sendName = second.sendName = appData.icsActive;
723     first.sdKludge = second.sdKludge = FALSE;
724     first.stKludge = second.stKludge = FALSE;
725     TidyProgramName(first.program, first.host, first.tidy);
726     TidyProgramName(second.program, second.host, second.tidy);
727     first.matchWins = second.matchWins = 0;
728     strcpy(first.variants, appData.variant);
729     strcpy(second.variants, appData.variant);
730     first.analysisSupport = second.analysisSupport = 2; /* detect */
731     first.analyzing = second.analyzing = FALSE;
732     first.initDone = second.initDone = FALSE;
733
734     /* New features added by Tord: */
735     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
736     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
737     /* End of new features added by Tord. */
738     first.fenOverride  = appData.fenOverride1;
739     second.fenOverride = appData.fenOverride2;
740
741     /* [HGM] time odds: set factor for each machine */
742     first.timeOdds  = appData.firstTimeOdds;
743     second.timeOdds = appData.secondTimeOdds;
744     { int norm = 1;
745         if(appData.timeOddsMode) {
746             norm = first.timeOdds;
747             if(norm > second.timeOdds) norm = second.timeOdds;
748         }
749         first.timeOdds /= norm;
750         second.timeOdds /= norm;
751     }
752
753     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
754     first.accumulateTC = appData.firstAccumulateTC;
755     second.accumulateTC = appData.secondAccumulateTC;
756     first.maxNrOfSessions = second.maxNrOfSessions = 1;
757
758     /* [HGM] debug */
759     first.debug = second.debug = FALSE;
760     first.supportsNPS = second.supportsNPS = UNKNOWN;
761
762     /* [HGM] options */
763     first.optionSettings  = appData.firstOptions;
764     second.optionSettings = appData.secondOptions;
765
766     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
767     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
768     first.isUCI = appData.firstIsUCI; /* [AS] */
769     second.isUCI = appData.secondIsUCI; /* [AS] */
770     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
771     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
772
773     if (appData.firstProtocolVersion > PROTOVER ||
774         appData.firstProtocolVersion < 1) {
775       char buf[MSG_SIZ];
776       sprintf(buf, _("protocol version %d not supported"),
777               appData.firstProtocolVersion);
778       DisplayFatalError(buf, 0, 2);
779     } else {
780       first.protocolVersion = appData.firstProtocolVersion;
781     }
782
783     if (appData.secondProtocolVersion > PROTOVER ||
784         appData.secondProtocolVersion < 1) {
785       char buf[MSG_SIZ];
786       sprintf(buf, _("protocol version %d not supported"),
787               appData.secondProtocolVersion);
788       DisplayFatalError(buf, 0, 2);
789     } else {
790       second.protocolVersion = appData.secondProtocolVersion;
791     }
792
793     if (appData.icsActive) {
794         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
795     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
796         appData.clockMode = FALSE;
797         first.sendTime = second.sendTime = 0;
798     }
799
800 #if ZIPPY
801     /* Override some settings from environment variables, for backward
802        compatibility.  Unfortunately it's not feasible to have the env
803        vars just set defaults, at least in xboard.  Ugh.
804     */
805     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
806       ZippyInit();
807     }
808 #endif
809
810     if (appData.noChessProgram) {
811         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812         sprintf(programVersion, "%s", PACKAGE_STRING);
813     } else {
814       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
817     }
818
819     if (!appData.icsActive) {
820       char buf[MSG_SIZ];
821       /* Check for variants that are supported only in ICS mode,
822          or not at all.  Some that are accepted here nevertheless
823          have bugs; see comments below.
824       */
825       VariantClass variant = StringToVariant(appData.variant);
826       switch (variant) {
827       case VariantBughouse:     /* need four players and two boards */
828       case VariantKriegspiel:   /* need to hide pieces and move details */
829       /* case VariantFischeRandom: (Fabien: moved below) */
830         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831         DisplayFatalError(buf, 0, 2);
832         return;
833
834       case VariantUnknown:
835       case VariantLoadable:
836       case Variant29:
837       case Variant30:
838       case Variant31:
839       case Variant32:
840       case Variant33:
841       case Variant34:
842       case Variant35:
843       case Variant36:
844       default:
845         sprintf(buf, _("Unknown variant name %s"), appData.variant);
846         DisplayFatalError(buf, 0, 2);
847         return;
848
849       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
850       case VariantFairy:      /* [HGM] TestLegality definitely off! */
851       case VariantGothic:     /* [HGM] should work */
852       case VariantCapablanca: /* [HGM] should work */
853       case VariantCourier:    /* [HGM] initial forced moves not implemented */
854       case VariantShogi:      /* [HGM] drops not tested for legality */
855       case VariantKnightmate: /* [HGM] should work */
856       case VariantCylinder:   /* [HGM] untested */
857       case VariantFalcon:     /* [HGM] untested */
858       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859                                  offboard interposition not understood */
860       case VariantNormal:     /* definitely works! */
861       case VariantWildCastle: /* pieces not automatically shuffled */
862       case VariantNoCastle:   /* pieces not automatically shuffled */
863       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864       case VariantLosers:     /* should work except for win condition,
865                                  and doesn't know captures are mandatory */
866       case VariantSuicide:    /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantGiveaway:   /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantTwoKings:   /* should work */
871       case VariantAtomic:     /* should work except for win condition */
872       case Variant3Check:     /* should work except for win condition */
873       case VariantShatranj:   /* should work except for all win conditions */
874       case VariantBerolina:   /* might work if TestLegality is off */
875       case VariantCapaRandom: /* should work */
876       case VariantJanus:      /* should work */
877       case VariantSuper:      /* experimental */
878       case VariantGreat:      /* experimental, requires legality testing to be off */
879         break;
880       }
881     }
882
883     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
884     InitEngineUCI( installDir, &second );
885 }
886
887 int NextIntegerFromString( char ** str, long * value )
888 {
889     int result = -1;
890     char * s = *str;
891
892     while( *s == ' ' || *s == '\t' ) {
893         s++;
894     }
895
896     *value = 0;
897
898     if( *s >= '0' && *s <= '9' ) {
899         while( *s >= '0' && *s <= '9' ) {
900             *value = *value * 10 + (*s - '0');
901             s++;
902         }
903
904         result = 0;
905     }
906
907     *str = s;
908
909     return result;
910 }
911
912 int NextTimeControlFromString( char ** str, long * value )
913 {
914     long temp;
915     int result = NextIntegerFromString( str, &temp );
916
917     if( result == 0 ) {
918         *value = temp * 60; /* Minutes */
919         if( **str == ':' ) {
920             (*str)++;
921             result = NextIntegerFromString( str, &temp );
922             *value += temp; /* Seconds */
923         }
924     }
925
926     return result;
927 }
928
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
931     int result = -1; long temp, temp2;
932
933     if(**str != '+') return -1; // old params remain in force!
934     (*str)++;
935     if( NextTimeControlFromString( str, &temp ) ) return -1;
936
937     if(**str != '/') {
938         /* time only: incremental or sudden-death time control */
939         if(**str == '+') { /* increment follows; read it */
940             (*str)++;
941             if(result = NextIntegerFromString( str, &temp2)) return -1;
942             *inc = temp2 * 1000;
943         } else *inc = 0;
944         *moves = 0; *tc = temp * 1000;
945         return 0;
946     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
947
948     (*str)++; /* classical time control */
949     result = NextTimeControlFromString( str, &temp2);
950     if(result == 0) {
951         *moves = temp/60;
952         *tc    = temp2 * 1000;
953         *inc   = 0;
954     }
955     return result;
956 }
957
958 int GetTimeQuota(int movenr)
959 {   /* [HGM] get time to add from the multi-session time-control string */
960     int moves=1; /* kludge to force reading of first session */
961     long time, increment;
962     char *s = fullTimeControlString;
963
964     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
965     do {
966         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968         if(movenr == -1) return time;    /* last move before new session     */
969         if(!moves) return increment;     /* current session is incremental   */
970         if(movenr >= 0) movenr -= moves; /* we already finished this session */
971     } while(movenr >= -1);               /* try again for next session       */
972
973     return 0; // no new time quota on this move
974 }
975
976 int
977 ParseTimeControl(tc, ti, mps)
978      char *tc;
979      int ti;
980      int mps;
981 {
982   long tc1;
983   long tc2;
984   char buf[MSG_SIZ];
985   
986   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
987   if(ti > 0) {
988     if(mps)
989       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990     else sprintf(buf, "+%s+%d", tc, ti);
991   } else {
992     if(mps)
993              sprintf(buf, "+%d/%s", mps, tc);
994     else sprintf(buf, "+%s", tc);
995   }
996   fullTimeControlString = StrSave(buf);
997   
998   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
999     return FALSE;
1000   }
1001   
1002   if( *tc == '/' ) {
1003     /* Parse second time control */
1004     tc++;
1005     
1006     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1007       return FALSE;
1008     }
1009     
1010     if( tc2 == 0 ) {
1011       return FALSE;
1012     }
1013     
1014     timeControl_2 = tc2 * 1000;
1015   }
1016   else {
1017     timeControl_2 = 0;
1018   }
1019   
1020   if( tc1 == 0 ) {
1021     return FALSE;
1022   }
1023   
1024   timeControl = tc1 * 1000;
1025   
1026   if (ti >= 0) {
1027     timeIncrement = ti * 1000;  /* convert to ms */
1028     movesPerSession = 0;
1029   } else {
1030     timeIncrement = 0;
1031     movesPerSession = mps;
1032   }
1033   return TRUE;
1034 }
1035
1036 void
1037 InitBackEnd2()
1038 {
1039   if (appData.debugMode) {
1040     fprintf(debugFP, "%s\n", programVersion);
1041   }
1042
1043   if (appData.matchGames > 0) {
1044     appData.matchMode = TRUE;
1045   } else if (appData.matchMode) {
1046     appData.matchGames = 1;
1047   }
1048   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1049     appData.matchGames = appData.sameColorGames;
1050   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1051     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1052     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1053   }
1054   Reset(TRUE, FALSE);
1055   if (appData.noChessProgram || first.protocolVersion == 1) {
1056     InitBackEnd3();
1057   } else {
1058     /* kludge: allow timeout for initial "feature" commands */
1059     FreezeUI();
1060     DisplayMessage("", _("Starting chess program"));
1061     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1062   }
1063 }
1064
1065 void
1066 InitBackEnd3 P((void))
1067 {
1068     GameMode initialMode;
1069     char buf[MSG_SIZ];
1070     int err;
1071
1072     InitChessProgram(&first, startedFromSetupPosition);
1073
1074
1075     if (appData.icsActive) {
1076 #ifdef WIN32
1077         /* [DM] Make a console window if needed [HGM] merged ifs */
1078         ConsoleCreate();
1079 #endif
1080         err = establish();
1081         if (err != 0) {
1082             if (*appData.icsCommPort != NULLCHAR) {
1083                 sprintf(buf, _("Could not open comm port %s"),
1084                         appData.icsCommPort);
1085             } else {
1086                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1087                         appData.icsHost, appData.icsPort);
1088             }
1089             DisplayFatalError(buf, err, 1);
1090             return;
1091         }
1092         SetICSMode();
1093         telnetISR =
1094           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1095         fromUserISR =
1096           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1097     } else if (appData.noChessProgram) {
1098         SetNCPMode();
1099     } else {
1100         SetGNUMode();
1101     }
1102
1103     if (*appData.cmailGameName != NULLCHAR) {
1104         SetCmailMode();
1105         OpenLoopback(&cmailPR);
1106         cmailISR =
1107           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1108     }
1109
1110     ThawUI();
1111     DisplayMessage("", "");
1112     if (StrCaseCmp(appData.initialMode, "") == 0) {
1113       initialMode = BeginningOfGame;
1114     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1115       initialMode = TwoMachinesPlay;
1116     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1117       initialMode = AnalyzeFile;
1118     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1119       initialMode = AnalyzeMode;
1120     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1121       initialMode = MachinePlaysWhite;
1122     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1123       initialMode = MachinePlaysBlack;
1124     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1125       initialMode = EditGame;
1126     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1127       initialMode = EditPosition;
1128     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1129       initialMode = Training;
1130     } else {
1131       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1132       DisplayFatalError(buf, 0, 2);
1133       return;
1134     }
1135
1136     if (appData.matchMode) {
1137         /* Set up machine vs. machine match */
1138         if (appData.noChessProgram) {
1139             DisplayFatalError(_("Can't have a match with no chess programs"),
1140                               0, 2);
1141             return;
1142         }
1143         matchMode = TRUE;
1144         matchGame = 1;
1145         if (*appData.loadGameFile != NULLCHAR) {
1146             int index = appData.loadGameIndex; // [HGM] autoinc
1147             if(index<0) lastIndex = index = 1;
1148             if (!LoadGameFromFile(appData.loadGameFile,
1149                                   index,
1150                                   appData.loadGameFile, FALSE)) {
1151                 DisplayFatalError(_("Bad game file"), 0, 1);
1152                 return;
1153             }
1154         } else if (*appData.loadPositionFile != NULLCHAR) {
1155             int index = appData.loadPositionIndex; // [HGM] autoinc
1156             if(index<0) lastIndex = index = 1;
1157             if (!LoadPositionFromFile(appData.loadPositionFile,
1158                                       index,
1159                                       appData.loadPositionFile)) {
1160                 DisplayFatalError(_("Bad position file"), 0, 1);
1161                 return;
1162             }
1163         }
1164         TwoMachinesEvent();
1165     } else if (*appData.cmailGameName != NULLCHAR) {
1166         /* Set up cmail mode */
1167         ReloadCmailMsgEvent(TRUE);
1168     } else {
1169         /* Set up other modes */
1170         if (initialMode == AnalyzeFile) {
1171           if (*appData.loadGameFile == NULLCHAR) {
1172             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1173             return;
1174           }
1175         }
1176         if (*appData.loadGameFile != NULLCHAR) {
1177             (void) LoadGameFromFile(appData.loadGameFile,
1178                                     appData.loadGameIndex,
1179                                     appData.loadGameFile, TRUE);
1180         } else if (*appData.loadPositionFile != NULLCHAR) {
1181             (void) LoadPositionFromFile(appData.loadPositionFile,
1182                                         appData.loadPositionIndex,
1183                                         appData.loadPositionFile);
1184             /* [HGM] try to make self-starting even after FEN load */
1185             /* to allow automatic setup of fairy variants with wtm */
1186             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1187                 gameMode = BeginningOfGame;
1188                 setboardSpoiledMachineBlack = 1;
1189             }
1190             /* [HGM] loadPos: make that every new game uses the setup */
1191             /* from file as long as we do not switch variant          */
1192             if(!blackPlaysFirst) { int i;
1193                 startedFromPositionFile = TRUE;
1194                 CopyBoard(filePosition, boards[0]);
1195                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1196             }
1197         }
1198         if (initialMode == AnalyzeMode) {
1199           if (appData.noChessProgram) {
1200             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1201             return;
1202           }
1203           if (appData.icsActive) {
1204             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1205             return;
1206           }
1207           AnalyzeModeEvent();
1208         } else if (initialMode == AnalyzeFile) {
1209           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210           ShowThinkingEvent();
1211           AnalyzeFileEvent();
1212           AnalysisPeriodicEvent(1);
1213         } else if (initialMode == MachinePlaysWhite) {
1214           if (appData.noChessProgram) {
1215             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1216                               0, 2);
1217             return;
1218           }
1219           if (appData.icsActive) {
1220             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1221                               0, 2);
1222             return;
1223           }
1224           MachineWhiteEvent();
1225         } else if (initialMode == MachinePlaysBlack) {
1226           if (appData.noChessProgram) {
1227             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1228                               0, 2);
1229             return;
1230           }
1231           if (appData.icsActive) {
1232             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1233                               0, 2);
1234             return;
1235           }
1236           MachineBlackEvent();
1237         } else if (initialMode == TwoMachinesPlay) {
1238           if (appData.noChessProgram) {
1239             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1240                               0, 2);
1241             return;
1242           }
1243           if (appData.icsActive) {
1244             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1245                               0, 2);
1246             return;
1247           }
1248           TwoMachinesEvent();
1249         } else if (initialMode == EditGame) {
1250           EditGameEvent();
1251         } else if (initialMode == EditPosition) {
1252           EditPositionEvent();
1253         } else if (initialMode == Training) {
1254           if (*appData.loadGameFile == NULLCHAR) {
1255             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1256             return;
1257           }
1258           TrainingEvent();
1259         }
1260     }
1261 }
1262
1263 /*
1264  * Establish will establish a contact to a remote host.port.
1265  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266  *  used to talk to the host.
1267  * Returns 0 if okay, error code if not.
1268  */
1269 int
1270 establish()
1271 {
1272     char buf[MSG_SIZ];
1273
1274     if (*appData.icsCommPort != NULLCHAR) {
1275         /* Talk to the host through a serial comm port */
1276         return OpenCommPort(appData.icsCommPort, &icsPR);
1277
1278     } else if (*appData.gateway != NULLCHAR) {
1279         if (*appData.remoteShell == NULLCHAR) {
1280             /* Use the rcmd protocol to run telnet program on a gateway host */
1281             snprintf(buf, sizeof(buf), "%s %s %s",
1282                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1283             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1284
1285         } else {
1286             /* Use the rsh program to run telnet program on a gateway host */
1287             if (*appData.remoteUser == NULLCHAR) {
1288                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289                         appData.gateway, appData.telnetProgram,
1290                         appData.icsHost, appData.icsPort);
1291             } else {
1292                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293                         appData.remoteShell, appData.gateway,
1294                         appData.remoteUser, appData.telnetProgram,
1295                         appData.icsHost, appData.icsPort);
1296             }
1297             return StartChildProcess(buf, "", &icsPR);
1298
1299         }
1300     } else if (appData.useTelnet) {
1301         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1302
1303     } else {
1304         /* TCP socket interface differs somewhat between
1305            Unix and NT; handle details in the front end.
1306            */
1307         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1308     }
1309 }
1310
1311 void
1312 show_bytes(fp, buf, count)
1313      FILE *fp;
1314      char *buf;
1315      int count;
1316 {
1317     while (count--) {
1318         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319             fprintf(fp, "\\%03o", *buf & 0xff);
1320         } else {
1321             putc(*buf, fp);
1322         }
1323         buf++;
1324     }
1325     fflush(fp);
1326 }
1327
1328 /* Returns an errno value */
1329 int
1330 OutputMaybeTelnet(pr, message, count, outError)
1331      ProcRef pr;
1332      char *message;
1333      int count;
1334      int *outError;
1335 {
1336     char buf[8192], *p, *q, *buflim;
1337     int left, newcount, outcount;
1338
1339     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340         *appData.gateway != NULLCHAR) {
1341         if (appData.debugMode) {
1342             fprintf(debugFP, ">ICS: ");
1343             show_bytes(debugFP, message, count);
1344             fprintf(debugFP, "\n");
1345         }
1346         return OutputToProcess(pr, message, count, outError);
1347     }
1348
1349     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1350     p = message;
1351     q = buf;
1352     left = count;
1353     newcount = 0;
1354     while (left) {
1355         if (q >= buflim) {
1356             if (appData.debugMode) {
1357                 fprintf(debugFP, ">ICS: ");
1358                 show_bytes(debugFP, buf, newcount);
1359                 fprintf(debugFP, "\n");
1360             }
1361             outcount = OutputToProcess(pr, buf, newcount, outError);
1362             if (outcount < newcount) return -1; /* to be sure */
1363             q = buf;
1364             newcount = 0;
1365         }
1366         if (*p == '\n') {
1367             *q++ = '\r';
1368             newcount++;
1369         } else if (((unsigned char) *p) == TN_IAC) {
1370             *q++ = (char) TN_IAC;
1371             newcount ++;
1372         }
1373         *q++ = *p++;
1374         newcount++;
1375         left--;
1376     }
1377     if (appData.debugMode) {
1378         fprintf(debugFP, ">ICS: ");
1379         show_bytes(debugFP, buf, newcount);
1380         fprintf(debugFP, "\n");
1381     }
1382     outcount = OutputToProcess(pr, buf, newcount, outError);
1383     if (outcount < newcount) return -1; /* to be sure */
1384     return count;
1385 }
1386
1387 void
1388 read_from_player(isr, closure, message, count, error)
1389      InputSourceRef isr;
1390      VOIDSTAR closure;
1391      char *message;
1392      int count;
1393      int error;
1394 {
1395     int outError, outCount;
1396     static int gotEof = 0;
1397
1398     /* Pass data read from player on to ICS */
1399     if (count > 0) {
1400         gotEof = 0;
1401         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402         if (outCount < count) {
1403             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1404         }
1405     } else if (count < 0) {
1406         RemoveInputSource(isr);
1407         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408     } else if (gotEof++ > 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1411     }
1412 }
1413
1414 void
1415 KeepAlive()
1416 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417     SendToICS("date\n");
1418     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1419 }
1420
1421 void
1422 SendToICS(s)
1423      char *s;
1424 {
1425     int count, outCount, outError;
1426
1427     if (icsPR == NULL) return;
1428
1429     count = strlen(s);
1430     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1431     if (outCount < count) {
1432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1433     }
1434 }
1435
1436 /* This is used for sending logon scripts to the ICS. Sending
1437    without a delay causes problems when using timestamp on ICC
1438    (at least on my machine). */
1439 void
1440 SendToICSDelayed(s,msdelay)
1441      char *s;
1442      long msdelay;
1443 {
1444     int count, outCount, outError;
1445
1446     if (icsPR == NULL) return;
1447
1448     count = strlen(s);
1449     if (appData.debugMode) {
1450         fprintf(debugFP, ">ICS: ");
1451         show_bytes(debugFP, s, count);
1452         fprintf(debugFP, "\n");
1453     }
1454     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1455                                       msdelay);
1456     if (outCount < count) {
1457         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1458     }
1459 }
1460
1461
1462 /* Remove all highlighting escape sequences in s
1463    Also deletes any suffix starting with '('
1464    */
1465 char *
1466 StripHighlightAndTitle(s)
1467      char *s;
1468 {
1469     static char retbuf[MSG_SIZ];
1470     char *p = retbuf;
1471
1472     while (*s != NULLCHAR) {
1473         while (*s == '\033') {
1474             while (*s != NULLCHAR && !isalpha(*s)) s++;
1475             if (*s != NULLCHAR) s++;
1476         }
1477         while (*s != NULLCHAR && *s != '\033') {
1478             if (*s == '(' || *s == '[') {
1479                 *p = NULLCHAR;
1480                 return retbuf;
1481             }
1482             *p++ = *s++;
1483         }
1484     }
1485     *p = NULLCHAR;
1486     return retbuf;
1487 }
1488
1489 /* Remove all highlighting escape sequences in s */
1490 char *
1491 StripHighlight(s)
1492      char *s;
1493 {
1494     static char retbuf[MSG_SIZ];
1495     char *p = retbuf;
1496
1497     while (*s != NULLCHAR) {
1498         while (*s == '\033') {
1499             while (*s != NULLCHAR && !isalpha(*s)) s++;
1500             if (*s != NULLCHAR) s++;
1501         }
1502         while (*s != NULLCHAR && *s != '\033') {
1503             *p++ = *s++;
1504         }
1505     }
1506     *p = NULLCHAR;
1507     return retbuf;
1508 }
1509
1510 char *variantNames[] = VARIANT_NAMES;
1511 char *
1512 VariantName(v)
1513      VariantClass v;
1514 {
1515     return variantNames[v];
1516 }
1517
1518
1519 /* Identify a variant from the strings the chess servers use or the
1520    PGN Variant tag names we use. */
1521 VariantClass
1522 StringToVariant(e)
1523      char *e;
1524 {
1525     char *p;
1526     int wnum = -1;
1527     VariantClass v = VariantNormal;
1528     int i, found = FALSE;
1529     char buf[MSG_SIZ];
1530
1531     if (!e) return v;
1532
1533     /* [HGM] skip over optional board-size prefixes */
1534     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1535         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1536         while( *e++ != '_');
1537     }
1538
1539     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1540       if (StrCaseStr(e, variantNames[i])) {
1541         v = (VariantClass) i;
1542         found = TRUE;
1543         break;
1544       }
1545     }
1546
1547     if (!found) {
1548       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1549           || StrCaseStr(e, "wild/fr")
1550           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1551         v = VariantFischeRandom;
1552       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1553                  (i = 1, p = StrCaseStr(e, "w"))) {
1554         p += i;
1555         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1556         if (isdigit(*p)) {
1557           wnum = atoi(p);
1558         } else {
1559           wnum = -1;
1560         }
1561         switch (wnum) {
1562         case 0: /* FICS only, actually */
1563         case 1:
1564           /* Castling legal even if K starts on d-file */
1565           v = VariantWildCastle;
1566           break;
1567         case 2:
1568         case 3:
1569         case 4:
1570           /* Castling illegal even if K & R happen to start in
1571              normal positions. */
1572           v = VariantNoCastle;
1573           break;
1574         case 5:
1575         case 7:
1576         case 8:
1577         case 10:
1578         case 11:
1579         case 12:
1580         case 13:
1581         case 14:
1582         case 15:
1583         case 18:
1584         case 19:
1585           /* Castling legal iff K & R start in normal positions */
1586           v = VariantNormal;
1587           break;
1588         case 6:
1589         case 20:
1590         case 21:
1591           /* Special wilds for position setup; unclear what to do here */
1592           v = VariantLoadable;
1593           break;
1594         case 9:
1595           /* Bizarre ICC game */
1596           v = VariantTwoKings;
1597           break;
1598         case 16:
1599           v = VariantKriegspiel;
1600           break;
1601         case 17:
1602           v = VariantLosers;
1603           break;
1604         case 22:
1605           v = VariantFischeRandom;
1606           break;
1607         case 23:
1608           v = VariantCrazyhouse;
1609           break;
1610         case 24:
1611           v = VariantBughouse;
1612           break;
1613         case 25:
1614           v = Variant3Check;
1615           break;
1616         case 26:
1617           /* Not quite the same as FICS suicide! */
1618           v = VariantGiveaway;
1619           break;
1620         case 27:
1621           v = VariantAtomic;
1622           break;
1623         case 28:
1624           v = VariantShatranj;
1625           break;
1626
1627         /* Temporary names for future ICC types.  The name *will* change in
1628            the next xboard/WinBoard release after ICC defines it. */
1629         case 29:
1630           v = Variant29;
1631           break;
1632         case 30:
1633           v = Variant30;
1634           break;
1635         case 31:
1636           v = Variant31;
1637           break;
1638         case 32:
1639           v = Variant32;
1640           break;
1641         case 33:
1642           v = Variant33;
1643           break;
1644         case 34:
1645           v = Variant34;
1646           break;
1647         case 35:
1648           v = Variant35;
1649           break;
1650         case 36:
1651           v = Variant36;
1652           break;
1653         case 37:
1654           v = VariantShogi;
1655           break;
1656         case 38:
1657           v = VariantXiangqi;
1658           break;
1659         case 39:
1660           v = VariantCourier;
1661           break;
1662         case 40:
1663           v = VariantGothic;
1664           break;
1665         case 41:
1666           v = VariantCapablanca;
1667           break;
1668         case 42:
1669           v = VariantKnightmate;
1670           break;
1671         case 43:
1672           v = VariantFairy;
1673           break;
1674         case 44:
1675           v = VariantCylinder;
1676           break;
1677         case 45:
1678           v = VariantFalcon;
1679           break;
1680         case 46:
1681           v = VariantCapaRandom;
1682           break;
1683         case 47:
1684           v = VariantBerolina;
1685           break;
1686         case 48:
1687           v = VariantJanus;
1688           break;
1689         case 49:
1690           v = VariantSuper;
1691           break;
1692         case 50:
1693           v = VariantGreat;
1694           break;
1695         case -1:
1696           /* Found "wild" or "w" in the string but no number;
1697              must assume it's normal chess. */
1698           v = VariantNormal;
1699           break;
1700         default:
1701           sprintf(buf, _("Unknown wild type %d"), wnum);
1702           DisplayError(buf, 0);
1703           v = VariantUnknown;
1704           break;
1705         }
1706       }
1707     }
1708     if (appData.debugMode) {
1709       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1710               e, wnum, VariantName(v));
1711     }
1712     return v;
1713 }
1714
1715 static int leftover_start = 0, leftover_len = 0;
1716 char star_match[STAR_MATCH_N][MSG_SIZ];
1717
1718 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1719    advance *index beyond it, and set leftover_start to the new value of
1720    *index; else return FALSE.  If pattern contains the character '*', it
1721    matches any sequence of characters not containing '\r', '\n', or the
1722    character following the '*' (if any), and the matched sequence(s) are
1723    copied into star_match.
1724    */
1725 int
1726 looking_at(buf, index, pattern)
1727      char *buf;
1728      int *index;
1729      char *pattern;
1730 {
1731     char *bufp = &buf[*index], *patternp = pattern;
1732     int star_count = 0;
1733     char *matchp = star_match[0];
1734
1735     for (;;) {
1736         if (*patternp == NULLCHAR) {
1737             *index = leftover_start = bufp - buf;
1738             *matchp = NULLCHAR;
1739             return TRUE;
1740         }
1741         if (*bufp == NULLCHAR) return FALSE;
1742         if (*patternp == '*') {
1743             if (*bufp == *(patternp + 1)) {
1744                 *matchp = NULLCHAR;
1745                 matchp = star_match[++star_count];
1746                 patternp += 2;
1747                 bufp++;
1748                 continue;
1749             } else if (*bufp == '\n' || *bufp == '\r') {
1750                 patternp++;
1751                 if (*patternp == NULLCHAR)
1752                   continue;
1753                 else
1754                   return FALSE;
1755             } else {
1756                 *matchp++ = *bufp++;
1757                 continue;
1758             }
1759         }
1760         if (*patternp != *bufp) return FALSE;
1761         patternp++;
1762         bufp++;
1763     }
1764 }
1765
1766 void
1767 SendToPlayer(data, length)
1768      char *data;
1769      int length;
1770 {
1771     int error, outCount;
1772     outCount = OutputToProcess(NoProc, data, length, &error);
1773     if (outCount < length) {
1774         DisplayFatalError(_("Error writing to display"), error, 1);
1775     }
1776 }
1777
1778 void
1779 PackHolding(packed, holding)
1780      char packed[];
1781      char *holding;
1782 {
1783     char *p = holding;
1784     char *q = packed;
1785     int runlength = 0;
1786     int curr = 9999;
1787     do {
1788         if (*p == curr) {
1789             runlength++;
1790         } else {
1791             switch (runlength) {
1792               case 0:
1793                 break;
1794               case 1:
1795                 *q++ = curr;
1796                 break;
1797               case 2:
1798                 *q++ = curr;
1799                 *q++ = curr;
1800                 break;
1801               default:
1802                 sprintf(q, "%d", runlength);
1803                 while (*q) q++;
1804                 *q++ = curr;
1805                 break;
1806             }
1807             runlength = 1;
1808             curr = *p;
1809         }
1810     } while (*p++);
1811     *q = NULLCHAR;
1812 }
1813
1814 /* Telnet protocol requests from the front end */
1815 void
1816 TelnetRequest(ddww, option)
1817      unsigned char ddww, option;
1818 {
1819     unsigned char msg[3];
1820     int outCount, outError;
1821
1822     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1823
1824     if (appData.debugMode) {
1825         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1826         switch (ddww) {
1827           case TN_DO:
1828             ddwwStr = "DO";
1829             break;
1830           case TN_DONT:
1831             ddwwStr = "DONT";
1832             break;
1833           case TN_WILL:
1834             ddwwStr = "WILL";
1835             break;
1836           case TN_WONT:
1837             ddwwStr = "WONT";
1838             break;
1839           default:
1840             ddwwStr = buf1;
1841             sprintf(buf1, "%d", ddww);
1842             break;
1843         }
1844         switch (option) {
1845           case TN_ECHO:
1846             optionStr = "ECHO";
1847             break;
1848           default:
1849             optionStr = buf2;
1850             sprintf(buf2, "%d", option);
1851             break;
1852         }
1853         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1854     }
1855     msg[0] = TN_IAC;
1856     msg[1] = ddww;
1857     msg[2] = option;
1858     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1859     if (outCount < 3) {
1860         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1861     }
1862 }
1863
1864 void
1865 DoEcho()
1866 {
1867     if (!appData.icsActive) return;
1868     TelnetRequest(TN_DO, TN_ECHO);
1869 }
1870
1871 void
1872 DontEcho()
1873 {
1874     if (!appData.icsActive) return;
1875     TelnetRequest(TN_DONT, TN_ECHO);
1876 }
1877
1878 void
1879 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1880 {
1881     /* put the holdings sent to us by the server on the board holdings area */
1882     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1883     char p;
1884     ChessSquare piece;
1885
1886     if(gameInfo.holdingsWidth < 2)  return;
1887
1888     if( (int)lowestPiece >= BlackPawn ) {
1889         holdingsColumn = 0;
1890         countsColumn = 1;
1891         holdingsStartRow = BOARD_HEIGHT-1;
1892         direction = -1;
1893     } else {
1894         holdingsColumn = BOARD_WIDTH-1;
1895         countsColumn = BOARD_WIDTH-2;
1896         holdingsStartRow = 0;
1897         direction = 1;
1898     }
1899
1900     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1901         board[i][holdingsColumn] = EmptySquare;
1902         board[i][countsColumn]   = (ChessSquare) 0;
1903     }
1904     while( (p=*holdings++) != NULLCHAR ) {
1905         piece = CharToPiece( ToUpper(p) );
1906         if(piece == EmptySquare) continue;
1907         /*j = (int) piece - (int) WhitePawn;*/
1908         j = PieceToNumber(piece);
1909         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1910         if(j < 0) continue;               /* should not happen */
1911         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1912         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1913         board[holdingsStartRow+j*direction][countsColumn]++;
1914     }
1915
1916 }
1917
1918
1919 void
1920 VariantSwitch(Board board, VariantClass newVariant)
1921 {
1922    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1923    int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1924 //   Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1925
1926    startedFromPositionFile = FALSE;
1927    if(gameInfo.variant == newVariant) return;
1928
1929    /* [HGM] This routine is called each time an assignment is made to
1930     * gameInfo.variant during a game, to make sure the board sizes
1931     * are set to match the new variant. If that means adding or deleting
1932     * holdings, we shift the playing board accordingly
1933     * This kludge is needed because in ICS observe mode, we get boards
1934     * of an ongoing game without knowing the variant, and learn about the
1935     * latter only later. This can be because of the move list we requested,
1936     * in which case the game history is refilled from the beginning anyway,
1937     * but also when receiving holdings of a crazyhouse game. In the latter
1938     * case we want to add those holdings to the already received position.
1939     */
1940
1941
1942   if (appData.debugMode) {
1943     fprintf(debugFP, "Switch board from %s to %s\n",
1944                VariantName(gameInfo.variant), VariantName(newVariant));
1945     setbuf(debugFP, NULL);
1946   }
1947     shuffleOpenings = 0;       /* [HGM] shuffle */
1948     gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1949     switch(newVariant) {
1950             case VariantShogi:
1951               newWidth = 9;  newHeight = 9;
1952               gameInfo.holdingsSize = 7;
1953             case VariantBughouse:
1954             case VariantCrazyhouse:
1955               newHoldingsWidth = 2; break;
1956             default:
1957               newHoldingsWidth = gameInfo.holdingsSize = 0;
1958     }
1959
1960     if(newWidth  != gameInfo.boardWidth  ||
1961        newHeight != gameInfo.boardHeight ||
1962        newHoldingsWidth != gameInfo.holdingsWidth ) {
1963
1964         /* shift position to new playing area, if needed */
1965         if(newHoldingsWidth > gameInfo.holdingsWidth) {
1966            for(i=0; i<BOARD_HEIGHT; i++)
1967                for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1968                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1969                                                      board[i][j];
1970            for(i=0; i<newHeight; i++) {
1971                board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1972                board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1973            }
1974         } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1975            for(i=0; i<BOARD_HEIGHT; i++)
1976                for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1977                    board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1978                                                  board[i][j];
1979         }
1980
1981         gameInfo.boardWidth  = newWidth;
1982         gameInfo.boardHeight = newHeight;
1983         gameInfo.holdingsWidth = newHoldingsWidth;
1984         gameInfo.variant = newVariant;
1985         InitDrawingSizes(-2, 0);
1986
1987         /* [HGM] The following should definitely be solved in a better way */
1988         InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
1989     } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
1990
1991     forwardMostMove = oldForwardMostMove;
1992     backwardMostMove = oldBackwardMostMove;
1993     currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
1994 }
1995
1996 static int loggedOn = FALSE;
1997
1998 /*-- Game start info cache: --*/
1999 int gs_gamenum;
2000 char gs_kind[MSG_SIZ];
2001 static char player1Name[128] = "";
2002 static char player2Name[128] = "";
2003 static int player1Rating = -1;
2004 static int player2Rating = -1;
2005 /*----------------------------*/
2006
2007 ColorClass curColor = ColorNormal;
2008 int suppressKibitz = 0;
2009
2010 void
2011 read_from_ics(isr, closure, data, count, error)
2012      InputSourceRef isr;
2013      VOIDSTAR closure;
2014      char *data;
2015      int count;
2016      int error;
2017 {
2018 #define BUF_SIZE 8192
2019 #define STARTED_NONE 0
2020 #define STARTED_MOVES 1
2021 #define STARTED_BOARD 2
2022 #define STARTED_OBSERVE 3
2023 #define STARTED_HOLDINGS 4
2024 #define STARTED_CHATTER 5
2025 #define STARTED_COMMENT 6
2026 #define STARTED_MOVES_NOHIDE 7
2027
2028     static int started = STARTED_NONE;
2029     static char parse[20000];
2030     static int parse_pos = 0;
2031     static char buf[BUF_SIZE + 1];
2032     static int firstTime = TRUE, intfSet = FALSE;
2033     static ColorClass prevColor = ColorNormal;
2034     static int savingComment = FALSE;
2035     char str[500];
2036     int i, oldi;
2037     int buf_len;
2038     int next_out;
2039     int tkind;
2040     int backup;    /* [DM] For zippy color lines */
2041     char *p;
2042     char talker[MSG_SIZ]; // [HGM] chat
2043     int channel;
2044
2045     if (appData.debugMode) {
2046       if (!error) {
2047         fprintf(debugFP, "<ICS: ");
2048         show_bytes(debugFP, data, count);
2049         fprintf(debugFP, "\n");
2050       }
2051     }
2052
2053     if (appData.debugMode) { int f = forwardMostMove;
2054         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2055                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2056     }
2057     if (count > 0) {
2058         /* If last read ended with a partial line that we couldn't parse,
2059            prepend it to the new read and try again. */
2060         if (leftover_len > 0) {
2061             for (i=0; i<leftover_len; i++)
2062               buf[i] = buf[leftover_start + i];
2063         }
2064
2065         /* Copy in new characters, removing nulls and \r's */
2066         buf_len = leftover_len;
2067         for (i = 0; i < count; i++) {
2068             if (data[i] != NULLCHAR && data[i] != '\r')
2069               buf[buf_len++] = data[i];
2070             if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2071                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2072                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2073                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2074                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2075             }
2076         }
2077
2078         buf[buf_len] = NULLCHAR;
2079         next_out = leftover_len;
2080         leftover_start = 0;
2081
2082         i = 0;
2083         while (i < buf_len) {
2084             /* Deal with part of the TELNET option negotiation
2085                protocol.  We refuse to do anything beyond the
2086                defaults, except that we allow the WILL ECHO option,
2087                which ICS uses to turn off password echoing when we are
2088                directly connected to it.  We reject this option
2089                if localLineEditing mode is on (always on in xboard)
2090                and we are talking to port 23, which might be a real
2091                telnet server that will try to keep WILL ECHO on permanently.
2092              */
2093             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2094                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2095                 unsigned char option;
2096                 oldi = i;
2097                 switch ((unsigned char) buf[++i]) {
2098                   case TN_WILL:
2099                     if (appData.debugMode)
2100                       fprintf(debugFP, "\n<WILL ");
2101                     switch (option = (unsigned char) buf[++i]) {
2102                       case TN_ECHO:
2103                         if (appData.debugMode)
2104                           fprintf(debugFP, "ECHO ");
2105                         /* Reply only if this is a change, according
2106                            to the protocol rules. */
2107                         if (remoteEchoOption) break;
2108                         if (appData.localLineEditing &&
2109                             atoi(appData.icsPort) == TN_PORT) {
2110                             TelnetRequest(TN_DONT, TN_ECHO);
2111                         } else {
2112                             EchoOff();
2113                             TelnetRequest(TN_DO, TN_ECHO);
2114                             remoteEchoOption = TRUE;
2115                         }
2116                         break;
2117                       default:
2118                         if (appData.debugMode)
2119                           fprintf(debugFP, "%d ", option);
2120                         /* Whatever this is, we don't want it. */
2121                         TelnetRequest(TN_DONT, option);
2122                         break;
2123                     }
2124                     break;
2125                   case TN_WONT:
2126                     if (appData.debugMode)
2127                       fprintf(debugFP, "\n<WONT ");
2128                     switch (option = (unsigned char) buf[++i]) {
2129                       case TN_ECHO:
2130                         if (appData.debugMode)
2131                           fprintf(debugFP, "ECHO ");
2132                         /* Reply only if this is a change, according
2133                            to the protocol rules. */
2134                         if (!remoteEchoOption) break;
2135                         EchoOn();
2136                         TelnetRequest(TN_DONT, TN_ECHO);
2137                         remoteEchoOption = FALSE;
2138                         break;
2139                       default:
2140                         if (appData.debugMode)
2141                           fprintf(debugFP, "%d ", (unsigned char) option);
2142                         /* Whatever this is, it must already be turned
2143                            off, because we never agree to turn on
2144                            anything non-default, so according to the
2145                            protocol rules, we don't reply. */
2146                         break;
2147                     }
2148                     break;
2149                   case TN_DO:
2150                     if (appData.debugMode)
2151                       fprintf(debugFP, "\n<DO ");
2152                     switch (option = (unsigned char) buf[++i]) {
2153                       default:
2154                         /* Whatever this is, we refuse to do it. */
2155                         if (appData.debugMode)
2156                           fprintf(debugFP, "%d ", option);
2157                         TelnetRequest(TN_WONT, option);
2158                         break;
2159                     }
2160                     break;
2161                   case TN_DONT:
2162                     if (appData.debugMode)
2163                       fprintf(debugFP, "\n<DONT ");
2164                     switch (option = (unsigned char) buf[++i]) {
2165                       default:
2166                         if (appData.debugMode)
2167                           fprintf(debugFP, "%d ", option);
2168                         /* Whatever this is, we are already not doing
2169                            it, because we never agree to do anything
2170                            non-default, so according to the protocol
2171                            rules, we don't reply. */
2172                         break;
2173                     }
2174                     break;
2175                   case TN_IAC:
2176                     if (appData.debugMode)
2177                       fprintf(debugFP, "\n<IAC ");
2178                     /* Doubled IAC; pass it through */
2179                     i--;
2180                     break;
2181                   default:
2182                     if (appData.debugMode)
2183                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2184                     /* Drop all other telnet commands on the floor */
2185                     break;
2186                 }
2187                 if (oldi > next_out)
2188                   SendToPlayer(&buf[next_out], oldi - next_out);
2189                 if (++i > next_out)
2190                   next_out = i;
2191                 continue;
2192             }
2193
2194             /* OK, this at least will *usually* work */
2195             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2196                 loggedOn = TRUE;
2197             }
2198
2199             if (loggedOn && !intfSet) {
2200                 if (ics_type == ICS_ICC) {
2201                   sprintf(str,
2202                           "/set-quietly interface %s\n/set-quietly style 12\n",
2203                           programVersion);
2204
2205                 } else if (ics_type == ICS_CHESSNET) {
2206                   sprintf(str, "/style 12\n");
2207                 } else {
2208                   strcpy(str, "alias $ @\n$set interface ");
2209                   strcat(str, programVersion);
2210                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2211 #ifdef WIN32
2212                   strcat(str, "$iset nohighlight 1\n");
2213 #endif
2214                   strcat(str, "$iset lock 1\n$style 12\n");
2215                 }
2216                 SendToICS(str);
2217                 intfSet = TRUE;
2218             }
2219
2220             if (started == STARTED_COMMENT) {
2221                 /* Accumulate characters in comment */
2222                 parse[parse_pos++] = buf[i];
2223                 if (buf[i] == '\n') {
2224                     parse[parse_pos] = NULLCHAR;
2225                     if(chattingPartner>=0) {
2226                         char mess[MSG_SIZ];
2227                         sprintf(mess, "%s%s", talker, parse);
2228                         OutputChatMessage(chattingPartner, mess);
2229                         chattingPartner = -1;
2230                     } else
2231                     if(!suppressKibitz) // [HGM] kibitz
2232                         AppendComment(forwardMostMove, StripHighlight(parse));
2233                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2234                         int nrDigit = 0, nrAlph = 0, i;
2235                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2236                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2237                         parse[parse_pos] = NULLCHAR;
2238                         // try to be smart: if it does not look like search info, it should go to
2239                         // ICS interaction window after all, not to engine-output window.
2240                         for(i=0; i<parse_pos; i++) { // count letters and digits
2241                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2242                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2243                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2244                         }
2245                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2246                             int depth=0; float score;
2247                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2248                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2249                                 pvInfoList[forwardMostMove-1].depth = depth;
2250                                 pvInfoList[forwardMostMove-1].score = 100*score;
2251                             }
2252                             OutputKibitz(suppressKibitz, parse);
2253                         } else {
2254                             char tmp[MSG_SIZ];
2255                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2256                             SendToPlayer(tmp, strlen(tmp));
2257                         }
2258                     }
2259                     started = STARTED_NONE;
2260                 } else {
2261                     /* Don't match patterns against characters in chatter */
2262                     i++;
2263                     continue;
2264                 }
2265             }
2266             if (started == STARTED_CHATTER) {
2267                 if (buf[i] != '\n') {
2268                     /* Don't match patterns against characters in chatter */
2269                     i++;
2270                     continue;
2271                 }
2272                 started = STARTED_NONE;
2273             }
2274
2275             /* Kludge to deal with rcmd protocol */
2276             if (firstTime && looking_at(buf, &i, "\001*")) {
2277                 DisplayFatalError(&buf[1], 0, 1);
2278                 continue;
2279             } else {
2280                 firstTime = FALSE;
2281             }
2282
2283             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2284                 ics_type = ICS_ICC;
2285                 ics_prefix = "/";
2286                 if (appData.debugMode)
2287                   fprintf(debugFP, "ics_type %d\n", ics_type);
2288                 continue;
2289             }
2290             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2291                 ics_type = ICS_FICS;
2292                 ics_prefix = "$";
2293                 if (appData.debugMode)
2294                   fprintf(debugFP, "ics_type %d\n", ics_type);
2295                 continue;
2296             }
2297             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2298                 ics_type = ICS_CHESSNET;
2299                 ics_prefix = "/";
2300                 if (appData.debugMode)
2301                   fprintf(debugFP, "ics_type %d\n", ics_type);
2302                 continue;
2303             }
2304
2305             if (!loggedOn &&
2306                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2307                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2308                  looking_at(buf, &i, "will be \"*\""))) {
2309               strcpy(ics_handle, star_match[0]);
2310               continue;
2311             }
2312
2313             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2314               char buf[MSG_SIZ];
2315               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2316               DisplayIcsInteractionTitle(buf);
2317               have_set_title = TRUE;
2318             }
2319
2320             /* skip finger notes */
2321             if (started == STARTED_NONE &&
2322                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2323                  (buf[i] == '1' && buf[i+1] == '0')) &&
2324                 buf[i+2] == ':' && buf[i+3] == ' ') {
2325               started = STARTED_CHATTER;
2326               i += 3;
2327               continue;
2328             }
2329
2330             /* skip formula vars */
2331             if (started == STARTED_NONE &&
2332                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2333               started = STARTED_CHATTER;
2334               i += 3;
2335               continue;
2336             }
2337
2338             oldi = i;
2339             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2340             if (appData.autoKibitz && started == STARTED_NONE &&
2341                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2342                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2343                 if(looking_at(buf, &i, "* kibitzes: ") &&
2344                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2345                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2346                         suppressKibitz = TRUE;
2347                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2348                                 && (gameMode == IcsPlayingWhite)) ||
2349                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2350                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2351                             started = STARTED_CHATTER; // own kibitz we simply discard
2352                         else {
2353                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2354                             parse_pos = 0; parse[0] = NULLCHAR;
2355                             savingComment = TRUE;
2356                             suppressKibitz = gameMode != IcsObserving ? 2 :
2357                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2358                         }
2359                         continue;
2360                 } else
2361                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2362                     started = STARTED_CHATTER;
2363                     suppressKibitz = TRUE;
2364                 }
2365             } // [HGM] kibitz: end of patch
2366
2367 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2368
2369             // [HGM] chat: intercept tells by users for which we have an open chat window
2370             channel = -1;
2371             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2372                                            looking_at(buf, &i, "* whispers:") ||
2373                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2374                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2375                 int p;
2376                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2377                 chattingPartner = -1;
2378
2379                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2380                 for(p=0; p<MAX_CHAT; p++) {
2381                     if(channel == atoi(chatPartner[p])) {
2382                     talker[0] = '['; strcat(talker, "]");
2383                     chattingPartner = p; break;
2384                     }
2385                 } else
2386                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2387                 for(p=0; p<MAX_CHAT; p++) {
2388                     if(!strcmp("WHISPER", chatPartner[p])) {
2389                         talker[0] = '['; strcat(talker, "]");
2390                         chattingPartner = p; break;
2391                     }
2392                 }
2393                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2394                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2395                     talker[0] = 0;
2396                     chattingPartner = p; break;
2397                 }
2398                 if(chattingPartner<0) i = oldi; else {
2399                     started = STARTED_COMMENT;
2400                     parse_pos = 0; parse[0] = NULLCHAR;
2401                     savingComment = TRUE;
2402                     suppressKibitz = TRUE;
2403                 }
2404             } // [HGM] chat: end of patch
2405
2406             if (appData.zippyTalk || appData.zippyPlay) {
2407                 /* [DM] Backup address for color zippy lines */
2408                 backup = i;
2409 #if ZIPPY
2410        #ifdef WIN32
2411                if (loggedOn == TRUE)
2412                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2413                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2414        #else
2415                 if (ZippyControl(buf, &i) ||
2416                     ZippyConverse(buf, &i) ||
2417                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2418                       loggedOn = TRUE;
2419                       if (!appData.colorize) continue;
2420                 }
2421        #endif
2422 #endif
2423             } // [DM] 'else { ' deleted
2424                 if (
2425                     /* Regular tells and says */
2426                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2427                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2428                     looking_at(buf, &i, "* says: ") ||
2429                     /* Don't color "message" or "messages" output */
2430                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2431                     looking_at(buf, &i, "*. * at *:*: ") ||
2432                     looking_at(buf, &i, "--* (*:*): ") ||
2433                     /* Message notifications (same color as tells) */
2434                     looking_at(buf, &i, "* has left a message ") ||
2435                     looking_at(buf, &i, "* just sent you a message:\n") ||
2436                     /* Whispers and kibitzes */
2437                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2438                     looking_at(buf, &i, "* kibitzes: ") ||
2439                     /* Channel tells */
2440                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2441
2442                   if (tkind == 1 && strchr(star_match[0], ':')) {
2443                       /* Avoid "tells you:" spoofs in channels */
2444                      tkind = 3;
2445                   }
2446                   if (star_match[0][0] == NULLCHAR ||
2447                       strchr(star_match[0], ' ') ||
2448                       (tkind == 3 && strchr(star_match[1], ' '))) {
2449                     /* Reject bogus matches */
2450                     i = oldi;
2451                   } else {
2452                     if (appData.colorize) {
2453                       if (oldi > next_out) {
2454                         SendToPlayer(&buf[next_out], oldi - next_out);
2455                         next_out = oldi;
2456                       }
2457                       switch (tkind) {
2458                       case 1:
2459                         Colorize(ColorTell, FALSE);
2460                         curColor = ColorTell;
2461                         break;
2462                       case 2:
2463                         Colorize(ColorKibitz, FALSE);
2464                         curColor = ColorKibitz;
2465                         break;
2466                       case 3:
2467                         p = strrchr(star_match[1], '(');
2468                         if (p == NULL) {
2469                           p = star_match[1];
2470                         } else {
2471                           p++;
2472                         }
2473                         if (atoi(p) == 1) {
2474                           Colorize(ColorChannel1, FALSE);
2475                           curColor = ColorChannel1;
2476                         } else {
2477                           Colorize(ColorChannel, FALSE);
2478                           curColor = ColorChannel;
2479                         }
2480                         break;
2481                       case 5:
2482                         curColor = ColorNormal;
2483                         break;
2484                       }
2485                     }
2486                     if (started == STARTED_NONE && appData.autoComment &&
2487                         (gameMode == IcsObserving ||
2488                          gameMode == IcsPlayingWhite ||
2489                          gameMode == IcsPlayingBlack)) {
2490                       parse_pos = i - oldi;
2491                       memcpy(parse, &buf[oldi], parse_pos);
2492                       parse[parse_pos] = NULLCHAR;
2493                       started = STARTED_COMMENT;
2494                       savingComment = TRUE;
2495                     } else {
2496                       started = STARTED_CHATTER;
2497                       savingComment = FALSE;
2498                     }
2499                     loggedOn = TRUE;
2500                     continue;
2501                   }
2502                 }
2503
2504                 if (looking_at(buf, &i, "* s-shouts: ") ||
2505                     looking_at(buf, &i, "* c-shouts: ")) {
2506                     if (appData.colorize) {
2507                         if (oldi > next_out) {
2508                             SendToPlayer(&buf[next_out], oldi - next_out);
2509                             next_out = oldi;
2510                         }
2511                         Colorize(ColorSShout, FALSE);
2512                         curColor = ColorSShout;
2513                     }
2514                     loggedOn = TRUE;
2515                     started = STARTED_CHATTER;
2516                     continue;
2517                 }
2518
2519                 if (looking_at(buf, &i, "--->")) {
2520                     loggedOn = TRUE;
2521                     continue;
2522                 }
2523
2524                 if (looking_at(buf, &i, "* shouts: ") ||
2525                     looking_at(buf, &i, "--> ")) {
2526                     if (appData.colorize) {
2527                         if (oldi > next_out) {
2528                             SendToPlayer(&buf[next_out], oldi - next_out);
2529                             next_out = oldi;
2530                         }
2531                         Colorize(ColorShout, FALSE);
2532                         curColor = ColorShout;
2533                     }
2534                     loggedOn = TRUE;
2535                     started = STARTED_CHATTER;
2536                     continue;
2537                 }
2538
2539                 if (looking_at( buf, &i, "Challenge:")) {
2540                     if (appData.colorize) {
2541                         if (oldi > next_out) {
2542                             SendToPlayer(&buf[next_out], oldi - next_out);
2543                             next_out = oldi;
2544                         }
2545                         Colorize(ColorChallenge, FALSE);
2546                         curColor = ColorChallenge;
2547                     }
2548                     loggedOn = TRUE;
2549                     continue;
2550                 }
2551
2552                 if (looking_at(buf, &i, "* offers you") ||
2553                     looking_at(buf, &i, "* offers to be") ||
2554                     looking_at(buf, &i, "* would like to") ||
2555                     looking_at(buf, &i, "* requests to") ||
2556                     looking_at(buf, &i, "Your opponent offers") ||
2557                     looking_at(buf, &i, "Your opponent requests")) {
2558
2559                     if (appData.colorize) {
2560                         if (oldi > next_out) {
2561                             SendToPlayer(&buf[next_out], oldi - next_out);
2562                             next_out = oldi;
2563                         }
2564                         Colorize(ColorRequest, FALSE);
2565                         curColor = ColorRequest;
2566                     }
2567                     continue;
2568                 }
2569
2570                 if (looking_at(buf, &i, "* (*) seeking")) {
2571                     if (appData.colorize) {
2572                         if (oldi > next_out) {
2573                             SendToPlayer(&buf[next_out], oldi - next_out);
2574                             next_out = oldi;
2575                         }
2576                         Colorize(ColorSeek, FALSE);
2577                         curColor = ColorSeek;
2578                     }
2579                     continue;
2580             }
2581
2582             if (looking_at(buf, &i, "\\   ")) {
2583                 if (prevColor != ColorNormal) {
2584                     if (oldi > next_out) {
2585                         SendToPlayer(&buf[next_out], oldi - next_out);
2586                         next_out = oldi;
2587                     }
2588                     Colorize(prevColor, TRUE);
2589                     curColor = prevColor;
2590                 }
2591                 if (savingComment) {
2592                     parse_pos = i - oldi;
2593                     memcpy(parse, &buf[oldi], parse_pos);
2594                     parse[parse_pos] = NULLCHAR;
2595                     started = STARTED_COMMENT;
2596                 } else {
2597                     started = STARTED_CHATTER;
2598                 }
2599                 continue;
2600             }
2601
2602             if (looking_at(buf, &i, "Black Strength :") ||
2603                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2604                 looking_at(buf, &i, "<10>") ||
2605                 looking_at(buf, &i, "#@#")) {
2606                 /* Wrong board style */
2607                 loggedOn = TRUE;
2608                 SendToICS(ics_prefix);
2609                 SendToICS("set style 12\n");
2610                 SendToICS(ics_prefix);
2611                 SendToICS("refresh\n");
2612                 continue;
2613             }
2614
2615             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2616                 ICSInitScript();
2617                 have_sent_ICS_logon = 1;
2618                 continue;
2619             }
2620
2621             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2622                 (looking_at(buf, &i, "\n<12> ") ||
2623                  looking_at(buf, &i, "<12> "))) {
2624                 loggedOn = TRUE;
2625                 if (oldi > next_out) {
2626                     SendToPlayer(&buf[next_out], oldi - next_out);
2627                 }
2628                 next_out = i;
2629                 started = STARTED_BOARD;
2630                 parse_pos = 0;
2631                 continue;
2632             }
2633
2634             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2635                 looking_at(buf, &i, "<b1> ")) {
2636                 if (oldi > next_out) {
2637                     SendToPlayer(&buf[next_out], oldi - next_out);
2638                 }
2639                 next_out = i;
2640                 started = STARTED_HOLDINGS;
2641                 parse_pos = 0;
2642                 continue;
2643             }
2644
2645             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2646                 loggedOn = TRUE;
2647                 /* Header for a move list -- first line */
2648
2649                 switch (ics_getting_history) {
2650                   case H_FALSE:
2651                     switch (gameMode) {
2652                       case IcsIdle:
2653                       case BeginningOfGame:
2654                         /* User typed "moves" or "oldmoves" while we
2655                            were idle.  Pretend we asked for these
2656                            moves and soak them up so user can step
2657                            through them and/or save them.
2658                            */
2659                         Reset(FALSE, TRUE);
2660                         gameMode = IcsObserving;
2661                         ModeHighlight();
2662                         ics_gamenum = -1;
2663                         ics_getting_history = H_GOT_UNREQ_HEADER;
2664                         break;
2665                       case EditGame: /*?*/
2666                       case EditPosition: /*?*/
2667                         /* Should above feature work in these modes too? */
2668                         /* For now it doesn't */
2669                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2670                         break;
2671                       default:
2672                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2673                         break;
2674                     }
2675                     break;
2676                   case H_REQUESTED:
2677                     /* Is this the right one? */
2678                     if (gameInfo.white && gameInfo.black &&
2679                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2680                         strcmp(gameInfo.black, star_match[2]) == 0) {
2681                         /* All is well */
2682                         ics_getting_history = H_GOT_REQ_HEADER;
2683                     }
2684                     break;
2685                   case H_GOT_REQ_HEADER:
2686                   case H_GOT_UNREQ_HEADER:
2687                   case H_GOT_UNWANTED_HEADER:
2688                   case H_GETTING_MOVES:
2689                     /* Should not happen */
2690                     DisplayError(_("Error gathering move list: two headers"), 0);
2691                     ics_getting_history = H_FALSE;
2692                     break;
2693                 }
2694
2695                 /* Save player ratings into gameInfo if needed */
2696                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2697                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2698                     (gameInfo.whiteRating == -1 ||
2699                      gameInfo.blackRating == -1)) {
2700
2701                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2702                     gameInfo.blackRating = string_to_rating(star_match[3]);
2703                     if (appData.debugMode)
2704                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2705                               gameInfo.whiteRating, gameInfo.blackRating);
2706                 }
2707                 continue;
2708             }
2709
2710             if (looking_at(buf, &i,
2711               "* * match, initial time: * minute*, increment: * second")) {
2712                 /* Header for a move list -- second line */
2713                 /* Initial board will follow if this is a wild game */
2714                 if (gameInfo.event != NULL) free(gameInfo.event);
2715                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2716                 gameInfo.event = StrSave(str);
2717                 /* [HGM] we switched variant. Translate boards if needed. */
2718                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2719                 continue;
2720             }
2721
2722             if (looking_at(buf, &i, "Move  ")) {
2723                 /* Beginning of a move list */
2724                 switch (ics_getting_history) {
2725                   case H_FALSE:
2726                     /* Normally should not happen */
2727                     /* Maybe user hit reset while we were parsing */
2728                     break;
2729                   case H_REQUESTED:
2730                     /* Happens if we are ignoring a move list that is not
2731                      * the one we just requested.  Common if the user
2732                      * tries to observe two games without turning off
2733                      * getMoveList */
2734                     break;
2735                   case H_GETTING_MOVES:
2736                     /* Should not happen */
2737                     DisplayError(_("Error gathering move list: nested"), 0);
2738                     ics_getting_history = H_FALSE;
2739                     break;
2740                   case H_GOT_REQ_HEADER:
2741                     ics_getting_history = H_GETTING_MOVES;
2742                     started = STARTED_MOVES;
2743                     parse_pos = 0;
2744                     if (oldi > next_out) {
2745                         SendToPlayer(&buf[next_out], oldi - next_out);
2746                     }
2747                     break;
2748                   case H_GOT_UNREQ_HEADER:
2749                     ics_getting_history = H_GETTING_MOVES;
2750                     started = STARTED_MOVES_NOHIDE;
2751                     parse_pos = 0;
2752                     break;
2753                   case H_GOT_UNWANTED_HEADER:
2754                     ics_getting_history = H_FALSE;
2755                     break;
2756                 }
2757                 continue;
2758             }
2759
2760             if (looking_at(buf, &i, "% ") ||
2761                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2762                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2763                 savingComment = FALSE;
2764                 switch (started) {
2765                   case STARTED_MOVES:
2766                   case STARTED_MOVES_NOHIDE:
2767                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2768                     parse[parse_pos + i - oldi] = NULLCHAR;
2769                     ParseGameHistory(parse);
2770 #if ZIPPY
2771                     if (appData.zippyPlay && first.initDone) {
2772                         FeedMovesToProgram(&first, forwardMostMove);
2773                         if (gameMode == IcsPlayingWhite) {
2774                             if (WhiteOnMove(forwardMostMove)) {
2775                                 if (first.sendTime) {
2776                                   if (first.useColors) {
2777                                     SendToProgram("black\n", &first);
2778                                   }
2779                                   SendTimeRemaining(&first, TRUE);
2780                                 }
2781                                 if (first.useColors) {
2782                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783                                 }
2784                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
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 (first.useColors) {
2806                                   SendToProgram("black\n", &first);
2807                                 }
2808                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2809                                 first.maybeThinking = TRUE;
2810                             } else {
2811                                 if (first.usePlayother) {
2812                                   if (first.sendTime) {
2813                                     SendTimeRemaining(&first, FALSE);
2814                                   }
2815                                   SendToProgram("playother\n", &first);
2816                                   firstMove = FALSE;
2817                                 } else {
2818                                   firstMove = TRUE;
2819                                 }
2820                             }
2821                         }
2822                     }
2823 #endif
2824                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2825                         /* Moves came from oldmoves or moves command
2826                            while we weren't doing anything else.
2827                            */
2828                         currentMove = forwardMostMove;
2829                         ClearHighlights();/*!!could figure this out*/
2830                         flipView = appData.flipView;
2831                         DrawPosition(FALSE, boards[currentMove]);
2832                         DisplayBothClocks();
2833                         sprintf(str, "%s vs. %s",
2834                                 gameInfo.white, gameInfo.black);
2835                         DisplayTitle(str);
2836                         gameMode = IcsIdle;
2837                     } else {
2838                         /* Moves were history of an active game */
2839                         if (gameInfo.resultDetails != NULL) {
2840                             free(gameInfo.resultDetails);
2841                             gameInfo.resultDetails = NULL;
2842                         }
2843                     }
2844                     HistorySet(parseList, backwardMostMove,
2845                                forwardMostMove, currentMove-1);
2846                     DisplayMove(currentMove - 1);
2847                     if (started == STARTED_MOVES) next_out = i;
2848                     started = STARTED_NONE;
2849                     ics_getting_history = H_FALSE;
2850                     break;
2851
2852                   case STARTED_OBSERVE:
2853                     started = STARTED_NONE;
2854                     SendToICS(ics_prefix);
2855                     SendToICS("refresh\n");
2856                     break;
2857
2858                   default:
2859                     break;
2860                 }
2861                 if(bookHit) { // [HGM] book: simulate book reply
2862                     static char bookMove[MSG_SIZ]; // a bit generous?
2863
2864                     programStats.nodes = programStats.depth = programStats.time =
2865                     programStats.score = programStats.got_only_move = 0;
2866                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2867
2868                     strcpy(bookMove, "move ");
2869                     strcat(bookMove, bookHit);
2870                     HandleMachineMove(bookMove, &first);
2871                 }
2872                 continue;
2873             }
2874
2875             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2876                  started == STARTED_HOLDINGS ||
2877                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2878                 /* Accumulate characters in move list or board */
2879                 parse[parse_pos++] = buf[i];
2880             }
2881
2882             /* Start of game messages.  Mostly we detect start of game
2883                when the first board image arrives.  On some versions
2884                of the ICS, though, we need to do a "refresh" after starting
2885                to observe in order to get the current board right away. */
2886             if (looking_at(buf, &i, "Adding game * to observation list")) {
2887                 started = STARTED_OBSERVE;
2888                 continue;
2889             }
2890
2891             /* Handle auto-observe */
2892             if (appData.autoObserve &&
2893                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2894                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2895                 char *player;
2896                 /* Choose the player that was highlighted, if any. */
2897                 if (star_match[0][0] == '\033' ||
2898                     star_match[1][0] != '\033') {
2899                     player = star_match[0];
2900                 } else {
2901                     player = star_match[2];
2902                 }
2903                 sprintf(str, "%sobserve %s\n",
2904                         ics_prefix, StripHighlightAndTitle(player));
2905                 SendToICS(str);
2906
2907                 /* Save ratings from notify string */
2908                 strcpy(player1Name, star_match[0]);
2909                 player1Rating = string_to_rating(star_match[1]);
2910                 strcpy(player2Name, star_match[2]);
2911                 player2Rating = string_to_rating(star_match[3]);
2912
2913                 if (appData.debugMode)
2914                   fprintf(debugFP,
2915                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2916                           player1Name, player1Rating,
2917                           player2Name, player2Rating);
2918
2919                 continue;
2920             }
2921
2922             /* Deal with automatic examine mode after a game,
2923                and with IcsObserving -> IcsExamining transition */
2924             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2925                 looking_at(buf, &i, "has made you an examiner of game *")) {
2926
2927                 int gamenum = atoi(star_match[0]);
2928                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2929                     gamenum == ics_gamenum) {
2930                     /* We were already playing or observing this game;
2931                        no need to refetch history */
2932                     gameMode = IcsExamining;
2933                     if (pausing) {
2934                         pauseExamForwardMostMove = forwardMostMove;
2935                     } else if (currentMove < forwardMostMove) {
2936                         ForwardInner(forwardMostMove);
2937                     }
2938                 } else {
2939                     /* I don't think this case really can happen */
2940                     SendToICS(ics_prefix);
2941                     SendToICS("refresh\n");
2942                 }
2943                 continue;
2944             }
2945
2946             /* Error messages */
2947 //          if (ics_user_moved) {
2948             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2949                 if (looking_at(buf, &i, "Illegal move") ||
2950                     looking_at(buf, &i, "Not a legal move") ||
2951                     looking_at(buf, &i, "Your king is in check") ||
2952                     looking_at(buf, &i, "It isn't your turn") ||
2953                     looking_at(buf, &i, "It is not your move")) {
2954                     /* Illegal move */
2955                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2956                         currentMove = --forwardMostMove;
2957                         DisplayMove(currentMove - 1); /* before DMError */
2958                         DrawPosition(FALSE, boards[currentMove]);
2959                         SwitchClocks();
2960                         DisplayBothClocks();
2961                     }
2962                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2963                     ics_user_moved = 0;
2964                     continue;
2965                 }
2966             }
2967
2968             if (looking_at(buf, &i, "still have time") ||
2969                 looking_at(buf, &i, "not out of time") ||
2970                 looking_at(buf, &i, "either player is out of time") ||
2971                 looking_at(buf, &i, "has timeseal; checking")) {
2972                 /* We must have called his flag a little too soon */
2973                 whiteFlag = blackFlag = FALSE;
2974                 continue;
2975             }
2976
2977             if (looking_at(buf, &i, "added * seconds to") ||
2978                 looking_at(buf, &i, "seconds were added to")) {
2979                 /* Update the clocks */
2980                 SendToICS(ics_prefix);
2981                 SendToICS("refresh\n");
2982                 continue;
2983             }
2984
2985             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2986                 ics_clock_paused = TRUE;
2987                 StopClocks();
2988                 continue;
2989             }
2990
2991             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2992                 ics_clock_paused = FALSE;
2993                 StartClocks();
2994                 continue;
2995             }
2996
2997             /* Grab player ratings from the Creating: message.
2998                Note we have to check for the special case when
2999                the ICS inserts things like [white] or [black]. */
3000             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3001                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3002                 /* star_matches:
3003                    0    player 1 name (not necessarily white)
3004                    1    player 1 rating
3005                    2    empty, white, or black (IGNORED)
3006                    3    player 2 name (not necessarily black)
3007                    4    player 2 rating
3008
3009                    The names/ratings are sorted out when the game
3010                    actually starts (below).
3011                 */
3012                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3013                 player1Rating = string_to_rating(star_match[1]);
3014                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3015                 player2Rating = string_to_rating(star_match[4]);
3016
3017                 if (appData.debugMode)
3018                   fprintf(debugFP,
3019                           "Ratings from 'Creating:' %s %d, %s %d\n",
3020                           player1Name, player1Rating,
3021                           player2Name, player2Rating);
3022
3023                 continue;
3024             }
3025
3026             /* Improved generic start/end-of-game messages */
3027             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3028                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3029                 /* If tkind == 0: */
3030                 /* star_match[0] is the game number */
3031                 /*           [1] is the white player's name */
3032                 /*           [2] is the black player's name */
3033                 /* For end-of-game: */
3034                 /*           [3] is the reason for the game end */
3035                 /*           [4] is a PGN end game-token, preceded by " " */
3036                 /* For start-of-game: */
3037                 /*           [3] begins with "Creating" or "Continuing" */
3038                 /*           [4] is " *" or empty (don't care). */
3039                 int gamenum = atoi(star_match[0]);
3040                 char *whitename, *blackname, *why, *endtoken;
3041                 ChessMove endtype = (ChessMove) 0;
3042
3043                 if (tkind == 0) {
3044                   whitename = star_match[1];
3045                   blackname = star_match[2];
3046                   why = star_match[3];
3047                   endtoken = star_match[4];
3048                 } else {
3049                   whitename = star_match[1];
3050                   blackname = star_match[3];
3051                   why = star_match[5];
3052                   endtoken = star_match[6];
3053                 }
3054
3055                 /* Game start messages */
3056                 if (strncmp(why, "Creating ", 9) == 0 ||
3057                     strncmp(why, "Continuing ", 11) == 0) {
3058                     gs_gamenum = gamenum;
3059                     strcpy(gs_kind, strchr(why, ' ') + 1);
3060 #if ZIPPY
3061                     if (appData.zippyPlay) {
3062                         ZippyGameStart(whitename, blackname);
3063                     }
3064 #endif /*ZIPPY*/
3065                     continue;
3066                 }
3067
3068                 /* Game end messages */
3069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3070                     ics_gamenum != gamenum) {
3071                     continue;
3072                 }
3073                 while (endtoken[0] == ' ') endtoken++;
3074                 switch (endtoken[0]) {
3075                   case '*':
3076                   default:
3077                     endtype = GameUnfinished;
3078                     break;
3079                   case '0':
3080                     endtype = BlackWins;
3081                     break;
3082                   case '1':
3083                     if (endtoken[1] == '/')
3084                       endtype = GameIsDrawn;
3085                     else
3086                       endtype = WhiteWins;
3087                     break;
3088                 }
3089                 GameEnds(endtype, why, GE_ICS);
3090 #if ZIPPY
3091                 if (appData.zippyPlay && first.initDone) {
3092                     ZippyGameEnd(endtype, why);
3093                     if (first.pr == NULL) {
3094                       /* Start the next process early so that we'll
3095                          be ready for the next challenge */
3096                       StartChessProgram(&first);
3097                     }
3098                     /* Send "new" early, in case this command takes
3099                        a long time to finish, so that we'll be ready
3100                        for the next challenge. */
3101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3102                     Reset(TRUE, TRUE);
3103                 }
3104 #endif /*ZIPPY*/
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Removing game * from observation") ||
3109                 looking_at(buf, &i, "no longer observing game *") ||
3110                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3111                 if (gameMode == IcsObserving &&
3112                     atoi(star_match[0]) == ics_gamenum)
3113                   {
3114                       /* icsEngineAnalyze */
3115                       if (appData.icsEngineAnalyze) {
3116                             ExitAnalyzeMode();
3117                             ModeHighlight();
3118                       }
3119                       StopClocks();
3120                       gameMode = IcsIdle;
3121                       ics_gamenum = -1;
3122                       ics_user_moved = FALSE;
3123                   }
3124                 continue;
3125             }
3126
3127             if (looking_at(buf, &i, "no longer examining game *")) {
3128                 if (gameMode == IcsExamining &&
3129                     atoi(star_match[0]) == ics_gamenum)
3130                   {
3131                       gameMode = IcsIdle;
3132                       ics_gamenum = -1;
3133                       ics_user_moved = FALSE;
3134                   }
3135                 continue;
3136             }
3137
3138             /* Advance leftover_start past any newlines we find,
3139                so only partial lines can get reparsed */
3140             if (looking_at(buf, &i, "\n")) {
3141                 prevColor = curColor;
3142                 if (curColor != ColorNormal) {
3143                     if (oldi > next_out) {
3144                         SendToPlayer(&buf[next_out], oldi - next_out);
3145                         next_out = oldi;
3146                     }
3147                     Colorize(ColorNormal, FALSE);
3148                     curColor = ColorNormal;
3149                 }
3150                 if (started == STARTED_BOARD) {
3151                     started = STARTED_NONE;
3152                     parse[parse_pos] = NULLCHAR;
3153                     ParseBoard12(parse);
3154                     ics_user_moved = 0;
3155
3156                     /* Send premove here */
3157                     if (appData.premove) {
3158                       char str[MSG_SIZ];
3159                       if (currentMove == 0 &&
3160                           gameMode == IcsPlayingWhite &&
3161                           appData.premoveWhite) {
3162                         sprintf(str, "%s%s\n", ics_prefix,
3163                                 appData.premoveWhiteText);
3164                         if (appData.debugMode)
3165                           fprintf(debugFP, "Sending premove:\n");
3166                         SendToICS(str);
3167                       } else if (currentMove == 1 &&
3168                                  gameMode == IcsPlayingBlack &&
3169                                  appData.premoveBlack) {
3170                         sprintf(str, "%s%s\n", ics_prefix,
3171                                 appData.premoveBlackText);
3172                         if (appData.debugMode)
3173                           fprintf(debugFP, "Sending premove:\n");
3174                         SendToICS(str);
3175                       } else if (gotPremove) {
3176                         gotPremove = 0;
3177                         ClearPremoveHighlights();
3178                         if (appData.debugMode)
3179                           fprintf(debugFP, "Sending premove:\n");
3180                           UserMoveEvent(premoveFromX, premoveFromY,
3181                                         premoveToX, premoveToY,
3182                                         premovePromoChar);
3183                       }
3184                     }
3185
3186                     /* Usually suppress following prompt */
3187                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3188                         if (looking_at(buf, &i, "*% ")) {
3189                             savingComment = FALSE;
3190                         }
3191                     }
3192                     next_out = i;
3193                 } else if (started == STARTED_HOLDINGS) {
3194                     int gamenum;
3195                     char new_piece[MSG_SIZ];
3196                     started = STARTED_NONE;
3197                     parse[parse_pos] = NULLCHAR;
3198                     if (appData.debugMode)
3199                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3200                                                         parse, currentMove);
3201                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3202                         gamenum == ics_gamenum) {
3203                         if (gameInfo.variant == VariantNormal) {
3204                           /* [HGM] We seem to switch variant during a game!
3205                            * Presumably no holdings were displayed, so we have
3206                            * to move the position two files to the right to
3207                            * create room for them!
3208                            */
3209                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3210                           /* Get a move list just to see the header, which
3211                              will tell us whether this is really bug or zh */
3212                           if (ics_getting_history == H_FALSE) {
3213                             ics_getting_history = H_REQUESTED;
3214                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3215                             SendToICS(str);
3216                           }
3217                         }
3218                         new_piece[0] = NULLCHAR;
3219                         sscanf(parse, "game %d white [%s black [%s <- %s",
3220                                &gamenum, white_holding, black_holding,
3221                                new_piece);
3222                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3223                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3224                         /* [HGM] copy holdings to board holdings area */
3225                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3226                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3227 #if ZIPPY
3228                         if (appData.zippyPlay && first.initDone) {
3229                             ZippyHoldings(white_holding, black_holding,
3230                                           new_piece);
3231                         }
3232 #endif /*ZIPPY*/
3233                         if (tinyLayout || smallLayout) {
3234                             char wh[16], bh[16];
3235                             PackHolding(wh, white_holding);
3236                             PackHolding(bh, black_holding);
3237                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3238                                     gameInfo.white, gameInfo.black);
3239                         } else {
3240                             sprintf(str, "%s [%s] vs. %s [%s]",
3241                                     gameInfo.white, white_holding,
3242                                     gameInfo.black, black_holding);
3243                         }
3244
3245                         DrawPosition(FALSE, boards[currentMove]);
3246                         DisplayTitle(str);
3247                     }
3248                     /* Suppress following prompt */
3249                     if (looking_at(buf, &i, "*% ")) {
3250                         savingComment = FALSE;
3251                     }
3252                     next_out = i;
3253                 }
3254                 continue;
3255             }
3256
3257             i++;                /* skip unparsed character and loop back */
3258         }
3259
3260         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3261             started != STARTED_HOLDINGS && i > next_out) {
3262             SendToPlayer(&buf[next_out], i - next_out);
3263             next_out = i;
3264         }
3265         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3266
3267         leftover_len = buf_len - leftover_start;
3268         /* if buffer ends with something we couldn't parse,
3269            reparse it after appending the next read */
3270
3271     } else if (count == 0) {
3272         RemoveInputSource(isr);
3273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3274     } else {
3275         DisplayFatalError(_("Error reading from ICS"), error, 1);
3276     }
3277 }
3278
3279
3280 /* Board style 12 looks like this:
3281
3282    <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
3283
3284  * The "<12> " is stripped before it gets to this routine.  The two
3285  * trailing 0's (flip state and clock ticking) are later addition, and
3286  * some chess servers may not have them, or may have only the first.
3287  * Additional trailing fields may be added in the future.
3288  */
3289
3290 #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"
3291
3292 #define RELATION_OBSERVING_PLAYED    0
3293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3294 #define RELATION_PLAYING_MYMOVE      1
3295 #define RELATION_PLAYING_NOTMYMOVE  -1
3296 #define RELATION_EXAMINING           2
3297 #define RELATION_ISOLATED_BOARD     -3
3298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3299
3300 void
3301 ParseBoard12(string)
3302      char *string;
3303 {
3304     GameMode newGameMode;
3305     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3306     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3307     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3308     char to_play, board_chars[200];
3309     char move_str[500], str[500], elapsed_time[500];
3310     char black[32], white[32];
3311     Board board;
3312     int prevMove = currentMove;
3313     int ticking = 2;
3314     ChessMove moveType;
3315     int fromX, fromY, toX, toY;
3316     char promoChar;
3317     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3318     char *bookHit = NULL; // [HGM] book
3319
3320     fromX = fromY = toX = toY = -1;
3321
3322     newGame = FALSE;
3323
3324     if (appData.debugMode)
3325       fprintf(debugFP, _("Parsing board: %s\n"), string);
3326
3327     move_str[0] = NULLCHAR;
3328     elapsed_time[0] = NULLCHAR;
3329     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3330         int  i = 0, j;
3331         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3332             if(string[i] == ' ') { ranks++; files = 0; }
3333             else files++;
3334             i++;
3335         }
3336         for(j = 0; j <i; j++) board_chars[j] = string[j];
3337         board_chars[i] = '\0';
3338         string += i + 1;
3339     }
3340     n = sscanf(string, PATTERN, &to_play, &double_push,
3341                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3342                &gamenum, white, black, &relation, &basetime, &increment,
3343                &white_stren, &black_stren, &white_time, &black_time,
3344                &moveNum, str, elapsed_time, move_str, &ics_flip,
3345                &ticking);
3346
3347     if (n < 21) {
3348         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3349         DisplayError(str, 0);
3350         return;
3351     }
3352
3353     /* Convert the move number to internal form */
3354     moveNum = (moveNum - 1) * 2;
3355     if (to_play == 'B') moveNum++;
3356     if (moveNum >= MAX_MOVES) {
3357       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3358                         0, 1);
3359       return;
3360     }
3361
3362     switch (relation) {
3363       case RELATION_OBSERVING_PLAYED:
3364       case RELATION_OBSERVING_STATIC:
3365         if (gamenum == -1) {
3366             /* Old ICC buglet */
3367             relation = RELATION_OBSERVING_STATIC;
3368         }
3369         newGameMode = IcsObserving;
3370         break;
3371       case RELATION_PLAYING_MYMOVE:
3372       case RELATION_PLAYING_NOTMYMOVE:
3373         newGameMode =
3374           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3375             IcsPlayingWhite : IcsPlayingBlack;
3376         break;
3377       case RELATION_EXAMINING:
3378         newGameMode = IcsExamining;
3379         break;
3380       case RELATION_ISOLATED_BOARD:
3381       default:
3382         /* Just display this board.  If user was doing something else,
3383            we will forget about it until the next board comes. */
3384         newGameMode = IcsIdle;
3385         break;
3386       case RELATION_STARTING_POSITION:
3387         newGameMode = gameMode;
3388         break;
3389     }
3390
3391     /* Modify behavior for initial board display on move listing
3392        of wild games.
3393        */
3394     switch (ics_getting_history) {
3395       case H_FALSE:
3396       case H_REQUESTED:
3397         break;
3398       case H_GOT_REQ_HEADER:
3399       case H_GOT_UNREQ_HEADER:
3400         /* This is the initial position of the current game */
3401         gamenum = ics_gamenum;
3402         moveNum = 0;            /* old ICS bug workaround */
3403         if (to_play == 'B') {
3404           startedFromSetupPosition = TRUE;
3405           blackPlaysFirst = TRUE;
3406           moveNum = 1;
3407           if (forwardMostMove == 0) forwardMostMove = 1;
3408           if (backwardMostMove == 0) backwardMostMove = 1;
3409           if (currentMove == 0) currentMove = 1;
3410         }
3411         newGameMode = gameMode;
3412         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3413         break;
3414       case H_GOT_UNWANTED_HEADER:
3415         /* This is an initial board that we don't want */
3416         return;
3417       case H_GETTING_MOVES:
3418         /* Should not happen */
3419         DisplayError(_("Error gathering move list: extra board"), 0);
3420         ics_getting_history = H_FALSE;
3421         return;
3422     }
3423
3424     /* Take action if this is the first board of a new game, or of a
3425        different game than is currently being displayed.  */
3426     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3427         relation == RELATION_ISOLATED_BOARD) {
3428
3429         /* Forget the old game and get the history (if any) of the new one */
3430         if (gameMode != BeginningOfGame) {
3431           Reset(FALSE, TRUE);
3432         }
3433         newGame = TRUE;
3434         if (appData.autoRaiseBoard) BoardToTop();
3435         prevMove = -3;
3436         if (gamenum == -1) {
3437             newGameMode = IcsIdle;
3438         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3439                    appData.getMoveList) {
3440             /* Need to get game history */
3441             ics_getting_history = H_REQUESTED;
3442             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3443             SendToICS(str);
3444         }
3445
3446         /* Initially flip the board to have black on the bottom if playing
3447            black or if the ICS flip flag is set, but let the user change
3448            it with the Flip View button. */
3449         flipView = appData.autoFlipView ?
3450           (newGameMode == IcsPlayingBlack) || ics_flip :
3451           appData.flipView;
3452
3453         /* Done with values from previous mode; copy in new ones */
3454         gameMode = newGameMode;
3455         ModeHighlight();
3456         ics_gamenum = gamenum;
3457         if (gamenum == gs_gamenum) {
3458             int klen = strlen(gs_kind);
3459             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3460             sprintf(str, "ICS %s", gs_kind);
3461             gameInfo.event = StrSave(str);
3462         } else {
3463             gameInfo.event = StrSave("ICS game");
3464         }
3465         gameInfo.site = StrSave(appData.icsHost);
3466         gameInfo.date = PGNDate();
3467         gameInfo.round = StrSave("-");
3468         gameInfo.white = StrSave(white);
3469         gameInfo.black = StrSave(black);
3470         timeControl = basetime * 60 * 1000;
3471         timeControl_2 = 0;
3472         timeIncrement = increment * 1000;
3473         movesPerSession = 0;
3474         gameInfo.timeControl = TimeControlTagValue();
3475         VariantSwitch(board, StringToVariant(gameInfo.event) );
3476   if (appData.debugMode) {
3477     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3478     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3479     setbuf(debugFP, NULL);
3480   }
3481
3482         gameInfo.outOfBook = NULL;
3483
3484         /* Do we have the ratings? */
3485         if (strcmp(player1Name, white) == 0 &&
3486             strcmp(player2Name, black) == 0) {
3487             if (appData.debugMode)
3488               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3489                       player1Rating, player2Rating);
3490             gameInfo.whiteRating = player1Rating;
3491             gameInfo.blackRating = player2Rating;
3492         } else if (strcmp(player2Name, white) == 0 &&
3493                    strcmp(player1Name, black) == 0) {
3494             if (appData.debugMode)
3495               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3496                       player2Rating, player1Rating);
3497             gameInfo.whiteRating = player2Rating;
3498             gameInfo.blackRating = player1Rating;
3499         }
3500         player1Name[0] = player2Name[0] = NULLCHAR;
3501
3502         /* Silence shouts if requested */
3503         if (appData.quietPlay &&
3504             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3505             SendToICS(ics_prefix);
3506             SendToICS("set shout 0\n");
3507         }
3508     }
3509
3510     /* Deal with midgame name changes */
3511     if (!newGame) {
3512         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3513             if (gameInfo.white) free(gameInfo.white);
3514             gameInfo.white = StrSave(white);
3515         }
3516         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3517             if (gameInfo.black) free(gameInfo.black);
3518             gameInfo.black = StrSave(black);
3519         }
3520     }
3521
3522     /* Throw away game result if anything actually changes in examine mode */
3523     if (gameMode == IcsExamining && !newGame) {
3524         gameInfo.result = GameUnfinished;
3525         if (gameInfo.resultDetails != NULL) {
3526             free(gameInfo.resultDetails);
3527             gameInfo.resultDetails = NULL;
3528         }
3529     }
3530
3531     /* In pausing && IcsExamining mode, we ignore boards coming
3532        in if they are in a different variation than we are. */
3533     if (pauseExamInvalid) return;
3534     if (pausing && gameMode == IcsExamining) {
3535         if (moveNum <= pauseExamForwardMostMove) {
3536             pauseExamInvalid = TRUE;
3537             forwardMostMove = pauseExamForwardMostMove;
3538             return;
3539         }
3540     }
3541
3542   if (appData.debugMode) {
3543     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3544   }
3545     /* Parse the board */
3546     for (k = 0; k < ranks; k++) {
3547       for (j = 0; j < files; j++)
3548         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3549       if(gameInfo.holdingsWidth > 1) {
3550            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3551            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3552       }
3553     }
3554     CopyBoard(boards[moveNum], board);
3555     if (moveNum == 0) {
3556         startedFromSetupPosition =
3557           !CompareBoards(board, initialPosition);
3558         if(startedFromSetupPosition)
3559             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3560     }
3561
3562     /* [HGM] Set castling rights. Take the outermost Rooks,
3563        to make it also work for FRC opening positions. Note that board12
3564        is really defective for later FRC positions, as it has no way to
3565        indicate which Rook can castle if they are on the same side of King.
3566        For the initial position we grant rights to the outermost Rooks,
3567        and remember thos rights, and we then copy them on positions
3568        later in an FRC game. This means WB might not recognize castlings with
3569        Rooks that have moved back to their original position as illegal,
3570        but in ICS mode that is not its job anyway.
3571     */
3572     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3573     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3574
3575         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3576             if(board[0][i] == WhiteRook) j = i;
3577         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3578         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3579             if(board[0][i] == WhiteRook) j = i;
3580         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3581         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3582             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3583         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3584         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3585             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3586         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587
3588         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3589         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3590             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3591         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3592             if(board[BOARD_HEIGHT-1][k] == bKing)
3593                 initialRights[5] = castlingRights[moveNum][5] = k;
3594     } else { int r;
3595         r = castlingRights[moveNum][0] = initialRights[0];
3596         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3597         r = castlingRights[moveNum][1] = initialRights[1];
3598         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3599         r = castlingRights[moveNum][3] = initialRights[3];
3600         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3601         r = castlingRights[moveNum][4] = initialRights[4];
3602         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3603         /* wildcastle kludge: always assume King has rights */
3604         r = castlingRights[moveNum][2] = initialRights[2];
3605         r = castlingRights[moveNum][5] = initialRights[5];
3606     }
3607     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3608     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3609
3610
3611     if (ics_getting_history == H_GOT_REQ_HEADER ||
3612         ics_getting_history == H_GOT_UNREQ_HEADER) {
3613         /* This was an initial position from a move list, not
3614            the current position */
3615         return;
3616     }
3617
3618     /* Update currentMove and known move number limits */
3619     newMove = newGame || moveNum > forwardMostMove;
3620
3621     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3622     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3623         takeback = forwardMostMove - moveNum;
3624         for (i = 0; i < takeback; i++) {
3625              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3626              SendToProgram("undo\n", &first);
3627         }
3628     }
3629
3630     if (newGame) {
3631         forwardMostMove = backwardMostMove = currentMove = moveNum;
3632         if (gameMode == IcsExamining && moveNum == 0) {
3633           /* Workaround for ICS limitation: we are not told the wild
3634              type when starting to examine a game.  But if we ask for
3635              the move list, the move list header will tell us */
3636             ics_getting_history = H_REQUESTED;
3637             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3638             SendToICS(str);
3639         }
3640     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3641                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3642         forwardMostMove = moveNum;
3643         if (!pausing || currentMove > forwardMostMove)
3644           currentMove = forwardMostMove;
3645     } else {
3646         /* New part of history that is not contiguous with old part */
3647         if (pausing && gameMode == IcsExamining) {
3648             pauseExamInvalid = TRUE;
3649             forwardMostMove = pauseExamForwardMostMove;
3650             return;
3651         }
3652         forwardMostMove = backwardMostMove = currentMove = moveNum;
3653         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3654             ics_getting_history = H_REQUESTED;
3655             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656             SendToICS(str);
3657         }
3658     }
3659
3660     /* Update the clocks */
3661     if (strchr(elapsed_time, '.')) {
3662       /* Time is in ms */
3663       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3664       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3665     } else {
3666       /* Time is in seconds */
3667       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3668       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3669     }
3670
3671
3672 #if ZIPPY
3673     if (appData.zippyPlay && newGame &&
3674         gameMode != IcsObserving && gameMode != IcsIdle &&
3675         gameMode != IcsExamining)
3676       ZippyFirstBoard(moveNum, basetime, increment);
3677 #endif
3678
3679     /* Put the move on the move list, first converting
3680        to canonical algebraic form. */
3681     if (moveNum > 0) {
3682   if (appData.debugMode) {
3683     if (appData.debugMode) { int f = forwardMostMove;
3684         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3685                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3686     }
3687     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3688     fprintf(debugFP, "moveNum = %d\n", moveNum);
3689     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3690     setbuf(debugFP, NULL);
3691   }
3692         if (moveNum <= backwardMostMove) {
3693             /* We don't know what the board looked like before
3694                this move.  Punt. */
3695             strcpy(parseList[moveNum - 1], move_str);
3696             strcat(parseList[moveNum - 1], " ");
3697             strcat(parseList[moveNum - 1], elapsed_time);
3698             moveList[moveNum - 1][0] = NULLCHAR;
3699         } else if (strcmp(move_str, "none") == 0) {
3700             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3701             /* Again, we don't know what the board looked like;
3702                this is really the start of the game. */
3703             parseList[moveNum - 1][0] = NULLCHAR;
3704             moveList[moveNum - 1][0] = NULLCHAR;
3705             backwardMostMove = moveNum;
3706             startedFromSetupPosition = TRUE;
3707             fromX = fromY = toX = toY = -1;
3708         } else {
3709           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3710           //                 So we parse the long-algebraic move string in stead of the SAN move
3711           int valid; char buf[MSG_SIZ], *prom;
3712
3713           // str looks something like "Q/a1-a2"; kill the slash
3714           if(str[1] == '/')
3715                 sprintf(buf, "%c%s", str[0], str+2);
3716           else  strcpy(buf, str); // might be castling
3717           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3718                 strcat(buf, prom); // long move lacks promo specification!
3719           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3720                 if(appData.debugMode)
3721                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3722                 strcpy(move_str, buf);
3723           }
3724           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3725                                 &fromX, &fromY, &toX, &toY, &promoChar)
3726                || ParseOneMove(buf, moveNum - 1, &moveType,
3727                                 &fromX, &fromY, &toX, &toY, &promoChar);
3728           // end of long SAN patch
3729           if (valid) {
3730             (void) CoordsToAlgebraic(boards[moveNum - 1],
3731                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3732                                      fromY, fromX, toY, toX, promoChar,
3733                                      parseList[moveNum-1]);
3734             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3735                              castlingRights[moveNum]) ) {
3736               case MT_NONE:
3737               case MT_STALEMATE:
3738               default:
3739                 break;
3740               case MT_CHECK:
3741                 if(gameInfo.variant != VariantShogi)
3742                     strcat(parseList[moveNum - 1], "+");
3743                 break;
3744               case MT_CHECKMATE:
3745               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3746                 strcat(parseList[moveNum - 1], "#");
3747                 break;
3748             }
3749             strcat(parseList[moveNum - 1], " ");
3750             strcat(parseList[moveNum - 1], elapsed_time);
3751             /* currentMoveString is set as a side-effect of ParseOneMove */
3752             strcpy(moveList[moveNum - 1], currentMoveString);
3753             strcat(moveList[moveNum - 1], "\n");
3754           } else {
3755             /* Move from ICS was illegal!?  Punt. */
3756   if (appData.debugMode) {
3757     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3758     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3759   }
3760             strcpy(parseList[moveNum - 1], move_str);
3761             strcat(parseList[moveNum - 1], " ");
3762             strcat(parseList[moveNum - 1], elapsed_time);
3763             moveList[moveNum - 1][0] = NULLCHAR;
3764             fromX = fromY = toX = toY = -1;
3765           }
3766         }
3767   if (appData.debugMode) {
3768     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3769     setbuf(debugFP, NULL);
3770   }
3771
3772 #if ZIPPY
3773         /* Send move to chess program (BEFORE animating it). */
3774         if (appData.zippyPlay && !newGame && newMove &&
3775            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3776
3777             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3778                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3779                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3780                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3781                             move_str);
3782                     DisplayError(str, 0);
3783                 } else {
3784                     if (first.sendTime) {
3785                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3786                     }
3787                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3788                     if (firstMove && !bookHit) {
3789                         firstMove = FALSE;
3790                         if (first.useColors) {
3791                           SendToProgram(gameMode == IcsPlayingWhite ?
3792                                         "white\ngo\n" :
3793                                         "black\ngo\n", &first);
3794                         } else {
3795                           SendToProgram("go\n", &first);
3796                         }
3797                         first.maybeThinking = TRUE;
3798                     }
3799                 }
3800             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3801               if (moveList[moveNum - 1][0] == NULLCHAR) {
3802                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3803                 DisplayError(str, 0);
3804               } else {
3805                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3806                 SendMoveToProgram(moveNum - 1, &first);
3807               }
3808             }
3809         }
3810 #endif
3811     }
3812
3813     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3814         /* If move comes from a remote source, animate it.  If it
3815            isn't remote, it will have already been animated. */
3816         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3817             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3818         }
3819         if (!pausing && appData.highlightLastMove) {
3820             SetHighlights(fromX, fromY, toX, toY);
3821         }
3822     }
3823
3824     /* Start the clocks */
3825     whiteFlag = blackFlag = FALSE;
3826     appData.clockMode = !(basetime == 0 && increment == 0);
3827     if (ticking == 0) {
3828       ics_clock_paused = TRUE;
3829       StopClocks();
3830     } else if (ticking == 1) {
3831       ics_clock_paused = FALSE;
3832     }
3833     if (gameMode == IcsIdle ||
3834         relation == RELATION_OBSERVING_STATIC ||
3835         relation == RELATION_EXAMINING ||
3836         ics_clock_paused)
3837       DisplayBothClocks();
3838     else
3839       StartClocks();
3840
3841     /* Display opponents and material strengths */
3842     if (gameInfo.variant != VariantBughouse &&
3843         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3844         if (tinyLayout || smallLayout) {
3845             if(gameInfo.variant == VariantNormal)
3846                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3847                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3848                     basetime, increment);
3849             else
3850                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3851                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3852                     basetime, increment, (int) gameInfo.variant);
3853         } else {
3854             if(gameInfo.variant == VariantNormal)
3855                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3856                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3857                     basetime, increment);
3858             else
3859                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3860                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3861                     basetime, increment, VariantName(gameInfo.variant));
3862         }
3863         DisplayTitle(str);
3864   if (appData.debugMode) {
3865     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3866   }
3867     }
3868
3869
3870     /* Display the board */
3871     if (!pausing && !appData.noGUI) {
3872       if (appData.premove)
3873           if (!gotPremove ||
3874              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3875              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3876               ClearPremoveHighlights();
3877
3878       DrawPosition(FALSE, boards[currentMove]);
3879       DisplayMove(moveNum - 1);
3880       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3881             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3882               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3883         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3884       }
3885     }
3886
3887     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3888 #if ZIPPY
3889     if(bookHit) { // [HGM] book: simulate book reply
3890         static char bookMove[MSG_SIZ]; // a bit generous?
3891
3892         programStats.nodes = programStats.depth = programStats.time =
3893         programStats.score = programStats.got_only_move = 0;
3894         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3895
3896         strcpy(bookMove, "move ");
3897         strcat(bookMove, bookHit);
3898         HandleMachineMove(bookMove, &first);
3899     }
3900 #endif
3901 }
3902
3903 void
3904 GetMoveListEvent()
3905 {
3906     char buf[MSG_SIZ];
3907     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3908         ics_getting_history = H_REQUESTED;
3909         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3910         SendToICS(buf);
3911     }
3912 }
3913
3914 void
3915 AnalysisPeriodicEvent(force)
3916      int force;
3917 {
3918     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3919          && !force) || !appData.periodicUpdates)
3920       return;
3921
3922     /* Send . command to Crafty to collect stats */
3923     SendToProgram(".\n", &first);
3924
3925     /* Don't send another until we get a response (this makes
3926        us stop sending to old Crafty's which don't understand
3927        the "." command (sending illegal cmds resets node count & time,
3928        which looks bad)) */
3929     programStats.ok_to_send = 0;
3930 }
3931
3932 void
3933 SendMoveToProgram(moveNum, cps)
3934      int moveNum;
3935      ChessProgramState *cps;
3936 {
3937     char buf[MSG_SIZ];
3938
3939     if (cps->useUsermove) {
3940       SendToProgram("usermove ", cps);
3941     }
3942     if (cps->useSAN) {
3943       char *space;
3944       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3945         int len = space - parseList[moveNum];
3946         memcpy(buf, parseList[moveNum], len);
3947         buf[len++] = '\n';
3948         buf[len] = NULLCHAR;
3949       } else {
3950         sprintf(buf, "%s\n", parseList[moveNum]);
3951       }
3952       SendToProgram(buf, cps);
3953     } else {
3954       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3955         AlphaRank(moveList[moveNum], 4);
3956         SendToProgram(moveList[moveNum], cps);
3957         AlphaRank(moveList[moveNum], 4); // and back
3958       } else
3959       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3960        * the engine. It would be nice to have a better way to identify castle
3961        * moves here. */
3962       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3963                                                                          && cps->useOOCastle) {
3964         int fromX = moveList[moveNum][0] - AAA;
3965         int fromY = moveList[moveNum][1] - ONE;
3966         int toX = moveList[moveNum][2] - AAA;
3967         int toY = moveList[moveNum][3] - ONE;
3968         if((boards[moveNum][fromY][fromX] == WhiteKing
3969             && boards[moveNum][toY][toX] == WhiteRook)
3970            || (boards[moveNum][fromY][fromX] == BlackKing
3971                && boards[moveNum][toY][toX] == BlackRook)) {
3972           if(toX > fromX) SendToProgram("O-O\n", cps);
3973           else SendToProgram("O-O-O\n", cps);
3974         }
3975         else SendToProgram(moveList[moveNum], cps);
3976       }
3977       else SendToProgram(moveList[moveNum], cps);
3978       /* End of additions by Tord */
3979     }
3980
3981     /* [HGM] setting up the opening has brought engine in force mode! */
3982     /*       Send 'go' if we are in a mode where machine should play. */
3983     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3984         (gameMode == TwoMachinesPlay   ||
3985 #ifdef ZIPPY
3986          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
3987 #endif
3988          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
3989         SendToProgram("go\n", cps);
3990   if (appData.debugMode) {
3991     fprintf(debugFP, "(extra)\n");
3992   }
3993     }
3994     setboardSpoiledMachineBlack = 0;
3995 }
3996
3997 void
3998 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3999      ChessMove moveType;
4000      int fromX, fromY, toX, toY;
4001 {
4002     char user_move[MSG_SIZ];
4003
4004     switch (moveType) {
4005       default:
4006         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4007                 (int)moveType, fromX, fromY, toX, toY);
4008         DisplayError(user_move + strlen("say "), 0);
4009         break;
4010       case WhiteKingSideCastle:
4011       case BlackKingSideCastle:
4012       case WhiteQueenSideCastleWild:
4013       case BlackQueenSideCastleWild:
4014       /* PUSH Fabien */
4015       case WhiteHSideCastleFR:
4016       case BlackHSideCastleFR:
4017       /* POP Fabien */
4018         sprintf(user_move, "o-o\n");
4019         break;
4020       case WhiteQueenSideCastle:
4021       case BlackQueenSideCastle:
4022       case WhiteKingSideCastleWild:
4023       case BlackKingSideCastleWild:
4024       /* PUSH Fabien */
4025       case WhiteASideCastleFR:
4026       case BlackASideCastleFR:
4027       /* POP Fabien */
4028         sprintf(user_move, "o-o-o\n");
4029         break;
4030       case WhitePromotionQueen:
4031       case BlackPromotionQueen:
4032       case WhitePromotionRook:
4033       case BlackPromotionRook:
4034       case WhitePromotionBishop:
4035       case BlackPromotionBishop:
4036       case WhitePromotionKnight:
4037       case BlackPromotionKnight:
4038       case WhitePromotionKing:
4039       case BlackPromotionKing:
4040       case WhitePromotionChancellor:
4041       case BlackPromotionChancellor:
4042       case WhitePromotionArchbishop:
4043       case BlackPromotionArchbishop:
4044         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4045             sprintf(user_move, "%c%c%c%c=%c\n",
4046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4047                 PieceToChar(WhiteFerz));
4048         else if(gameInfo.variant == VariantGreat)
4049             sprintf(user_move, "%c%c%c%c=%c\n",
4050                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4051                 PieceToChar(WhiteMan));
4052         else
4053             sprintf(user_move, "%c%c%c%c=%c\n",
4054                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4055                 PieceToChar(PromoPiece(moveType)));
4056         break;
4057       case WhiteDrop:
4058       case BlackDrop:
4059         sprintf(user_move, "%c@%c%c\n",
4060                 ToUpper(PieceToChar((ChessSquare) fromX)),
4061                 AAA + toX, ONE + toY);
4062         break;
4063       case NormalMove:
4064       case WhiteCapturesEnPassant:
4065       case BlackCapturesEnPassant:
4066       case IllegalMove:  /* could be a variant we don't quite understand */
4067         sprintf(user_move, "%c%c%c%c\n",
4068                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4069         break;
4070     }
4071     SendToICS(user_move);
4072     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4073         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4074 }
4075
4076 void
4077 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4078      int rf, ff, rt, ft;
4079      char promoChar;
4080      char move[7];
4081 {
4082     if (rf == DROP_RANK) {
4083         sprintf(move, "%c@%c%c\n",
4084                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4085     } else {
4086         if (promoChar == 'x' || promoChar == NULLCHAR) {
4087             sprintf(move, "%c%c%c%c\n",
4088                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4089         } else {
4090             sprintf(move, "%c%c%c%c%c\n",
4091                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4092         }
4093     }
4094 }
4095
4096 void
4097 ProcessICSInitScript(f)
4098      FILE *f;
4099 {
4100     char buf[MSG_SIZ];
4101
4102     while (fgets(buf, MSG_SIZ, f)) {
4103         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4104     }
4105
4106     fclose(f);
4107 }
4108
4109
4110 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4111 void
4112 AlphaRank(char *move, int n)
4113 {
4114 //    char *p = move, c; int x, y;
4115
4116     if (appData.debugMode) {
4117         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4118     }
4119
4120     if(move[1]=='*' &&
4121        move[2]>='0' && move[2]<='9' &&
4122        move[3]>='a' && move[3]<='x'    ) {
4123         move[1] = '@';
4124         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4125         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4126     } else
4127     if(move[0]>='0' && move[0]<='9' &&
4128        move[1]>='a' && move[1]<='x' &&
4129        move[2]>='0' && move[2]<='9' &&
4130        move[3]>='a' && move[3]<='x'    ) {
4131         /* input move, Shogi -> normal */
4132         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4133         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4136     } else
4137     if(move[1]=='@' &&
4138        move[3]>='0' && move[3]<='9' &&
4139        move[2]>='a' && move[2]<='x'    ) {
4140         move[1] = '*';
4141         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4142         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4143     } else
4144     if(
4145        move[0]>='a' && move[0]<='x' &&
4146        move[3]>='0' && move[3]<='9' &&
4147        move[2]>='a' && move[2]<='x'    ) {
4148          /* output move, normal -> Shogi */
4149         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4150         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4153         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4154     }
4155     if (appData.debugMode) {
4156         fprintf(debugFP, "   out = '%s'\n", move);
4157     }
4158 }
4159
4160 /* Parser for moves from gnuchess, ICS, or user typein box */
4161 Boolean
4162 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4163      char *move;
4164      int moveNum;
4165      ChessMove *moveType;
4166      int *fromX, *fromY, *toX, *toY;
4167      char *promoChar;
4168 {
4169     if (appData.debugMode) {
4170         fprintf(debugFP, "move to parse: %s\n", move);
4171     }
4172     *moveType = yylexstr(moveNum, move);
4173
4174     switch (*moveType) {
4175       case WhitePromotionChancellor:
4176       case BlackPromotionChancellor:
4177       case WhitePromotionArchbishop:
4178       case BlackPromotionArchbishop:
4179       case WhitePromotionQueen:
4180       case BlackPromotionQueen:
4181       case WhitePromotionRook:
4182       case BlackPromotionRook:
4183       case WhitePromotionBishop:
4184       case BlackPromotionBishop:
4185       case WhitePromotionKnight:
4186       case BlackPromotionKnight:
4187       case WhitePromotionKing:
4188       case BlackPromotionKing:
4189       case NormalMove:
4190       case WhiteCapturesEnPassant:
4191       case BlackCapturesEnPassant:
4192       case WhiteKingSideCastle:
4193       case WhiteQueenSideCastle:
4194       case BlackKingSideCastle:
4195       case BlackQueenSideCastle:
4196       case WhiteKingSideCastleWild:
4197       case WhiteQueenSideCastleWild:
4198       case BlackKingSideCastleWild:
4199       case BlackQueenSideCastleWild:
4200       /* Code added by Tord: */
4201       case WhiteHSideCastleFR:
4202       case WhiteASideCastleFR:
4203       case BlackHSideCastleFR:
4204       case BlackASideCastleFR:
4205       /* End of code added by Tord */
4206       case IllegalMove:         /* bug or odd chess variant */
4207         *fromX = currentMoveString[0] - AAA;
4208         *fromY = currentMoveString[1] - ONE;
4209         *toX = currentMoveString[2] - AAA;
4210         *toY = currentMoveString[3] - ONE;
4211         *promoChar = currentMoveString[4];
4212         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4213             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4214     if (appData.debugMode) {
4215         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4216     }
4217             *fromX = *fromY = *toX = *toY = 0;
4218             return FALSE;
4219         }
4220         if (appData.testLegality) {
4221           return (*moveType != IllegalMove);
4222         } else {
4223           return !(fromX == fromY && toX == toY);
4224         }
4225
4226       case WhiteDrop:
4227       case BlackDrop:
4228         *fromX = *moveType == WhiteDrop ?
4229           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4230           (int) CharToPiece(ToLower(currentMoveString[0]));
4231         *fromY = DROP_RANK;
4232         *toX = currentMoveString[2] - AAA;
4233         *toY = currentMoveString[3] - ONE;
4234         *promoChar = NULLCHAR;
4235         return TRUE;
4236
4237       case AmbiguousMove:
4238       case ImpossibleMove:
4239       case (ChessMove) 0:       /* end of file */
4240       case ElapsedTime:
4241       case Comment:
4242       case PGNTag:
4243       case NAG:
4244       case WhiteWins:
4245       case BlackWins:
4246       case GameIsDrawn:
4247       default:
4248     if (appData.debugMode) {
4249         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4250     }
4251         /* bug? */
4252         *fromX = *fromY = *toX = *toY = 0;
4253         *promoChar = NULLCHAR;
4254         return FALSE;
4255     }
4256 }
4257
4258 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4259 // All positions will have equal probability, but the current method will not provide a unique
4260 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4261 #define DARK 1
4262 #define LITE 2
4263 #define ANY 3
4264
4265 int squaresLeft[4];
4266 int piecesLeft[(int)BlackPawn];
4267 int seed, nrOfShuffles;
4268
4269 void GetPositionNumber()
4270 {       // sets global variable seed
4271         int i;
4272
4273         seed = appData.defaultFrcPosition;
4274         if(seed < 0) { // randomize based on time for negative FRC position numbers
4275                 for(i=0; i<50; i++) seed += random();
4276                 seed = random() ^ random() >> 8 ^ random() << 8;
4277                 if(seed<0) seed = -seed;
4278         }
4279 }
4280
4281 int put(Board board, int pieceType, int rank, int n, int shade)
4282 // put the piece on the (n-1)-th empty squares of the given shade
4283 {
4284         int i;
4285
4286         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4287                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4288                         board[rank][i] = (ChessSquare) pieceType;
4289                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4290                         squaresLeft[ANY]--;
4291                         piecesLeft[pieceType]--;
4292                         return i;
4293                 }
4294         }
4295         return -1;
4296 }
4297
4298
4299 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4300 // calculate where the next piece goes, (any empty square), and put it there
4301 {
4302         int i;
4303
4304         i = seed % squaresLeft[shade];
4305         nrOfShuffles *= squaresLeft[shade];
4306         seed /= squaresLeft[shade];
4307         put(board, pieceType, rank, i, shade);
4308 }
4309
4310 void AddTwoPieces(Board board, int pieceType, int rank)
4311 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4312 {
4313         int i, n=squaresLeft[ANY], j=n-1, k;
4314
4315         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4316         i = seed % k;  // pick one
4317         nrOfShuffles *= k;
4318         seed /= k;
4319         while(i >= j) i -= j--;
4320         j = n - 1 - j; i += j;
4321         put(board, pieceType, rank, j, ANY);
4322         put(board, pieceType, rank, i, ANY);
4323 }
4324
4325 void SetUpShuffle(Board board, int number)
4326 {
4327         int i, p, first=1;
4328
4329         GetPositionNumber(); nrOfShuffles = 1;
4330
4331         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4332         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4333         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4334
4335         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4336
4337         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4338             p = (int) board[0][i];
4339             if(p < (int) BlackPawn) piecesLeft[p] ++;
4340             board[0][i] = EmptySquare;
4341         }
4342
4343         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4344             // shuffles restricted to allow normal castling put KRR first
4345             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4346                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4347             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4348                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4349             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4350                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4351             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4352                 put(board, WhiteRook, 0, 0, ANY);
4353             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4354         }
4355
4356         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4357             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4358             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4359                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4360                 while(piecesLeft[p] >= 2) {
4361                     AddOnePiece(board, p, 0, LITE);
4362                     AddOnePiece(board, p, 0, DARK);
4363                 }
4364                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4365             }
4366
4367         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4368             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4369             // but we leave King and Rooks for last, to possibly obey FRC restriction
4370             if(p == (int)WhiteRook) continue;
4371             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4372             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4373         }
4374
4375         // now everything is placed, except perhaps King (Unicorn) and Rooks
4376
4377         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4378             // Last King gets castling rights
4379             while(piecesLeft[(int)WhiteUnicorn]) {
4380                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4381                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4382             }
4383
4384             while(piecesLeft[(int)WhiteKing]) {
4385                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4386                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4387             }
4388
4389
4390         } else {
4391             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4392             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4393         }
4394
4395         // Only Rooks can be left; simply place them all
4396         while(piecesLeft[(int)WhiteRook]) {
4397                 i = put(board, WhiteRook, 0, 0, ANY);
4398                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4399                         if(first) {
4400                                 first=0;
4401                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4402                         }
4403                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4404                 }
4405         }
4406         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4407             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4408         }
4409
4410         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4411 }
4412
4413 int SetCharTable( char *table, const char * map )
4414 /* [HGM] moved here from winboard.c because of its general usefulness */
4415 /*       Basically a safe strcpy that uses the last character as King */
4416 {
4417     int result = FALSE; int NrPieces;
4418
4419     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4420                     && NrPieces >= 12 && !(NrPieces&1)) {
4421         int i; /* [HGM] Accept even length from 12 to 34 */
4422
4423         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4424         for( i=0; i<NrPieces/2-1; i++ ) {
4425             table[i] = map[i];
4426             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4427         }
4428         table[(int) WhiteKing]  = map[NrPieces/2-1];
4429         table[(int) BlackKing]  = map[NrPieces-1];
4430
4431         result = TRUE;
4432     }
4433
4434     return result;
4435 }
4436
4437 void Prelude(Board board)
4438 {       // [HGM] superchess: random selection of exo-pieces
4439         int i, j, k; ChessSquare p;
4440         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4441
4442         GetPositionNumber(); // use FRC position number
4443
4444         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4445             SetCharTable(pieceToChar, appData.pieceToCharTable);
4446             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4447                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4448         }
4449
4450         j = seed%4;                 seed /= 4;
4451         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4452         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4453         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4454         j = seed%3 + (seed%3 >= j); seed /= 3;
4455         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4456         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4457         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4458         j = seed%3;                 seed /= 3;
4459         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4460         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4461         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4462         j = seed%2 + (seed%2 >= j); seed /= 2;
4463         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4464         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4465         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4466         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4467         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4468         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4469         put(board, exoPieces[0],    0, 0, ANY);
4470         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4471 }
4472
4473 void
4474 InitPosition(redraw)
4475      int redraw;
4476 {
4477     ChessSquare (* pieces)[BOARD_SIZE];
4478     int i, j, pawnRow, overrule,
4479     oldx = gameInfo.boardWidth,
4480     oldy = gameInfo.boardHeight,
4481     oldh = gameInfo.holdingsWidth,
4482     oldv = gameInfo.variant;
4483
4484     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4485
4486     /* [AS] Initialize pv info list [HGM] and game status */
4487     {
4488         for( i=0; i<MAX_MOVES; i++ ) {
4489             pvInfoList[i].depth = 0;
4490             epStatus[i]=EP_NONE;
4491             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4492         }
4493
4494         initialRulePlies = 0; /* 50-move counter start */
4495
4496         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4497         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4498     }
4499
4500
4501     /* [HGM] logic here is completely changed. In stead of full positions */
4502     /* the initialized data only consist of the two backranks. The switch */
4503     /* selects which one we will use, which is than copied to the Board   */
4504     /* initialPosition, which for the rest is initialized by Pawns and    */
4505     /* empty squares. This initial position is then copied to boards[0],  */
4506     /* possibly after shuffling, so that it remains available.            */
4507
4508     gameInfo.holdingsWidth = 0; /* default board sizes */
4509     gameInfo.boardWidth    = 8;
4510     gameInfo.boardHeight   = 8;
4511     gameInfo.holdingsSize  = 0;
4512     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4513     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4514     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4515
4516     switch (gameInfo.variant) {
4517     case VariantFischeRandom:
4518       shuffleOpenings = TRUE;
4519     default:
4520       pieces = FIDEArray;
4521       break;
4522     case VariantShatranj:
4523       pieces = ShatranjArray;
4524       nrCastlingRights = 0;
4525       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4526       break;
4527     case VariantTwoKings:
4528       pieces = twoKingsArray;
4529       break;
4530     case VariantCapaRandom:
4531       shuffleOpenings = TRUE;
4532     case VariantCapablanca:
4533       pieces = CapablancaArray;
4534       gameInfo.boardWidth = 10;
4535       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4536       break;
4537     case VariantGothic:
4538       pieces = GothicArray;
4539       gameInfo.boardWidth = 10;
4540       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4541       break;
4542     case VariantJanus:
4543       pieces = JanusArray;
4544       gameInfo.boardWidth = 10;
4545       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4546       nrCastlingRights = 6;
4547         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4548         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4549         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4550         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4551         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4552         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4553       break;
4554     case VariantFalcon:
4555       pieces = FalconArray;
4556       gameInfo.boardWidth = 10;
4557       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4558       break;
4559     case VariantXiangqi:
4560       pieces = XiangqiArray;
4561       gameInfo.boardWidth  = 9;
4562       gameInfo.boardHeight = 10;
4563       nrCastlingRights = 0;
4564       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4565       break;
4566     case VariantShogi:
4567       pieces = ShogiArray;
4568       gameInfo.boardWidth  = 9;
4569       gameInfo.boardHeight = 9;
4570       gameInfo.holdingsSize = 7;
4571       nrCastlingRights = 0;
4572       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4573       break;
4574     case VariantCourier:
4575       pieces = CourierArray;
4576       gameInfo.boardWidth  = 12;
4577       nrCastlingRights = 0;
4578       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4579       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4580       break;
4581     case VariantKnightmate:
4582       pieces = KnightmateArray;
4583       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4584       break;
4585     case VariantFairy:
4586       pieces = fairyArray;
4587       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4588       break;
4589     case VariantGreat:
4590       pieces = GreatArray;
4591       gameInfo.boardWidth = 10;
4592       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4593       gameInfo.holdingsSize = 8;
4594       break;
4595     case VariantSuper:
4596       pieces = FIDEArray;
4597       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4598       gameInfo.holdingsSize = 8;
4599       startedFromSetupPosition = TRUE;
4600       break;
4601     case VariantCrazyhouse:
4602     case VariantBughouse:
4603       pieces = FIDEArray;
4604       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4605       gameInfo.holdingsSize = 5;
4606       break;
4607     case VariantWildCastle:
4608       pieces = FIDEArray;
4609       /* !!?shuffle with kings guaranteed to be on d or e file */
4610       shuffleOpenings = 1;
4611       break;
4612     case VariantNoCastle:
4613       pieces = FIDEArray;
4614       nrCastlingRights = 0;
4615       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4616       /* !!?unconstrained back-rank shuffle */
4617       shuffleOpenings = 1;
4618       break;
4619     }
4620
4621     overrule = 0;
4622     if(appData.NrFiles >= 0) {
4623         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4624         gameInfo.boardWidth = appData.NrFiles;
4625     }
4626     if(appData.NrRanks >= 0) {
4627         gameInfo.boardHeight = appData.NrRanks;
4628     }
4629     if(appData.holdingsSize >= 0) {
4630         i = appData.holdingsSize;
4631         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4632         gameInfo.holdingsSize = i;
4633     }
4634     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4635     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4636         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4637
4638     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4639     if(pawnRow < 1) pawnRow = 1;
4640
4641     /* User pieceToChar list overrules defaults */
4642     if(appData.pieceToCharTable != NULL)
4643         SetCharTable(pieceToChar, appData.pieceToCharTable);
4644
4645     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4646
4647         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4648             s = (ChessSquare) 0; /* account holding counts in guard band */
4649         for( i=0; i<BOARD_HEIGHT; i++ )
4650             initialPosition[i][j] = s;
4651
4652         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4653         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4654         initialPosition[pawnRow][j] = WhitePawn;
4655         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4656         if(gameInfo.variant == VariantXiangqi) {
4657             if(j&1) {
4658                 initialPosition[pawnRow][j] =
4659                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4660                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4661                    initialPosition[2][j] = WhiteCannon;
4662                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4663                 }
4664             }
4665         }
4666         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4667     }
4668     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4669
4670             j=BOARD_LEFT+1;
4671             initialPosition[1][j] = WhiteBishop;
4672             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4673             j=BOARD_RGHT-2;
4674             initialPosition[1][j] = WhiteRook;
4675             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4676     }
4677
4678     if( nrCastlingRights == -1) {
4679         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4680         /*       This sets default castling rights from none to normal corners   */
4681         /* Variants with other castling rights must set them themselves above    */
4682         nrCastlingRights = 6;
4683
4684         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4685         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4686         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4687         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4688         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4689         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4690      }
4691
4692      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4693      if(gameInfo.variant == VariantGreat) { // promotion commoners
4694         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4695         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4696         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4697         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4698      }
4699   if (appData.debugMode) {
4700     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4701   }
4702     if(shuffleOpenings) {
4703         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4704         startedFromSetupPosition = TRUE;
4705     }
4706     if(startedFromPositionFile) {
4707       /* [HGM] loadPos: use PositionFile for every new game */
4708       CopyBoard(initialPosition, filePosition);
4709       for(i=0; i<nrCastlingRights; i++)
4710           castlingRights[0][i] = initialRights[i] = fileRights[i];
4711       startedFromSetupPosition = TRUE;
4712     }
4713
4714     CopyBoard(boards[0], initialPosition);
4715     if(oldx != gameInfo.boardWidth ||
4716        oldy != gameInfo.boardHeight ||
4717        oldh != gameInfo.holdingsWidth
4718 #ifdef GOTHIC
4719        || oldv == VariantGothic ||        // For licensing popups
4720        gameInfo.variant == VariantGothic
4721 #endif
4722 #ifdef FALCON
4723        || oldv == VariantFalcon ||
4724        gameInfo.variant == VariantFalcon
4725 #endif
4726                                          )
4727       {
4728             InitDrawingSizes(-2 ,0);
4729       }
4730
4731     if (redraw)
4732       DrawPosition(TRUE, boards[currentMove]);
4733
4734 }
4735
4736 void
4737 SendBoard(cps, moveNum)
4738      ChessProgramState *cps;
4739      int moveNum;
4740 {
4741     char message[MSG_SIZ];
4742
4743     if (cps->useSetboard) {
4744       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4745       sprintf(message, "setboard %s\n", fen);
4746       SendToProgram(message, cps);
4747       free(fen);
4748
4749     } else {
4750       ChessSquare *bp;
4751       int i, j;
4752       /* Kludge to set black to move, avoiding the troublesome and now
4753        * deprecated "black" command.
4754        */
4755       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4756
4757       SendToProgram("edit\n", cps);
4758       SendToProgram("#\n", cps);
4759       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4760         bp = &boards[moveNum][i][BOARD_LEFT];
4761         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4762           if ((int) *bp < (int) BlackPawn) {
4763             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4764                     AAA + j, ONE + i);
4765             if(message[0] == '+' || message[0] == '~') {
4766                 sprintf(message, "%c%c%c+\n",
4767                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4768                         AAA + j, ONE + i);
4769             }
4770             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4771                 message[1] = BOARD_RGHT   - 1 - j + '1';
4772                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4773             }
4774             SendToProgram(message, cps);
4775           }
4776         }
4777       }
4778
4779       SendToProgram("c\n", cps);
4780       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4781         bp = &boards[moveNum][i][BOARD_LEFT];
4782         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4783           if (((int) *bp != (int) EmptySquare)
4784               && ((int) *bp >= (int) BlackPawn)) {
4785             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4786                     AAA + j, ONE + i);
4787             if(message[0] == '+' || message[0] == '~') {
4788                 sprintf(message, "%c%c%c+\n",
4789                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4790                         AAA + j, ONE + i);
4791             }
4792             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4793                 message[1] = BOARD_RGHT   - 1 - j + '1';
4794                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4795             }
4796             SendToProgram(message, cps);
4797           }
4798         }
4799       }
4800
4801       SendToProgram(".\n", cps);
4802     }
4803     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4804 }
4805
4806 int
4807 IsPromotion(fromX, fromY, toX, toY)
4808      int fromX, fromY, toX, toY;
4809 {
4810     /* [HGM] add Shogi promotions */
4811     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4812     ChessSquare piece;
4813
4814     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4815       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4816    /* [HGM] Note to self: line above also weeds out drops */
4817     piece = boards[currentMove][fromY][fromX];
4818     if(gameInfo.variant == VariantShogi) {
4819         promotionZoneSize = 3;
4820         highestPromotingPiece = (int)WhiteKing;
4821         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4822            and if in normal chess we then allow promotion to King, why not
4823            allow promotion of other piece in Shogi?                         */
4824     }
4825     if((int)piece >= BlackPawn) {
4826         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4827              return FALSE;
4828         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4829     } else {
4830         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4831            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4832     }
4833     return ( (int)piece <= highestPromotingPiece );
4834 }
4835
4836 int
4837 InPalace(row, column)
4838      int row, column;
4839 {   /* [HGM] for Xiangqi */
4840     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4841          column < (BOARD_WIDTH + 4)/2 &&
4842          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4843     return FALSE;
4844 }
4845
4846 int
4847 PieceForSquare (x, y)
4848      int x;
4849      int y;
4850 {
4851   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4852      return -1;
4853   else
4854      return boards[currentMove][y][x];
4855 }
4856
4857 int
4858 OKToStartUserMove(x, y)
4859      int x, y;
4860 {
4861     ChessSquare from_piece;
4862     int white_piece;
4863
4864     if (matchMode) return FALSE;
4865     if (gameMode == EditPosition) return TRUE;
4866
4867     if (x >= 0 && y >= 0)
4868       from_piece = boards[currentMove][y][x];
4869     else
4870       from_piece = EmptySquare;
4871
4872     if (from_piece == EmptySquare) return FALSE;
4873
4874     white_piece = (int)from_piece >= (int)WhitePawn &&
4875       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4876
4877     switch (gameMode) {
4878       case PlayFromGameFile:
4879       case AnalyzeFile:
4880       case TwoMachinesPlay:
4881       case EndOfGame:
4882         return FALSE;
4883
4884       case IcsObserving:
4885       case IcsIdle:
4886         return FALSE;
4887
4888       case MachinePlaysWhite:
4889       case IcsPlayingBlack:
4890         if (appData.zippyPlay) return FALSE;
4891         if (white_piece) {
4892             DisplayMoveError(_("You are playing Black"));
4893             return FALSE;
4894         }
4895         break;
4896
4897       case MachinePlaysBlack:
4898       case IcsPlayingWhite:
4899         if (appData.zippyPlay) return FALSE;
4900         if (!white_piece) {
4901             DisplayMoveError(_("You are playing White"));
4902             return FALSE;
4903         }
4904         break;
4905
4906       case EditGame:
4907         if (!white_piece && WhiteOnMove(currentMove)) {
4908             DisplayMoveError(_("It is White's turn"));
4909             return FALSE;
4910         }
4911         if (white_piece && !WhiteOnMove(currentMove)) {
4912             DisplayMoveError(_("It is Black's turn"));
4913             return FALSE;
4914         }
4915         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4916             /* Editing correspondence game history */
4917             /* Could disallow this or prompt for confirmation */
4918             cmailOldMove = -1;
4919         }
4920         if (currentMove < forwardMostMove) {
4921             /* Discarding moves */
4922             /* Could prompt for confirmation here,
4923                but I don't think that's such a good idea */
4924             forwardMostMove = currentMove;
4925         }
4926         break;
4927
4928       case BeginningOfGame:
4929         if (appData.icsActive) return FALSE;
4930         if (!appData.noChessProgram) {
4931             if (!white_piece) {
4932                 DisplayMoveError(_("You are playing White"));
4933                 return FALSE;
4934             }
4935         }
4936         break;
4937
4938       case Training:
4939         if (!white_piece && WhiteOnMove(currentMove)) {
4940             DisplayMoveError(_("It is White's turn"));
4941             return FALSE;
4942         }
4943         if (white_piece && !WhiteOnMove(currentMove)) {
4944             DisplayMoveError(_("It is Black's turn"));
4945             return FALSE;
4946         }
4947         break;
4948
4949       default:
4950       case IcsExamining:
4951         break;
4952     }
4953     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4954         && gameMode != AnalyzeFile && gameMode != Training) {
4955         DisplayMoveError(_("Displayed position is not current"));
4956         return FALSE;
4957     }
4958     return TRUE;
4959 }
4960
4961 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4962 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4963 int lastLoadGameUseList = FALSE;
4964 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4965 ChessMove lastLoadGameStart = (ChessMove) 0;
4966
4967 ChessMove
4968 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4969      int fromX, fromY, toX, toY;
4970      int promoChar;
4971      Boolean captureOwn;
4972 {
4973     ChessMove moveType;
4974     ChessSquare pdown, pup;
4975
4976     if (fromX < 0 || fromY < 0) return ImpossibleMove;
4977
4978     /* [HGM] suppress all moves into holdings area and guard band */
4979     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4980             return ImpossibleMove;
4981
4982     /* [HGM] <sameColor> moved to here from winboard.c */
4983     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
4984     pdown = boards[currentMove][fromY][fromX];
4985     pup = boards[currentMove][toY][toX];
4986     if (    gameMode != EditPosition && !captureOwn &&
4987             (WhitePawn <= pdown && pdown < BlackPawn &&
4988              WhitePawn <= pup && pup < BlackPawn  ||
4989              BlackPawn <= pdown && pdown < EmptySquare &&
4990              BlackPawn <= pup && pup < EmptySquare
4991             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
4992                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
4993                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
4994                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
4995                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
4996         )           )
4997          return Comment;
4998
4999     /* Check if the user is playing in turn.  This is complicated because we
5000        let the user "pick up" a piece before it is his turn.  So the piece he
5001        tried to pick up may have been captured by the time he puts it down!
5002        Therefore we use the color the user is supposed to be playing in this
5003        test, not the color of the piece that is currently on the starting
5004        square---except in EditGame mode, where the user is playing both
5005        sides; fortunately there the capture race can't happen.  (It can
5006        now happen in IcsExamining mode, but that's just too bad.  The user
5007        will get a somewhat confusing message in that case.)
5008        */
5009
5010     switch (gameMode) {
5011       case PlayFromGameFile:
5012       case AnalyzeFile:
5013       case TwoMachinesPlay:
5014       case EndOfGame:
5015       case IcsObserving:
5016       case IcsIdle:
5017         /* We switched into a game mode where moves are not accepted,
5018            perhaps while the mouse button was down. */
5019         return ImpossibleMove;
5020
5021       case MachinePlaysWhite:
5022         /* User is moving for Black */
5023         if (WhiteOnMove(currentMove)) {
5024             DisplayMoveError(_("It is White's turn"));
5025             return ImpossibleMove;
5026         }
5027         break;
5028
5029       case MachinePlaysBlack:
5030         /* User is moving for White */
5031         if (!WhiteOnMove(currentMove)) {
5032             DisplayMoveError(_("It is Black's turn"));
5033             return ImpossibleMove;
5034         }
5035         break;
5036
5037       case EditGame:
5038       case IcsExamining:
5039       case BeginningOfGame:
5040       case AnalyzeMode:
5041       case Training:
5042         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5043             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5044             /* User is moving for Black */
5045             if (WhiteOnMove(currentMove)) {
5046                 DisplayMoveError(_("It is White's turn"));
5047                 return ImpossibleMove;
5048             }
5049         } else {
5050             /* User is moving for White */
5051             if (!WhiteOnMove(currentMove)) {
5052                 DisplayMoveError(_("It is Black's turn"));
5053                 return ImpossibleMove;
5054             }
5055         }
5056         break;
5057
5058       case IcsPlayingBlack:
5059         /* User is moving for Black */
5060         if (WhiteOnMove(currentMove)) {
5061             if (!appData.premove) {
5062                 DisplayMoveError(_("It is White's turn"));
5063             } else if (toX >= 0 && toY >= 0) {
5064                 premoveToX = toX;
5065                 premoveToY = toY;
5066                 premoveFromX = fromX;
5067                 premoveFromY = fromY;
5068                 premovePromoChar = promoChar;
5069                 gotPremove = 1;
5070                 if (appData.debugMode)
5071                     fprintf(debugFP, "Got premove: fromX %d,"
5072                             "fromY %d, toX %d, toY %d\n",
5073                             fromX, fromY, toX, toY);
5074             }
5075             return ImpossibleMove;
5076         }
5077         break;
5078
5079       case IcsPlayingWhite:
5080         /* User is moving for White */
5081         if (!WhiteOnMove(currentMove)) {
5082             if (!appData.premove) {
5083                 DisplayMoveError(_("It is Black's turn"));
5084             } else if (toX >= 0 && toY >= 0) {
5085                 premoveToX = toX;
5086                 premoveToY = toY;
5087                 premoveFromX = fromX;
5088                 premoveFromY = fromY;
5089                 premovePromoChar = promoChar;
5090                 gotPremove = 1;
5091                 if (appData.debugMode)
5092                     fprintf(debugFP, "Got premove: fromX %d,"
5093                             "fromY %d, toX %d, toY %d\n",
5094                             fromX, fromY, toX, toY);
5095             }
5096             return ImpossibleMove;
5097         }
5098         break;
5099
5100       default:
5101         break;
5102
5103       case EditPosition:
5104         /* EditPosition, empty square, or different color piece;
5105            click-click move is possible */
5106         if (toX == -2 || toY == -2) {
5107             boards[0][fromY][fromX] = EmptySquare;
5108             return AmbiguousMove;
5109         } else if (toX >= 0 && toY >= 0) {
5110             boards[0][toY][toX] = boards[0][fromY][fromX];
5111             boards[0][fromY][fromX] = EmptySquare;
5112             return AmbiguousMove;
5113         }
5114         return ImpossibleMove;
5115     }
5116
5117     /* [HGM] If move started in holdings, it means a drop */
5118     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5119          if( pup != EmptySquare ) return ImpossibleMove;
5120          if(appData.testLegality) {
5121              /* it would be more logical if LegalityTest() also figured out
5122               * which drops are legal. For now we forbid pawns on back rank.
5123               * Shogi is on its own here...
5124               */
5125              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5126                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5127                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5128          }
5129          return WhiteDrop; /* Not needed to specify white or black yet */
5130     }
5131
5132     userOfferedDraw = FALSE;
5133
5134     /* [HGM] always test for legality, to get promotion info */
5135     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5136                           epStatus[currentMove], castlingRights[currentMove],
5137                                          fromY, fromX, toY, toX, promoChar);
5138     /* [HGM] but possibly ignore an IllegalMove result */
5139     if (appData.testLegality) {
5140         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5141             DisplayMoveError(_("Illegal move"));
5142             return ImpossibleMove;
5143         }
5144     }
5145     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5146     return moveType;
5147     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5148        function is made into one that returns an OK move type if FinishMove
5149        should be called. This to give the calling driver routine the
5150        opportunity to finish the userMove input with a promotion popup,
5151        without bothering the user with this for invalid or illegal moves */
5152
5153 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5154 }
5155
5156 /* Common tail of UserMoveEvent and DropMenuEvent */
5157 int
5158 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5159      ChessMove moveType;
5160      int fromX, fromY, toX, toY;
5161      /*char*/int promoChar;
5162 {
5163   char *bookHit = 0;
5164
5165   if(appData.debugMode)
5166     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5167
5168   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5169     {
5170       // [HGM] superchess: suppress promotions to non-available piece
5171       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5172       if(WhiteOnMove(currentMove))
5173         {
5174           if(!boards[currentMove][k][BOARD_WIDTH-2])
5175             return 0;
5176         }
5177       else
5178         {
5179           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5180             return 0;
5181         }
5182     }
5183   
5184   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5185      move type in caller when we know the move is a legal promotion */
5186   if(moveType == NormalMove && promoChar)
5187     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5188
5189   if(appData.debugMode) 
5190     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5191
5192   /* [HGM] convert drag-and-drop piece drops to standard form */
5193   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5194     {
5195       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5196       if(appData.debugMode) 
5197         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5198                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5199       //         fromX = boards[currentMove][fromY][fromX];
5200       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5201       if(fromX == 0) 
5202         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5203
5204       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5205
5206       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5207         fromX++; 
5208
5209       fromY = DROP_RANK;
5210     }
5211
5212   /* [HGM] <popupFix> The following if has been moved here from
5213      UserMoveEvent(). Because it seemed to belon here (why not allow
5214      piece drops in training games?), and because it can only be
5215      performed after it is known to what we promote. */
5216   if (gameMode == Training)
5217     {
5218       /* compare the move played on the board to the next move in the
5219        * game. If they match, display the move and the opponent's response.
5220        * If they don't match, display an error message.
5221        */
5222       int saveAnimate;
5223       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5224       CopyBoard(testBoard, boards[currentMove]);
5225       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5226
5227       if (CompareBoards(testBoard, boards[currentMove+1]))
5228         {
5229           ForwardInner(currentMove+1);
5230
5231           /* Autoplay the opponent's response.
5232            * if appData.animate was TRUE when Training mode was entered,
5233            * the response will be animated.
5234            */
5235           saveAnimate = appData.animate;
5236           appData.animate = animateTraining;
5237           ForwardInner(currentMove+1);
5238           appData.animate = saveAnimate;
5239
5240           /* check for the end of the game */
5241           if (currentMove >= forwardMostMove)
5242             {
5243               gameMode = PlayFromGameFile;
5244               ModeHighlight();
5245               SetTrainingModeOff();
5246               DisplayInformation(_("End of game"));
5247             }
5248         }
5249       else
5250         {
5251           DisplayError(_("Incorrect move"), 0);
5252         }
5253       return 1;
5254     }
5255
5256   /* Ok, now we know that the move is good, so we can kill
5257      the previous line in Analysis Mode */
5258   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5259     {
5260       forwardMostMove = currentMove;
5261     }
5262
5263   /* If we need the chess program but it's dead, restart it */
5264   ResurrectChessProgram();
5265
5266   /* A user move restarts a paused game*/
5267   if (pausing)
5268     PauseEvent();
5269
5270   thinkOutput[0] = NULLCHAR;
5271
5272   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5273
5274   if (gameMode == BeginningOfGame)
5275     {
5276       if (appData.noChessProgram)
5277         {
5278           gameMode = EditGame;
5279           SetGameInfo();
5280         }
5281       else
5282         {
5283           char buf[MSG_SIZ];
5284           gameMode = MachinePlaysBlack;
5285           StartClocks();
5286           SetGameInfo();
5287           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5288           DisplayTitle(buf);
5289           if (first.sendName)
5290             {
5291               sprintf(buf, "name %s\n", gameInfo.white);
5292               SendToProgram(buf, &first);
5293             }
5294           StartClocks();
5295         }
5296       ModeHighlight();
5297     }
5298   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5299
5300   /* Relay move to ICS or chess engine */
5301   if (appData.icsActive)
5302     {
5303       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5304           gameMode == IcsExamining)
5305         {
5306           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5307           ics_user_moved = 1;
5308         }
5309     }
5310   else
5311     {
5312       if (first.sendTime && (gameMode == BeginningOfGame ||
5313                              gameMode == MachinePlaysWhite ||
5314                              gameMode == MachinePlaysBlack))
5315         {
5316           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5317         }
5318       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5319         {
5320           // [HGM] book: if program might be playing, let it use book
5321           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5322           first.maybeThinking = TRUE;
5323         }
5324       else
5325         SendMoveToProgram(forwardMostMove-1, &first);
5326       if (currentMove == cmailOldMove + 1)
5327         {
5328           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5329         }
5330     }
5331
5332   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5333
5334   switch (gameMode)
5335     {
5336     case EditGame:
5337       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5338                        EP_UNKNOWN, castlingRights[currentMove]) )
5339         {
5340         case MT_NONE:
5341         case MT_CHECK:
5342           break;
5343         case MT_CHECKMATE:
5344         case MT_STAINMATE:
5345           if (WhiteOnMove(currentMove))
5346             {
5347               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5348             }
5349           else
5350             {
5351               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5352             }
5353           break;
5354         case MT_STALEMATE:
5355           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5356           break;
5357     }
5358       break;
5359
5360     case MachinePlaysBlack:
5361     case MachinePlaysWhite:
5362       /* disable certain menu options while machine is thinking */
5363       SetMachineThinkingEnables();
5364       break;
5365
5366     default:
5367       break;
5368     }
5369
5370   if(bookHit)
5371     { // [HGM] book: simulate book reply
5372       static char bookMove[MSG_SIZ]; // a bit generous?
5373
5374       programStats.nodes = programStats.depth = programStats.time =
5375         programStats.score = programStats.got_only_move = 0;
5376       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5377
5378       strcpy(bookMove, "move ");
5379       strcat(bookMove, bookHit);
5380       HandleMachineMove(bookMove, &first);
5381     }
5382
5383   return 1;
5384 }
5385
5386 void
5387 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5388      int fromX, fromY, toX, toY;
5389      int promoChar;
5390 {
5391     /* [HGM] This routine was added to allow calling of its two logical
5392        parts from other modules in the old way. Before, UserMoveEvent()
5393        automatically called FinishMove() if the move was OK, and returned
5394        otherwise. I separated the two, in order to make it possible to
5395        slip a promotion popup in between. But that it always needs two
5396        calls, to the first part, (now called UserMoveTest() ), and to
5397        FinishMove if the first part succeeded. Calls that do not need
5398        to do anything in between, can call this routine the old way.
5399     */
5400     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5401 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5402     if(moveType == AmbiguousMove)
5403         DrawPosition(FALSE, boards[currentMove]);
5404     else if(moveType != ImpossibleMove && moveType != Comment)
5405         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5406 }
5407
5408 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5409 {
5410 //    char * hint = lastHint;
5411     FrontEndProgramStats stats;
5412
5413     stats.which = cps == &first ? 0 : 1;
5414     stats.depth = cpstats->depth;
5415     stats.nodes = cpstats->nodes;
5416     stats.score = cpstats->score;
5417     stats.time = cpstats->time;
5418     stats.pv = cpstats->movelist;
5419     stats.hint = lastHint;
5420     stats.an_move_index = 0;
5421     stats.an_move_count = 0;
5422
5423     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5424         stats.hint = cpstats->move_name;
5425         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5426         stats.an_move_count = cpstats->nr_moves;
5427     }
5428
5429     SetProgramStats( &stats );
5430 }
5431
5432 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5433 {   // [HGM] book: this routine intercepts moves to simulate book replies
5434     char *bookHit = NULL;
5435
5436     //first determine if the incoming move brings opponent into his book
5437     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5438         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5439     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5440     if(bookHit != NULL && !cps->bookSuspend) {
5441         // make sure opponent is not going to reply after receiving move to book position
5442         SendToProgram("force\n", cps);
5443         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5444     }
5445     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5446     // now arrange restart after book miss
5447     if(bookHit) {
5448         // after a book hit we never send 'go', and the code after the call to this routine
5449         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5450         char buf[MSG_SIZ];
5451         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5452         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5453         SendToProgram(buf, cps);
5454         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5455     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5456         SendToProgram("go\n", cps);
5457         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5458     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5459         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5460             SendToProgram("go\n", cps);
5461         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5462     }
5463     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5464 }
5465
5466 char *savedMessage;
5467 ChessProgramState *savedState;
5468 void DeferredBookMove(void)
5469 {
5470         if(savedState->lastPing != savedState->lastPong)
5471                     ScheduleDelayedEvent(DeferredBookMove, 10);
5472         else
5473         HandleMachineMove(savedMessage, savedState);
5474 }
5475
5476 void
5477 HandleMachineMove(message, cps)
5478      char *message;
5479      ChessProgramState *cps;
5480 {
5481     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5482     char realname[MSG_SIZ];
5483     int fromX, fromY, toX, toY;
5484     ChessMove moveType;
5485     char promoChar;
5486     char *p;
5487     int machineWhite;
5488     char *bookHit;
5489
5490 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5491     /*
5492      * Kludge to ignore BEL characters
5493      */
5494     while (*message == '\007') message++;
5495
5496     /*
5497      * [HGM] engine debug message: ignore lines starting with '#' character
5498      */
5499     if(cps->debug && *message == '#') return;
5500
5501     /*
5502      * Look for book output
5503      */
5504     if (cps == &first && bookRequested) {
5505         if (message[0] == '\t' || message[0] == ' ') {
5506             /* Part of the book output is here; append it */
5507             strcat(bookOutput, message);
5508             strcat(bookOutput, "  \n");
5509             return;
5510         } else if (bookOutput[0] != NULLCHAR) {
5511             /* All of book output has arrived; display it */
5512             char *p = bookOutput;
5513             while (*p != NULLCHAR) {
5514                 if (*p == '\t') *p = ' ';
5515                 p++;
5516             }
5517             DisplayInformation(bookOutput);
5518             bookRequested = FALSE;
5519             /* Fall through to parse the current output */
5520         }
5521     }
5522
5523     /*
5524      * Look for machine move.
5525      */
5526     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5527         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5528     {
5529         /* This method is only useful on engines that support ping */
5530         if (cps->lastPing != cps->lastPong) {
5531           if (gameMode == BeginningOfGame) {
5532             /* Extra move from before last new; ignore */
5533             if (appData.debugMode) {
5534                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5535             }
5536           } else {
5537             if (appData.debugMode) {
5538                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5539                         cps->which, gameMode);
5540             }
5541
5542             SendToProgram("undo\n", cps);
5543           }
5544           return;
5545         }
5546
5547         switch (gameMode) {
5548           case BeginningOfGame:
5549             /* Extra move from before last reset; ignore */
5550             if (appData.debugMode) {
5551                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5552             }
5553             return;
5554
5555           case EndOfGame:
5556           case IcsIdle:
5557           default:
5558             /* Extra move after we tried to stop.  The mode test is
5559                not a reliable way of detecting this problem, but it's
5560                the best we can do on engines that don't support ping.
5561             */
5562             if (appData.debugMode) {
5563                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5564                         cps->which, gameMode);
5565             }
5566             SendToProgram("undo\n", cps);
5567             return;
5568
5569           case MachinePlaysWhite:
5570           case IcsPlayingWhite:
5571             machineWhite = TRUE;
5572             break;
5573
5574           case MachinePlaysBlack:
5575           case IcsPlayingBlack:
5576             machineWhite = FALSE;
5577             break;
5578
5579           case TwoMachinesPlay:
5580             machineWhite = (cps->twoMachinesColor[0] == 'w');
5581             break;
5582         }
5583         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5584             if (appData.debugMode) {
5585                 fprintf(debugFP,
5586                         "Ignoring move out of turn by %s, gameMode %d"
5587                         ", forwardMost %d\n",
5588                         cps->which, gameMode, forwardMostMove);
5589             }
5590             return;
5591         }
5592
5593     if (appData.debugMode) { int f = forwardMostMove;
5594         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5595                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5596     }
5597         if(cps->alphaRank) AlphaRank(machineMove, 4);
5598         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5599                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5600             /* Machine move could not be parsed; ignore it. */
5601             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5602                     machineMove, cps->which);
5603             DisplayError(buf1, 0);
5604             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5605                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5606             if (gameMode == TwoMachinesPlay) {
5607               GameEnds(machineWhite ? BlackWins : WhiteWins,
5608                        buf1, GE_XBOARD);
5609             }
5610             return;
5611         }
5612
5613         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5614         /* So we have to redo legality test with true e.p. status here,  */
5615         /* to make sure an illegal e.p. capture does not slip through,   */
5616         /* to cause a forfeit on a justified illegal-move complaint      */
5617         /* of the opponent.                                              */
5618         if( gameMode==TwoMachinesPlay && appData.testLegality
5619             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5620                                                               ) {
5621            ChessMove moveType;
5622            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5623                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5624                              fromY, fromX, toY, toX, promoChar);
5625             if (appData.debugMode) {
5626                 int i;
5627                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5628                     castlingRights[forwardMostMove][i], castlingRank[i]);
5629                 fprintf(debugFP, "castling rights\n");
5630             }
5631             if(moveType == IllegalMove) {
5632                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5633                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5634                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5635                            buf1, GE_XBOARD);
5636                 return;
5637            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5638            /* [HGM] Kludge to handle engines that send FRC-style castling
5639               when they shouldn't (like TSCP-Gothic) */
5640            switch(moveType) {
5641              case WhiteASideCastleFR:
5642              case BlackASideCastleFR:
5643                toX+=2;
5644                currentMoveString[2]++;
5645                break;
5646              case WhiteHSideCastleFR:
5647              case BlackHSideCastleFR:
5648                toX--;
5649                currentMoveString[2]--;
5650                break;
5651              default: ; // nothing to do, but suppresses warning of pedantic compilers
5652            }
5653         }
5654         hintRequested = FALSE;
5655         lastHint[0] = NULLCHAR;
5656         bookRequested = FALSE;
5657         /* Program may be pondering now */
5658         cps->maybeThinking = TRUE;
5659         if (cps->sendTime == 2) cps->sendTime = 1;
5660         if (cps->offeredDraw) cps->offeredDraw--;
5661
5662 #if ZIPPY
5663         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5664             first.initDone) {
5665           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5666           ics_user_moved = 1;
5667           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5668                 char buf[3*MSG_SIZ];
5669
5670                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5671                         programStats.score / 100.,
5672                         programStats.depth,
5673                         programStats.time / 100.,
5674                         (unsigned int)programStats.nodes,
5675                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5676                         programStats.movelist);
5677                 SendToICS(buf);
5678 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5679           }
5680         }
5681 #endif
5682         /* currentMoveString is set as a side-effect of ParseOneMove */
5683         strcpy(machineMove, currentMoveString);
5684         strcat(machineMove, "\n");
5685         strcpy(moveList[forwardMostMove], machineMove);
5686
5687         /* [AS] Save move info and clear stats for next move */
5688         pvInfoList[ forwardMostMove ].score = programStats.score;
5689         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5690         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5691         ClearProgramStats();
5692         thinkOutput[0] = NULLCHAR;
5693         hiddenThinkOutputState = 0;
5694
5695         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5696
5697         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5698         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5699             int count = 0;
5700
5701             while( count < adjudicateLossPlies ) {
5702                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5703
5704                 if( count & 1 ) {
5705                     score = -score; /* Flip score for winning side */
5706                 }
5707
5708                 if( score > adjudicateLossThreshold ) {
5709                     break;
5710                 }
5711
5712                 count++;
5713             }
5714
5715             if( count >= adjudicateLossPlies ) {
5716                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5717
5718                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5719                     "Xboard adjudication",
5720                     GE_XBOARD );
5721
5722                 return;
5723             }
5724         }
5725
5726         if( gameMode == TwoMachinesPlay ) {
5727           // [HGM] some adjudications useful with buggy engines
5728             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5729           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5730
5731
5732             if( appData.testLegality )
5733             {   /* [HGM] Some more adjudications for obstinate engines */
5734                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5735                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5736                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5737                 static int moveCount = 6;
5738                 ChessMove result;
5739                 char *reason = NULL;
5740
5741                 /* Count what is on board. */
5742                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5743                 {   ChessSquare p = boards[forwardMostMove][i][j];
5744                     int m=i;
5745
5746                     switch((int) p)
5747                     {   /* count B,N,R and other of each side */
5748                         case WhiteKing:
5749                         case BlackKing:
5750                              NrK++; break; // [HGM] atomic: count Kings
5751                         case WhiteKnight:
5752                              NrWN++; break;
5753                         case WhiteBishop:
5754                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5755                              bishopsColor |= 1 << ((i^j)&1);
5756                              NrWB++; break;
5757                         case BlackKnight:
5758                              NrBN++; break;
5759                         case BlackBishop:
5760                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5761                              bishopsColor |= 1 << ((i^j)&1);
5762                              NrBB++; break;
5763                         case WhiteRook:
5764                              NrWR++; break;
5765                         case BlackRook:
5766                              NrBR++; break;
5767                         case WhiteQueen:
5768                              NrWQ++; break;
5769                         case BlackQueen:
5770                              NrBQ++; break;
5771                         case EmptySquare:
5772                              break;
5773                         case BlackPawn:
5774                              m = 7-i;
5775                         case WhitePawn:
5776                              PawnAdvance += m; NrPawns++;
5777                     }
5778                     NrPieces += (p != EmptySquare);
5779                     NrW += ((int)p < (int)BlackPawn);
5780                     if(gameInfo.variant == VariantXiangqi &&
5781                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5782                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5783                         NrW -= ((int)p < (int)BlackPawn);
5784                     }
5785                 }
5786
5787                 /* Some material-based adjudications that have to be made before stalemate test */
5788                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5789                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5790                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5791                      if(appData.checkMates) {
5792                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5793                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5794                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5795                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5796                          return;
5797                      }
5798                 }
5799
5800                 /* Bare King in Shatranj (loses) or Losers (wins) */
5801                 if( NrW == 1 || NrPieces - NrW == 1) {
5802                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5803                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5804                      if(appData.checkMates) {
5805                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5806                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5807                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5808                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5809                          return;
5810                      }
5811                   } else
5812                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5813                   {    /* bare King */
5814                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5815                         if(appData.checkMates) {
5816                             /* but only adjudicate if adjudication enabled */
5817                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5818                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5819                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5820                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5821                             return;
5822                         }
5823                   }
5824                 } else bare = 1;
5825
5826
5827             // don't wait for engine to announce game end if we can judge ourselves
5828             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5829                                        castlingRights[forwardMostMove]) ) {
5830               case MT_CHECK:
5831                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5832                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5833                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5834                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5835                             checkCnt++;
5836                         if(checkCnt >= 2) {
5837                             reason = "Xboard adjudication: 3rd check";
5838                             epStatus[forwardMostMove] = EP_CHECKMATE;
5839                             break;
5840                         }
5841                     }
5842                 }
5843               case MT_NONE:
5844               default:
5845                 break;
5846               case MT_STALEMATE:
5847               case MT_STAINMATE:
5848                 reason = "Xboard adjudication: Stalemate";
5849                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5850                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5851                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5852                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5853                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5854                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5855                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5856                                                                         EP_CHECKMATE : EP_WINS);
5857                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5858                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5859                 }
5860                 break;
5861               case MT_CHECKMATE:
5862                 reason = "Xboard adjudication: Checkmate";
5863                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5864                 break;
5865             }
5866
5867                 switch(i = epStatus[forwardMostMove]) {
5868                     case EP_STALEMATE:
5869                         result = GameIsDrawn; break;
5870                     case EP_CHECKMATE:
5871                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5872                     case EP_WINS:
5873                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5874                     default:
5875                         result = (ChessMove) 0;
5876                 }
5877                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5878                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5879                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5880                     GameEnds( result, reason, GE_XBOARD );
5881                     return;
5882                 }
5883
5884                 /* Next absolutely insufficient mating material. */
5885                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5886                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5887                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5888                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5889                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5890
5891                      /* always flag draws, for judging claims */
5892                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5893
5894                      if(appData.materialDraws) {
5895                          /* but only adjudicate them if adjudication enabled */
5896                          SendToProgram("force\n", cps->other); // suppress reply
5897                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5898                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5899                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5900                          return;
5901                      }
5902                 }
5903
5904                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5905                 if(NrPieces == 4 &&
5906                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5907                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5908                    || NrWN==2 || NrBN==2     /* KNNK */
5909                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5910                   ) ) {
5911                      if(--moveCount < 0 && appData.trivialDraws)
5912                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5913                           SendToProgram("force\n", cps->other); // suppress reply
5914                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5915                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5916                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5917                           return;
5918                      }
5919                 } else moveCount = 6;
5920             }
5921           }
5922           
5923           if (appData.debugMode) { int i;
5924             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5925                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5926                     appData.drawRepeats);
5927             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5928               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5929             
5930           }
5931
5932                 /* Check for rep-draws */
5933                 count = 0;
5934                 for(k = forwardMostMove-2;
5935                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5936                         epStatus[k] < EP_UNKNOWN &&
5937                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5938                     k-=2)
5939                 {   int rights=0;
5940                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5941                         /* compare castling rights */
5942                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5943                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5944                                 rights++; /* King lost rights, while rook still had them */
5945                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5946                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5947                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5948                                    rights++; /* but at least one rook lost them */
5949                         }
5950                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5951                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5952                                 rights++;
5953                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5954                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5955                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5956                                    rights++;
5957                         }
5958                         if( rights == 0 && ++count > appData.drawRepeats-2
5959                             && appData.drawRepeats > 1) {
5960                              /* adjudicate after user-specified nr of repeats */
5961                              SendToProgram("force\n", cps->other); // suppress reply
5962                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5963                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5964                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5965                                 // [HGM] xiangqi: check for forbidden perpetuals
5966                                 int m, ourPerpetual = 1, hisPerpetual = 1;
5967                                 for(m=forwardMostMove; m>k; m-=2) {
5968                                     if(MateTest(boards[m], PosFlags(m),
5969                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
5970                                         ourPerpetual = 0; // the current mover did not always check
5971                                     if(MateTest(boards[m-1], PosFlags(m-1),
5972                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
5973                                         hisPerpetual = 0; // the opponent did not always check
5974                                 }
5975                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5976                                                                         ourPerpetual, hisPerpetual);
5977                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5978                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5979                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
5980                                     return;
5981                                 }
5982                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
5983                                     break; // (or we would have caught him before). Abort repetition-checking loop.
5984                                 // Now check for perpetual chases
5985                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5986                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
5987                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5988                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5989                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5990                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
5991                                         return;
5992                                     }
5993                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
5994                                         break; // Abort repetition-checking loop.
5995                                 }
5996                                 // if neither of us is checking or chasing all the time, or both are, it is draw
5997                              }
5998                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5999                              return;
6000                         }
6001                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6002                              epStatus[forwardMostMove] = EP_REP_DRAW;
6003                     }
6004                 }
6005
6006                 /* Now we test for 50-move draws. Determine ply count */
6007                 count = forwardMostMove;
6008                 /* look for last irreversble move */
6009                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6010                     count--;
6011                 /* if we hit starting position, add initial plies */
6012                 if( count == backwardMostMove )
6013                     count -= initialRulePlies;
6014                 count = forwardMostMove - count;
6015                 if( count >= 100)
6016                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6017                          /* this is used to judge if draw claims are legal */
6018                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6019                          SendToProgram("force\n", cps->other); // suppress reply
6020                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6021                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6022                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6023                          return;
6024                 }
6025
6026                 /* if draw offer is pending, treat it as a draw claim
6027                  * when draw condition present, to allow engines a way to
6028                  * claim draws before making their move to avoid a race
6029                  * condition occurring after their move
6030                  */
6031                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6032                          char *p = NULL;
6033                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6034                              p = "Draw claim: 50-move rule";
6035                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6036                              p = "Draw claim: 3-fold repetition";
6037                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6038                              p = "Draw claim: insufficient mating material";
6039                          if( p != NULL ) {
6040                              SendToProgram("force\n", cps->other); // suppress reply
6041                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6042                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6043                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6044                              return;
6045                          }
6046                 }
6047
6048
6049                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6050                     SendToProgram("force\n", cps->other); // suppress reply
6051                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6052                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6053
6054                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6055
6056                     return;
6057                 }
6058         }
6059
6060         bookHit = NULL;
6061         if (gameMode == TwoMachinesPlay) {
6062             /* [HGM] relaying draw offers moved to after reception of move */
6063             /* and interpreting offer as claim if it brings draw condition */
6064             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6065                 SendToProgram("draw\n", cps->other);
6066             }
6067             if (cps->other->sendTime) {
6068                 SendTimeRemaining(cps->other,
6069                                   cps->other->twoMachinesColor[0] == 'w');
6070             }
6071             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6072             if (firstMove && !bookHit) {
6073                 firstMove = FALSE;
6074                 if (cps->other->useColors) {
6075                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6076                 }
6077                 SendToProgram("go\n", cps->other);
6078             }
6079             cps->other->maybeThinking = TRUE;
6080         }
6081
6082         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6083
6084         if (!pausing && appData.ringBellAfterMoves) {
6085             RingBell();
6086         }
6087
6088         /*
6089          * Reenable menu items that were disabled while
6090          * machine was thinking
6091          */
6092         if (gameMode != TwoMachinesPlay)
6093             SetUserThinkingEnables();
6094
6095         // [HGM] book: after book hit opponent has received move and is now in force mode
6096         // force the book reply into it, and then fake that it outputted this move by jumping
6097         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6098         if(bookHit) {
6099                 static char bookMove[MSG_SIZ]; // a bit generous?
6100
6101                 strcpy(bookMove, "move ");
6102                 strcat(bookMove, bookHit);
6103                 message = bookMove;
6104                 cps = cps->other;
6105                 programStats.nodes = programStats.depth = programStats.time =
6106                 programStats.score = programStats.got_only_move = 0;
6107                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6108
6109                 if(cps->lastPing != cps->lastPong) {
6110                     savedMessage = message; // args for deferred call
6111                     savedState = cps;
6112                     ScheduleDelayedEvent(DeferredBookMove, 10);
6113                     return;
6114                 }
6115                 goto FakeBookMove;
6116         }
6117
6118         return;
6119     }
6120
6121     /* Set special modes for chess engines.  Later something general
6122      *  could be added here; for now there is just one kludge feature,
6123      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6124      *  when "xboard" is given as an interactive command.
6125      */
6126     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6127         cps->useSigint = FALSE;
6128         cps->useSigterm = FALSE;
6129     }
6130     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6131       ParseFeatures(message+8, cps);
6132       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6133     }
6134
6135     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6136      * want this, I was asked to put it in, and obliged.
6137      */
6138     if (!strncmp(message, "setboard ", 9)) {
6139         Board initial_position; int i;
6140
6141         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6142
6143         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6144             DisplayError(_("Bad FEN received from engine"), 0);
6145             return ;
6146         } else {
6147            Reset(FALSE, FALSE);
6148            CopyBoard(boards[0], initial_position);
6149            initialRulePlies = FENrulePlies;
6150            epStatus[0] = FENepStatus;
6151            for( i=0; i<nrCastlingRights; i++ )
6152                 castlingRights[0][i] = FENcastlingRights[i];
6153            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6154            else gameMode = MachinePlaysBlack;
6155            DrawPosition(FALSE, boards[currentMove]);
6156         }
6157         return;
6158     }
6159
6160     /*
6161      * Look for communication commands
6162      */
6163     if (!strncmp(message, "telluser ", 9)) {
6164         DisplayNote(message + 9);
6165         return;
6166     }
6167     if (!strncmp(message, "tellusererror ", 14)) {
6168         DisplayError(message + 14, 0);
6169         return;
6170     }
6171     if (!strncmp(message, "tellopponent ", 13)) {
6172       if (appData.icsActive) {
6173         if (loggedOn) {
6174           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6175           SendToICS(buf1);
6176         }
6177       } else {
6178         DisplayNote(message + 13);
6179       }
6180       return;
6181     }
6182     if (!strncmp(message, "tellothers ", 11)) {
6183       if (appData.icsActive) {
6184         if (loggedOn) {
6185           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6186           SendToICS(buf1);
6187         }
6188       }
6189       return;
6190     }
6191     if (!strncmp(message, "tellall ", 8)) {
6192       if (appData.icsActive) {
6193         if (loggedOn) {
6194           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6195           SendToICS(buf1);
6196         }
6197       } else {
6198         DisplayNote(message + 8);
6199       }
6200       return;
6201     }
6202     if (strncmp(message, "warning", 7) == 0) {
6203         /* Undocumented feature, use tellusererror in new code */
6204         DisplayError(message, 0);
6205         return;
6206     }
6207     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6208         strcpy(realname, cps->tidy);
6209         strcat(realname, " query");
6210         AskQuestion(realname, buf2, buf1, cps->pr);
6211         return;
6212     }
6213     /* Commands from the engine directly to ICS.  We don't allow these to be
6214      *  sent until we are logged on. Crafty kibitzes have been known to
6215      *  interfere with the login process.
6216      */
6217     if (loggedOn) {
6218         if (!strncmp(message, "tellics ", 8)) {
6219             SendToICS(message + 8);
6220             SendToICS("\n");
6221             return;
6222         }
6223         if (!strncmp(message, "tellicsnoalias ", 15)) {
6224             SendToICS(ics_prefix);
6225             SendToICS(message + 15);
6226             SendToICS("\n");
6227             return;
6228         }
6229         /* The following are for backward compatibility only */
6230         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6231             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6232             SendToICS(ics_prefix);
6233             SendToICS(message);
6234             SendToICS("\n");
6235             return;
6236         }
6237     }
6238     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6239         return;
6240     }
6241     /*
6242      * If the move is illegal, cancel it and redraw the board.
6243      * Also deal with other error cases.  Matching is rather loose
6244      * here to accommodate engines written before the spec.
6245      */
6246     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6247         strncmp(message, "Error", 5) == 0) {
6248         if (StrStr(message, "name") ||
6249             StrStr(message, "rating") || StrStr(message, "?") ||
6250             StrStr(message, "result") || StrStr(message, "board") ||
6251             StrStr(message, "bk") || StrStr(message, "computer") ||
6252             StrStr(message, "variant") || StrStr(message, "hint") ||
6253             StrStr(message, "random") || StrStr(message, "depth") ||
6254             StrStr(message, "accepted")) {
6255             return;
6256         }
6257         if (StrStr(message, "protover")) {
6258           /* Program is responding to input, so it's apparently done
6259              initializing, and this error message indicates it is
6260              protocol version 1.  So we don't need to wait any longer
6261              for it to initialize and send feature commands. */
6262           FeatureDone(cps, 1);
6263           cps->protocolVersion = 1;
6264           return;
6265         }
6266         cps->maybeThinking = FALSE;
6267
6268         if (StrStr(message, "draw")) {
6269             /* Program doesn't have "draw" command */
6270             cps->sendDrawOffers = 0;
6271             return;
6272         }
6273         if (cps->sendTime != 1 &&
6274             (StrStr(message, "time") || StrStr(message, "otim"))) {
6275           /* Program apparently doesn't have "time" or "otim" command */
6276           cps->sendTime = 0;
6277           return;
6278         }
6279         if (StrStr(message, "analyze")) {
6280             cps->analysisSupport = FALSE;
6281             cps->analyzing = FALSE;
6282             Reset(FALSE, TRUE);
6283             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6284             DisplayError(buf2, 0);
6285             return;
6286         }
6287         if (StrStr(message, "(no matching move)st")) {
6288           /* Special kludge for GNU Chess 4 only */
6289           cps->stKludge = TRUE;
6290           SendTimeControl(cps, movesPerSession, timeControl,
6291                           timeIncrement, appData.searchDepth,
6292                           searchTime);
6293           return;
6294         }
6295         if (StrStr(message, "(no matching move)sd")) {
6296           /* Special kludge for GNU Chess 4 only */
6297           cps->sdKludge = TRUE;
6298           SendTimeControl(cps, movesPerSession, timeControl,
6299                           timeIncrement, appData.searchDepth,
6300                           searchTime);
6301           return;
6302         }
6303         if (!StrStr(message, "llegal")) {
6304             return;
6305         }
6306         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6307             gameMode == IcsIdle) return;
6308         if (forwardMostMove <= backwardMostMove) return;
6309         if (pausing) PauseEvent();
6310       if(appData.forceIllegal) {
6311             // [HGM] illegal: machine refused move; force position after move into it
6312           SendToProgram("force\n", cps);
6313           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6314                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6315                 // when black is to move, while there might be nothing on a2 or black
6316                 // might already have the move. So send the board as if white has the move.
6317                 // But first we must change the stm of the engine, as it refused the last move
6318                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6319                 if(WhiteOnMove(forwardMostMove)) {
6320                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6321                     SendBoard(cps, forwardMostMove); // kludgeless board
6322                 } else {
6323                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6324                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6325                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6326                 }
6327           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6328             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6329                  gameMode == TwoMachinesPlay)
6330               SendToProgram("go\n", cps);
6331             return;
6332       } else
6333         if (gameMode == PlayFromGameFile) {
6334             /* Stop reading this game file */
6335             gameMode = EditGame;
6336             ModeHighlight();
6337         }
6338         currentMove = --forwardMostMove;
6339         DisplayMove(currentMove-1); /* before DisplayMoveError */
6340         SwitchClocks();
6341         DisplayBothClocks();
6342         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6343                 parseList[currentMove], cps->which);
6344         DisplayMoveError(buf1);
6345         DrawPosition(FALSE, boards[currentMove]);
6346
6347         /* [HGM] illegal-move claim should forfeit game when Xboard */
6348         /* only passes fully legal moves                            */
6349         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6350             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6351                                 "False illegal-move claim", GE_XBOARD );
6352         }
6353         return;
6354     }
6355     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6356         /* Program has a broken "time" command that
6357            outputs a string not ending in newline.
6358            Don't use it. */
6359         cps->sendTime = 0;
6360     }
6361
6362     /*
6363      * If chess program startup fails, exit with an error message.
6364      * Attempts to recover here are futile.
6365      */
6366     if ((StrStr(message, "unknown host") != NULL)
6367         || (StrStr(message, "No remote directory") != NULL)
6368         || (StrStr(message, "not found") != NULL)
6369         || (StrStr(message, "No such file") != NULL)
6370         || (StrStr(message, "can't alloc") != NULL)
6371         || (StrStr(message, "Permission denied") != NULL)) {
6372
6373         cps->maybeThinking = FALSE;
6374         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6375                 cps->which, cps->program, cps->host, message);
6376         RemoveInputSource(cps->isr);
6377         DisplayFatalError(buf1, 0, 1);
6378         return;
6379     }
6380
6381     /*
6382      * Look for hint output
6383      */
6384     if (sscanf(message, "Hint: %s", buf1) == 1) {
6385         if (cps == &first && hintRequested) {
6386             hintRequested = FALSE;
6387             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6388                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6389                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6390                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6391                                     fromY, fromX, toY, toX, promoChar, buf1);
6392                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6393                 DisplayInformation(buf2);
6394             } else {
6395                 /* Hint move could not be parsed!? */
6396               snprintf(buf2, sizeof(buf2),
6397                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6398                         buf1, cps->which);
6399                 DisplayError(buf2, 0);
6400             }
6401         } else {
6402             strcpy(lastHint, buf1);
6403         }
6404         return;
6405     }
6406
6407     /*
6408      * Ignore other messages if game is not in progress
6409      */
6410     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6411         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6412
6413     /*
6414      * look for win, lose, draw, or draw offer
6415      */
6416     if (strncmp(message, "1-0", 3) == 0) {
6417         char *p, *q, *r = "";
6418         p = strchr(message, '{');
6419         if (p) {
6420             q = strchr(p, '}');
6421             if (q) {
6422                 *q = NULLCHAR;
6423                 r = p + 1;
6424             }
6425         }
6426         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6427         return;
6428     } else if (strncmp(message, "0-1", 3) == 0) {
6429         char *p, *q, *r = "";
6430         p = strchr(message, '{');
6431         if (p) {
6432             q = strchr(p, '}');
6433             if (q) {
6434                 *q = NULLCHAR;
6435                 r = p + 1;
6436             }
6437         }
6438         /* Kludge for Arasan 4.1 bug */
6439         if (strcmp(r, "Black resigns") == 0) {
6440             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6441             return;
6442         }
6443         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6444         return;
6445     } else if (strncmp(message, "1/2", 3) == 0) {
6446         char *p, *q, *r = "";
6447         p = strchr(message, '{');
6448         if (p) {
6449             q = strchr(p, '}');
6450             if (q) {
6451                 *q = NULLCHAR;
6452                 r = p + 1;
6453             }
6454         }
6455
6456         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6457         return;
6458
6459     } else if (strncmp(message, "White resign", 12) == 0) {
6460         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6461         return;
6462     } else if (strncmp(message, "Black resign", 12) == 0) {
6463         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6464         return;
6465     } else if (strncmp(message, "White matches", 13) == 0 ||
6466                strncmp(message, "Black matches", 13) == 0   ) {
6467         /* [HGM] ignore GNUShogi noises */
6468         return;
6469     } else if (strncmp(message, "White", 5) == 0 &&
6470                message[5] != '(' &&
6471                StrStr(message, "Black") == NULL) {
6472         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6473         return;
6474     } else if (strncmp(message, "Black", 5) == 0 &&
6475                message[5] != '(') {
6476         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6477         return;
6478     } else if (strcmp(message, "resign") == 0 ||
6479                strcmp(message, "computer resigns") == 0) {
6480         switch (gameMode) {
6481           case MachinePlaysBlack:
6482           case IcsPlayingBlack:
6483             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6484             break;
6485           case MachinePlaysWhite:
6486           case IcsPlayingWhite:
6487             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6488             break;
6489           case TwoMachinesPlay:
6490             if (cps->twoMachinesColor[0] == 'w')
6491               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6492             else
6493               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6494             break;
6495           default:
6496             /* can't happen */
6497             break;
6498         }
6499         return;
6500     } else if (strncmp(message, "opponent mates", 14) == 0) {
6501         switch (gameMode) {
6502           case MachinePlaysBlack:
6503           case IcsPlayingBlack:
6504             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6505             break;
6506           case MachinePlaysWhite:
6507           case IcsPlayingWhite:
6508             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6509             break;
6510           case TwoMachinesPlay:
6511             if (cps->twoMachinesColor[0] == 'w')
6512               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6513             else
6514               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6515             break;
6516           default:
6517             /* can't happen */
6518             break;
6519         }
6520         return;
6521     } else if (strncmp(message, "computer mates", 14) == 0) {
6522         switch (gameMode) {
6523           case MachinePlaysBlack:
6524           case IcsPlayingBlack:
6525             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6526             break;
6527           case MachinePlaysWhite:
6528           case IcsPlayingWhite:
6529             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6530             break;
6531           case TwoMachinesPlay:
6532             if (cps->twoMachinesColor[0] == 'w')
6533               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6534             else
6535               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6536             break;
6537           default:
6538             /* can't happen */
6539             break;
6540         }
6541         return;
6542     } else if (strncmp(message, "checkmate", 9) == 0) {
6543         if (WhiteOnMove(forwardMostMove)) {
6544             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6545         } else {
6546             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6547         }
6548         return;
6549     } else if (strstr(message, "Draw") != NULL ||
6550                strstr(message, "game is a draw") != NULL) {
6551         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6552         return;
6553     } else if (strstr(message, "offer") != NULL &&
6554                strstr(message, "draw") != NULL) {
6555 #if ZIPPY
6556         if (appData.zippyPlay && first.initDone) {
6557             /* Relay offer to ICS */
6558             SendToICS(ics_prefix);
6559             SendToICS("draw\n");
6560         }
6561 #endif
6562         cps->offeredDraw = 2; /* valid until this engine moves twice */
6563         if (gameMode == TwoMachinesPlay) {
6564             if (cps->other->offeredDraw) {
6565                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6566             /* [HGM] in two-machine mode we delay relaying draw offer      */
6567             /* until after we also have move, to see if it is really claim */
6568             }
6569         } else if (gameMode == MachinePlaysWhite ||
6570                    gameMode == MachinePlaysBlack) {
6571           if (userOfferedDraw) {
6572             DisplayInformation(_("Machine accepts your draw offer"));
6573             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6574           } else {
6575             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6576           }
6577         }
6578     }
6579
6580
6581     /*
6582      * Look for thinking output
6583      */
6584     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6585           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6586                                 ) {
6587         int plylev, mvleft, mvtot, curscore, time;
6588         char mvname[MOVE_LEN];
6589         u64 nodes; // [DM]
6590         char plyext;
6591         int ignore = FALSE;
6592         int prefixHint = FALSE;
6593         mvname[0] = NULLCHAR;
6594
6595         switch (gameMode) {
6596           case MachinePlaysBlack:
6597           case IcsPlayingBlack:
6598             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6599             break;
6600           case MachinePlaysWhite:
6601           case IcsPlayingWhite:
6602             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6603             break;
6604           case AnalyzeMode:
6605           case AnalyzeFile:
6606             break;
6607           case IcsObserving: /* [DM] icsEngineAnalyze */
6608             if (!appData.icsEngineAnalyze) ignore = TRUE;
6609             break;
6610           case TwoMachinesPlay:
6611             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6612                 ignore = TRUE;
6613             }
6614             break;
6615           default:
6616             ignore = TRUE;
6617             break;
6618         }
6619
6620         if (!ignore) {
6621             buf1[0] = NULLCHAR;
6622             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6623                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6624
6625                 if (plyext != ' ' && plyext != '\t') {
6626                     time *= 100;
6627                 }
6628
6629                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6630                 if( cps->scoreIsAbsolute &&
6631                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6632                 {
6633                     curscore = -curscore;
6634                 }
6635
6636
6637                 programStats.depth = plylev;
6638                 programStats.nodes = nodes;
6639                 programStats.time = time;
6640                 programStats.score = curscore;
6641                 programStats.got_only_move = 0;
6642
6643                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6644                         int ticklen;
6645
6646                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6647                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6648                         if(WhiteOnMove(forwardMostMove))
6649                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6650                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6651                 }
6652
6653                 /* Buffer overflow protection */
6654                 if (buf1[0] != NULLCHAR) {
6655                     if (strlen(buf1) >= sizeof(programStats.movelist)
6656                         && appData.debugMode) {
6657                         fprintf(debugFP,
6658                                 "PV is too long; using the first %d bytes.\n",
6659                                 sizeof(programStats.movelist) - 1);
6660                     }
6661
6662                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6663                 } else {
6664                     sprintf(programStats.movelist, " no PV\n");
6665                 }
6666
6667                 if (programStats.seen_stat) {
6668                     programStats.ok_to_send = 1;
6669                 }
6670
6671                 if (strchr(programStats.movelist, '(') != NULL) {
6672                     programStats.line_is_book = 1;
6673                     programStats.nr_moves = 0;
6674                     programStats.moves_left = 0;
6675                 } else {
6676                     programStats.line_is_book = 0;
6677                 }
6678
6679                 SendProgramStatsToFrontend( cps, &programStats );
6680
6681                 /*
6682                     [AS] Protect the thinkOutput buffer from overflow... this
6683                     is only useful if buf1 hasn't overflowed first!
6684                 */
6685                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6686                         plylev,
6687                         (gameMode == TwoMachinesPlay ?
6688                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6689                         ((double) curscore) / 100.0,
6690                         prefixHint ? lastHint : "",
6691                         prefixHint ? " " : "" );
6692
6693                 if( buf1[0] != NULLCHAR ) {
6694                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6695
6696                     if( strlen(buf1) > max_len ) {
6697                         if( appData.debugMode) {
6698                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6699                         }
6700                         buf1[max_len+1] = '\0';
6701                     }
6702
6703                     strcat( thinkOutput, buf1 );
6704                 }
6705
6706                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6707                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6708                     DisplayMove(currentMove - 1);
6709                     DisplayAnalysis();
6710                 }
6711                 return;
6712
6713             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6714                 /* crafty (9.25+) says "(only move) <move>"
6715                  * if there is only 1 legal move
6716                  */
6717                 sscanf(p, "(only move) %s", buf1);
6718                 sprintf(thinkOutput, "%s (only move)", buf1);
6719                 sprintf(programStats.movelist, "%s (only move)", buf1);
6720                 programStats.depth = 1;
6721                 programStats.nr_moves = 1;
6722                 programStats.moves_left = 1;
6723                 programStats.nodes = 1;
6724                 programStats.time = 1;
6725                 programStats.got_only_move = 1;
6726
6727                 /* Not really, but we also use this member to
6728                    mean "line isn't going to change" (Crafty
6729                    isn't searching, so stats won't change) */
6730                 programStats.line_is_book = 1;
6731
6732                 SendProgramStatsToFrontend( cps, &programStats );
6733
6734                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6735                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6736                     DisplayMove(currentMove - 1);
6737                     DisplayAnalysis();
6738                 }
6739                 return;
6740             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6741                               &time, &nodes, &plylev, &mvleft,
6742                               &mvtot, mvname) >= 5) {
6743                 /* The stat01: line is from Crafty (9.29+) in response
6744                    to the "." command */
6745                 programStats.seen_stat = 1;
6746                 cps->maybeThinking = TRUE;
6747
6748                 if (programStats.got_only_move || !appData.periodicUpdates)
6749                   return;
6750
6751                 programStats.depth = plylev;
6752                 programStats.time = time;
6753                 programStats.nodes = nodes;
6754                 programStats.moves_left = mvleft;
6755                 programStats.nr_moves = mvtot;
6756                 strcpy(programStats.move_name, mvname);
6757                 programStats.ok_to_send = 1;
6758                 programStats.movelist[0] = '\0';
6759
6760                 SendProgramStatsToFrontend( cps, &programStats );
6761
6762                 DisplayAnalysis();
6763                 return;
6764
6765             } else if (strncmp(message,"++",2) == 0) {
6766                 /* Crafty 9.29+ outputs this */
6767                 programStats.got_fail = 2;
6768                 return;
6769
6770             } else if (strncmp(message,"--",2) == 0) {
6771                 /* Crafty 9.29+ outputs this */
6772                 programStats.got_fail = 1;
6773                 return;
6774
6775             } else if (thinkOutput[0] != NULLCHAR &&
6776                        strncmp(message, "    ", 4) == 0) {
6777                 unsigned message_len;
6778
6779                 p = message;
6780                 while (*p && *p == ' ') p++;
6781
6782                 message_len = strlen( p );
6783
6784                 /* [AS] Avoid buffer overflow */
6785                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6786                     strcat(thinkOutput, " ");
6787                     strcat(thinkOutput, p);
6788                 }
6789
6790                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6791                     strcat(programStats.movelist, " ");
6792                     strcat(programStats.movelist, p);
6793                 }
6794
6795                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6796                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6797                     DisplayMove(currentMove - 1);
6798                     DisplayAnalysis();
6799                 }
6800                 return;
6801             }
6802         }
6803         else {
6804             buf1[0] = NULLCHAR;
6805
6806             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6807                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6808             {
6809                 ChessProgramStats cpstats;
6810
6811                 if (plyext != ' ' && plyext != '\t') {
6812                     time *= 100;
6813                 }
6814
6815                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6816                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6817                     curscore = -curscore;
6818                 }
6819
6820                 cpstats.depth = plylev;
6821                 cpstats.nodes = nodes;
6822                 cpstats.time = time;
6823                 cpstats.score = curscore;
6824                 cpstats.got_only_move = 0;
6825                 cpstats.movelist[0] = '\0';
6826
6827                 if (buf1[0] != NULLCHAR) {
6828                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6829                 }
6830
6831                 cpstats.ok_to_send = 0;
6832                 cpstats.line_is_book = 0;
6833                 cpstats.nr_moves = 0;
6834                 cpstats.moves_left = 0;
6835
6836                 SendProgramStatsToFrontend( cps, &cpstats );
6837             }
6838         }
6839     }
6840 }
6841
6842
6843 /* Parse a game score from the character string "game", and
6844    record it as the history of the current game.  The game
6845    score is NOT assumed to start from the standard position.
6846    The display is not updated in any way.
6847    */
6848 void
6849 ParseGameHistory(game)
6850      char *game;
6851 {
6852     ChessMove moveType;
6853     int fromX, fromY, toX, toY, boardIndex;
6854     char promoChar;
6855     char *p, *q;
6856     char buf[MSG_SIZ];
6857
6858     if (appData.debugMode)
6859       fprintf(debugFP, "Parsing game history: %s\n", game);
6860
6861     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6862     gameInfo.site = StrSave(appData.icsHost);
6863     gameInfo.date = PGNDate();
6864     gameInfo.round = StrSave("-");
6865
6866     /* Parse out names of players */
6867     while (*game == ' ') game++;
6868     p = buf;
6869     while (*game != ' ') *p++ = *game++;
6870     *p = NULLCHAR;
6871     gameInfo.white = StrSave(buf);
6872     while (*game == ' ') game++;
6873     p = buf;
6874     while (*game != ' ' && *game != '\n') *p++ = *game++;
6875     *p = NULLCHAR;
6876     gameInfo.black = StrSave(buf);
6877
6878     /* Parse moves */
6879     boardIndex = blackPlaysFirst ? 1 : 0;
6880     yynewstr(game);
6881     for (;;) {
6882         yyboardindex = boardIndex;
6883         moveType = (ChessMove) yylex();
6884         switch (moveType) {
6885           case IllegalMove:             /* maybe suicide chess, etc. */
6886   if (appData.debugMode) {
6887     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6888     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6889     setbuf(debugFP, NULL);
6890   }
6891           case WhitePromotionChancellor:
6892           case BlackPromotionChancellor:
6893           case WhitePromotionArchbishop:
6894           case BlackPromotionArchbishop:
6895           case WhitePromotionQueen:
6896           case BlackPromotionQueen:
6897           case WhitePromotionRook:
6898           case BlackPromotionRook:
6899           case WhitePromotionBishop:
6900           case BlackPromotionBishop:
6901           case WhitePromotionKnight:
6902           case BlackPromotionKnight:
6903           case WhitePromotionKing:
6904           case BlackPromotionKing:
6905           case NormalMove:
6906           case WhiteCapturesEnPassant:
6907           case BlackCapturesEnPassant:
6908           case WhiteKingSideCastle:
6909           case WhiteQueenSideCastle:
6910           case BlackKingSideCastle:
6911           case BlackQueenSideCastle:
6912           case WhiteKingSideCastleWild:
6913           case WhiteQueenSideCastleWild:
6914           case BlackKingSideCastleWild:
6915           case BlackQueenSideCastleWild:
6916           /* PUSH Fabien */
6917           case WhiteHSideCastleFR:
6918           case WhiteASideCastleFR:
6919           case BlackHSideCastleFR:
6920           case BlackASideCastleFR:
6921           /* POP Fabien */
6922             fromX = currentMoveString[0] - AAA;
6923             fromY = currentMoveString[1] - ONE;
6924             toX = currentMoveString[2] - AAA;
6925             toY = currentMoveString[3] - ONE;
6926             promoChar = currentMoveString[4];
6927             break;
6928           case WhiteDrop:
6929           case BlackDrop:
6930             fromX = moveType == WhiteDrop ?
6931               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6932             (int) CharToPiece(ToLower(currentMoveString[0]));
6933             fromY = DROP_RANK;
6934             toX = currentMoveString[2] - AAA;
6935             toY = currentMoveString[3] - ONE;
6936             promoChar = NULLCHAR;
6937             break;
6938           case AmbiguousMove:
6939             /* bug? */
6940             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6941   if (appData.debugMode) {
6942     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6943     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6944     setbuf(debugFP, NULL);
6945   }
6946             DisplayError(buf, 0);
6947             return;
6948           case ImpossibleMove:
6949             /* bug? */
6950             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6951   if (appData.debugMode) {
6952     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6953     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6954     setbuf(debugFP, NULL);
6955   }
6956             DisplayError(buf, 0);
6957             return;
6958           case (ChessMove) 0:   /* end of file */
6959             if (boardIndex < backwardMostMove) {
6960                 /* Oops, gap.  How did that happen? */
6961                 DisplayError(_("Gap in move list"), 0);
6962                 return;
6963             }
6964             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6965             if (boardIndex > forwardMostMove) {
6966                 forwardMostMove = boardIndex;
6967             }
6968             return;
6969           case ElapsedTime:
6970             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6971                 strcat(parseList[boardIndex-1], " ");
6972                 strcat(parseList[boardIndex-1], yy_text);
6973             }
6974             continue;
6975           case Comment:
6976           case PGNTag:
6977           case NAG:
6978           default:
6979             /* ignore */
6980             continue;
6981           case WhiteWins:
6982           case BlackWins:
6983           case GameIsDrawn:
6984           case GameUnfinished:
6985             if (gameMode == IcsExamining) {
6986                 if (boardIndex < backwardMostMove) {
6987                     /* Oops, gap.  How did that happen? */
6988                     return;
6989                 }
6990                 backwardMostMove = blackPlaysFirst ? 1 : 0;
6991                 return;
6992             }
6993             gameInfo.result = moveType;
6994             p = strchr(yy_text, '{');
6995             if (p == NULL) p = strchr(yy_text, '(');
6996             if (p == NULL) {
6997                 p = yy_text;
6998                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6999             } else {
7000                 q = strchr(p, *p == '{' ? '}' : ')');
7001                 if (q != NULL) *q = NULLCHAR;
7002                 p++;
7003             }
7004             gameInfo.resultDetails = StrSave(p);
7005             continue;
7006         }
7007         if (boardIndex >= forwardMostMove &&
7008             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7009             backwardMostMove = blackPlaysFirst ? 1 : 0;
7010             return;
7011         }
7012         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7013                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7014                                  parseList[boardIndex]);
7015         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7016         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7017         /* currentMoveString is set as a side-effect of yylex */
7018         strcpy(moveList[boardIndex], currentMoveString);
7019         strcat(moveList[boardIndex], "\n");
7020         boardIndex++;
7021         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7022                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7023         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7024                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7025           case MT_NONE:
7026           case MT_STALEMATE:
7027           default:
7028             break;
7029           case MT_CHECK:
7030             if(gameInfo.variant != VariantShogi)
7031                 strcat(parseList[boardIndex - 1], "+");
7032             break;
7033           case MT_CHECKMATE:
7034           case MT_STAINMATE:
7035             strcat(parseList[boardIndex - 1], "#");
7036             break;
7037         }
7038     }
7039 }
7040
7041
7042 /* Apply a move to the given board  */
7043 void
7044 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7045      int fromX, fromY, toX, toY;
7046      int promoChar;
7047      Board board;
7048      char *castling;
7049      char *ep;
7050 {
7051   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7052
7053     /* [HGM] compute & store e.p. status and castling rights for new position */
7054     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7055     { int i;
7056
7057       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7058       oldEP = *ep;
7059       *ep = EP_NONE;
7060
7061       if( board[toY][toX] != EmptySquare )
7062            *ep = EP_CAPTURE;
7063
7064       if( board[fromY][fromX] == WhitePawn ) {
7065            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7066                *ep = EP_PAWN_MOVE;
7067            if( toY-fromY==2) {
7068                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7069                         gameInfo.variant != VariantBerolina || toX < fromX)
7070                       *ep = toX | berolina;
7071                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7072                         gameInfo.variant != VariantBerolina || toX > fromX)
7073                       *ep = toX;
7074            }
7075       } else
7076       if( board[fromY][fromX] == BlackPawn ) {
7077            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7078                *ep = EP_PAWN_MOVE;
7079            if( toY-fromY== -2) {
7080                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7081                         gameInfo.variant != VariantBerolina || toX < fromX)
7082                       *ep = toX | berolina;
7083                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7084                         gameInfo.variant != VariantBerolina || toX > fromX)
7085                       *ep = toX;
7086            }
7087        }
7088
7089        for(i=0; i<nrCastlingRights; i++) {
7090            if(castling[i] == fromX && castlingRank[i] == fromY ||
7091               castling[i] == toX   && castlingRank[i] == toY
7092              ) castling[i] = -1; // revoke for moved or captured piece
7093        }
7094
7095     }
7096
7097   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7098   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7099        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7100
7101   if (fromX == toX && fromY == toY) return;
7102
7103   if (fromY == DROP_RANK) {
7104         /* must be first */
7105         piece = board[toY][toX] = (ChessSquare) fromX;
7106   } else {
7107      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7108      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7109      if(gameInfo.variant == VariantKnightmate)
7110          king += (int) WhiteUnicorn - (int) WhiteKing;
7111
7112     /* Code added by Tord: */
7113     /* FRC castling assumed when king captures friendly rook. */
7114     if (board[fromY][fromX] == WhiteKing &&
7115              board[toY][toX] == WhiteRook) {
7116       board[fromY][fromX] = EmptySquare;
7117       board[toY][toX] = EmptySquare;
7118       if(toX > fromX) {
7119         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7120       } else {
7121         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7122       }
7123     } else if (board[fromY][fromX] == BlackKing &&
7124                board[toY][toX] == BlackRook) {
7125       board[fromY][fromX] = EmptySquare;
7126       board[toY][toX] = EmptySquare;
7127       if(toX > fromX) {
7128         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7129       } else {
7130         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7131       }
7132     /* End of code added by Tord */
7133
7134     } else if (board[fromY][fromX] == king
7135         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7136         && toY == fromY && toX > fromX+1) {
7137         board[fromY][fromX] = EmptySquare;
7138         board[toY][toX] = king;
7139         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7140         board[fromY][BOARD_RGHT-1] = EmptySquare;
7141     } else if (board[fromY][fromX] == king
7142         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7143                && toY == fromY && toX < fromX-1) {
7144         board[fromY][fromX] = EmptySquare;
7145         board[toY][toX] = king;
7146         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7147         board[fromY][BOARD_LEFT] = EmptySquare;
7148     } else if (board[fromY][fromX] == WhitePawn
7149                && toY == BOARD_HEIGHT-1
7150                && gameInfo.variant != VariantXiangqi
7151                ) {
7152         /* white pawn promotion */
7153         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7154         if (board[toY][toX] == EmptySquare) {
7155             board[toY][toX] = WhiteQueen;
7156         }
7157         if(gameInfo.variant==VariantBughouse ||
7158            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7159             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7160         board[fromY][fromX] = EmptySquare;
7161     } else if ((fromY == BOARD_HEIGHT-4)
7162                && (toX != fromX)
7163                && gameInfo.variant != VariantXiangqi
7164                && gameInfo.variant != VariantBerolina
7165                && (board[fromY][fromX] == WhitePawn)
7166                && (board[toY][toX] == EmptySquare)) {
7167         board[fromY][fromX] = EmptySquare;
7168         board[toY][toX] = WhitePawn;
7169         captured = board[toY - 1][toX];
7170         board[toY - 1][toX] = EmptySquare;
7171     } else if ((fromY == BOARD_HEIGHT-4)
7172                && (toX == fromX)
7173                && gameInfo.variant == VariantBerolina
7174                && (board[fromY][fromX] == WhitePawn)
7175                && (board[toY][toX] == EmptySquare)) {
7176         board[fromY][fromX] = EmptySquare;
7177         board[toY][toX] = WhitePawn;
7178         if(oldEP & EP_BEROLIN_A) {
7179                 captured = board[fromY][fromX-1];
7180                 board[fromY][fromX-1] = EmptySquare;
7181         }else{  captured = board[fromY][fromX+1];
7182                 board[fromY][fromX+1] = EmptySquare;
7183         }
7184     } else if (board[fromY][fromX] == king
7185         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7186                && toY == fromY && toX > fromX+1) {
7187         board[fromY][fromX] = EmptySquare;
7188         board[toY][toX] = king;
7189         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7190         board[fromY][BOARD_RGHT-1] = EmptySquare;
7191     } else if (board[fromY][fromX] == king
7192         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7193                && toY == fromY && toX < fromX-1) {
7194         board[fromY][fromX] = EmptySquare;
7195         board[toY][toX] = king;
7196         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7197         board[fromY][BOARD_LEFT] = EmptySquare;
7198     } else if (fromY == 7 && fromX == 3
7199                && board[fromY][fromX] == BlackKing
7200                && toY == 7 && toX == 5) {
7201         board[fromY][fromX] = EmptySquare;
7202         board[toY][toX] = BlackKing;
7203         board[fromY][7] = EmptySquare;
7204         board[toY][4] = BlackRook;
7205     } else if (fromY == 7 && fromX == 3
7206                && board[fromY][fromX] == BlackKing
7207                && toY == 7 && toX == 1) {
7208         board[fromY][fromX] = EmptySquare;
7209         board[toY][toX] = BlackKing;
7210         board[fromY][0] = EmptySquare;
7211         board[toY][2] = BlackRook;
7212     } else if (board[fromY][fromX] == BlackPawn
7213                && toY == 0
7214                && gameInfo.variant != VariantXiangqi
7215                ) {
7216         /* black pawn promotion */
7217         board[0][toX] = CharToPiece(ToLower(promoChar));
7218         if (board[0][toX] == EmptySquare) {
7219             board[0][toX] = BlackQueen;
7220         }
7221         if(gameInfo.variant==VariantBughouse ||
7222            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7223             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7224         board[fromY][fromX] = EmptySquare;
7225     } else if ((fromY == 3)
7226                && (toX != fromX)
7227                && gameInfo.variant != VariantXiangqi
7228                && gameInfo.variant != VariantBerolina
7229                && (board[fromY][fromX] == BlackPawn)
7230                && (board[toY][toX] == EmptySquare)) {
7231         board[fromY][fromX] = EmptySquare;
7232         board[toY][toX] = BlackPawn;
7233         captured = board[toY + 1][toX];
7234         board[toY + 1][toX] = EmptySquare;
7235     } else if ((fromY == 3)
7236                && (toX == fromX)
7237                && gameInfo.variant == VariantBerolina
7238                && (board[fromY][fromX] == BlackPawn)
7239                && (board[toY][toX] == EmptySquare)) {
7240         board[fromY][fromX] = EmptySquare;
7241         board[toY][toX] = BlackPawn;
7242         if(oldEP & EP_BEROLIN_A) {
7243                 captured = board[fromY][fromX-1];
7244                 board[fromY][fromX-1] = EmptySquare;
7245         }else{  captured = board[fromY][fromX+1];
7246                 board[fromY][fromX+1] = EmptySquare;
7247         }
7248     } else {
7249         board[toY][toX] = board[fromY][fromX];
7250         board[fromY][fromX] = EmptySquare;
7251     }
7252
7253     /* [HGM] now we promote for Shogi, if needed */
7254     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7255         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7256   }
7257
7258     if (gameInfo.holdingsWidth != 0) {
7259
7260       /* !!A lot more code needs to be written to support holdings  */
7261       /* [HGM] OK, so I have written it. Holdings are stored in the */
7262       /* penultimate board files, so they are automaticlly stored   */
7263       /* in the game history.                                       */
7264       if (fromY == DROP_RANK) {
7265         /* Delete from holdings, by decreasing count */
7266         /* and erasing image if necessary            */
7267         p = (int) fromX;
7268         if(p < (int) BlackPawn) { /* white drop */
7269              p -= (int)WhitePawn;
7270              if(p >= gameInfo.holdingsSize) p = 0;
7271              if(--board[p][BOARD_WIDTH-2] == 0)
7272                   board[p][BOARD_WIDTH-1] = EmptySquare;
7273         } else {                  /* black drop */
7274              p -= (int)BlackPawn;
7275              if(p >= gameInfo.holdingsSize) p = 0;
7276              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7277                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7278         }
7279       }
7280       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7281           && gameInfo.variant != VariantBughouse        ) {
7282         /* [HGM] holdings: Add to holdings, if holdings exist */
7283         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7284                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7285                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7286         }
7287         p = (int) captured;
7288         if (p >= (int) BlackPawn) {
7289           p -= (int)BlackPawn;
7290           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7291                   /* in Shogi restore piece to its original  first */
7292                   captured = (ChessSquare) (DEMOTED captured);
7293                   p = DEMOTED p;
7294           }
7295           p = PieceToNumber((ChessSquare)p);
7296           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7297           board[p][BOARD_WIDTH-2]++;
7298           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7299         } else {
7300           p -= (int)WhitePawn;
7301           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7302                   captured = (ChessSquare) (DEMOTED captured);
7303                   p = DEMOTED p;
7304           }
7305           p = PieceToNumber((ChessSquare)p);
7306           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7307           board[BOARD_HEIGHT-1-p][1]++;
7308           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7309         }
7310       }
7311
7312     } else if (gameInfo.variant == VariantAtomic) {
7313       if (captured != EmptySquare) {
7314         int y, x;
7315         for (y = toY-1; y <= toY+1; y++) {
7316           for (x = toX-1; x <= toX+1; x++) {
7317             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7318                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7319               board[y][x] = EmptySquare;
7320             }
7321           }
7322         }
7323         board[toY][toX] = EmptySquare;
7324       }
7325     }
7326     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7327         /* [HGM] Shogi promotions */
7328         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7329     }
7330
7331     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7332                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7333         // [HGM] superchess: take promotion piece out of holdings
7334         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7335         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7336             if(!--board[k][BOARD_WIDTH-2])
7337                 board[k][BOARD_WIDTH-1] = EmptySquare;
7338         } else {
7339             if(!--board[BOARD_HEIGHT-1-k][1])
7340                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7341         }
7342     }
7343
7344 }
7345
7346 /* Updates forwardMostMove */
7347 void
7348 MakeMove(fromX, fromY, toX, toY, promoChar)
7349      int fromX, fromY, toX, toY;
7350      int promoChar;
7351 {
7352 //    forwardMostMove++; // [HGM] bare: moved downstream
7353
7354     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7355         int timeLeft; static int lastLoadFlag=0; int king, piece;
7356         piece = boards[forwardMostMove][fromY][fromX];
7357         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7358         if(gameInfo.variant == VariantKnightmate)
7359             king += (int) WhiteUnicorn - (int) WhiteKing;
7360         if(forwardMostMove == 0) {
7361             if(blackPlaysFirst)
7362                 fprintf(serverMoves, "%s;", second.tidy);
7363             fprintf(serverMoves, "%s;", first.tidy);
7364             if(!blackPlaysFirst)
7365                 fprintf(serverMoves, "%s;", second.tidy);
7366         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7367         lastLoadFlag = loadFlag;
7368         // print base move
7369         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7370         // print castling suffix
7371         if( toY == fromY && piece == king ) {
7372             if(toX-fromX > 1)
7373                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7374             if(fromX-toX >1)
7375                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7376         }
7377         // e.p. suffix
7378         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7379              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7380              boards[forwardMostMove][toY][toX] == EmptySquare
7381              && fromX != toX )
7382                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7383         // promotion suffix
7384         if(promoChar != NULLCHAR)
7385                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7386         if(!loadFlag) {
7387             fprintf(serverMoves, "/%d/%d",
7388                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7389             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7390             else                      timeLeft = blackTimeRemaining/1000;
7391             fprintf(serverMoves, "/%d", timeLeft);
7392         }
7393         fflush(serverMoves);
7394     }
7395
7396     if (forwardMostMove+1 >= MAX_MOVES) {
7397       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7398                         0, 1);
7399       return;
7400     }
7401     if (commentList[forwardMostMove+1] != NULL) {
7402         free(commentList[forwardMostMove+1]);
7403         commentList[forwardMostMove+1] = NULL;
7404     }
7405     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7406     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7407     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7408                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7409     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7410     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7411     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7412     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7413     gameInfo.result = GameUnfinished;
7414     if (gameInfo.resultDetails != NULL) {
7415         free(gameInfo.resultDetails);
7416         gameInfo.resultDetails = NULL;
7417     }
7418     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7419                               moveList[forwardMostMove - 1]);
7420     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7421                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7422                              fromY, fromX, toY, toX, promoChar,
7423                              parseList[forwardMostMove - 1]);
7424     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7425                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7426                             castlingRights[forwardMostMove]) ) {
7427       case MT_NONE:
7428       case MT_STALEMATE:
7429       default:
7430         break;
7431       case MT_CHECK:
7432         if(gameInfo.variant != VariantShogi)
7433             strcat(parseList[forwardMostMove - 1], "+");
7434         break;
7435       case MT_CHECKMATE:
7436       case MT_STAINMATE:
7437         strcat(parseList[forwardMostMove - 1], "#");
7438         break;
7439     }
7440     if (appData.debugMode) {
7441         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7442     }
7443
7444 }
7445
7446 /* Updates currentMove if not pausing */
7447 void
7448 ShowMove(fromX, fromY, toX, toY)
7449 {
7450     int instant = (gameMode == PlayFromGameFile) ?
7451         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7452
7453     if(appData.noGUI) return;
7454
7455     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7456       {
7457         if (!instant)
7458           {
7459             if (forwardMostMove == currentMove + 1)
7460               {
7461 //TODO
7462 //              AnimateMove(boards[forwardMostMove - 1],
7463 //                          fromX, fromY, toX, toY);
7464               }
7465             if (appData.highlightLastMove)
7466               {
7467                 SetHighlights(fromX, fromY, toX, toY);
7468               }
7469           }
7470         currentMove = forwardMostMove;
7471     }
7472
7473     if (instant) return;
7474
7475     DisplayMove(currentMove - 1);
7476     DrawPosition(FALSE, boards[currentMove]);
7477     DisplayBothClocks();
7478     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7479
7480     return;
7481 }
7482
7483 void SendEgtPath(ChessProgramState *cps)
7484 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7485         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7486
7487         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7488
7489         while(*p) {
7490             char c, *q = name+1, *r, *s;
7491
7492             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7493             while(*p && *p != ',') *q++ = *p++;
7494             *q++ = ':'; *q = 0;
7495             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7496                 strcmp(name, ",nalimov:") == 0 ) {
7497                 // take nalimov path from the menu-changeable option first, if it is defined
7498                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7499                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7500             } else
7501             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7502                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7503                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7504                 s = r = StrStr(s, ":") + 1; // beginning of path info
7505                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7506                 c = *r; *r = 0;             // temporarily null-terminate path info
7507                     *--q = 0;               // strip of trailig ':' from name
7508                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7509                 *r = c;
7510                 SendToProgram(buf,cps);     // send egtbpath command for this format
7511             }
7512             if(*p == ',') p++; // read away comma to position for next format name
7513         }
7514 }
7515
7516 void
7517 InitChessProgram(cps, setup)
7518      ChessProgramState *cps;
7519      int setup; /* [HGM] needed to setup FRC opening position */
7520 {
7521     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7522     if (appData.noChessProgram) return;
7523     hintRequested = FALSE;
7524     bookRequested = FALSE;
7525
7526     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7527     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7528     if(cps->memSize) { /* [HGM] memory */
7529         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7530         SendToProgram(buf, cps);
7531     }
7532     SendEgtPath(cps); /* [HGM] EGT */
7533     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7534         sprintf(buf, "cores %d\n", appData.smpCores);
7535         SendToProgram(buf, cps);
7536     }
7537
7538     SendToProgram(cps->initString, cps);
7539     if (gameInfo.variant != VariantNormal &&
7540         gameInfo.variant != VariantLoadable
7541         /* [HGM] also send variant if board size non-standard */
7542         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7543                                             ) {
7544       char *v = VariantName(gameInfo.variant);
7545       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7546         /* [HGM] in protocol 1 we have to assume all variants valid */
7547         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7548         DisplayFatalError(buf, 0, 1);
7549         return;
7550       }
7551
7552       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7553       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7554       if( gameInfo.variant == VariantXiangqi )
7555            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7556       if( gameInfo.variant == VariantShogi )
7557            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7558       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7559            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7560       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7561                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7562            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7563       if( gameInfo.variant == VariantCourier )
7564            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7565       if( gameInfo.variant == VariantSuper )
7566            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7567       if( gameInfo.variant == VariantGreat )
7568            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7569
7570       if(overruled) {
7571            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7572                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7573            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7574            if(StrStr(cps->variants, b) == NULL) {
7575                // specific sized variant not known, check if general sizing allowed
7576                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7577                    if(StrStr(cps->variants, "boardsize") == NULL) {
7578                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7579                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7580                        DisplayFatalError(buf, 0, 1);
7581                        return;
7582                    }
7583                    /* [HGM] here we really should compare with the maximum supported board size */
7584                }
7585            }
7586       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7587       sprintf(buf, "variant %s\n", b);
7588       SendToProgram(buf, cps);
7589     }
7590     currentlyInitializedVariant = gameInfo.variant;
7591
7592     /* [HGM] send opening position in FRC to first engine */
7593     if(setup) {
7594           SendToProgram("force\n", cps);
7595           SendBoard(cps, 0);
7596           /* engine is now in force mode! Set flag to wake it up after first move. */
7597           setboardSpoiledMachineBlack = 1;
7598     }
7599
7600     if (cps->sendICS) {
7601       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7602       SendToProgram(buf, cps);
7603     }
7604     cps->maybeThinking = FALSE;
7605     cps->offeredDraw = 0;
7606     if (!appData.icsActive) {
7607         SendTimeControl(cps, movesPerSession, timeControl,
7608                         timeIncrement, appData.searchDepth,
7609                         searchTime);
7610     }
7611     if (appData.showThinking
7612         // [HGM] thinking: four options require thinking output to be sent
7613         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7614                                 ) {
7615         SendToProgram("post\n", cps);
7616     }
7617     SendToProgram("hard\n", cps);
7618     if (!appData.ponderNextMove) {
7619         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7620            it without being sure what state we are in first.  "hard"
7621            is not a toggle, so that one is OK.
7622          */
7623         SendToProgram("easy\n", cps);
7624     }
7625     if (cps->usePing) {
7626       sprintf(buf, "ping %d\n", ++cps->lastPing);
7627       SendToProgram(buf, cps);
7628     }
7629     cps->initDone = TRUE;
7630 }
7631
7632
7633 void
7634 StartChessProgram(cps)
7635      ChessProgramState *cps;
7636 {
7637     char buf[MSG_SIZ];
7638     int err;
7639
7640     if (appData.noChessProgram) return;
7641     cps->initDone = FALSE;
7642
7643     if (strcmp(cps->host, "localhost") == 0) {
7644         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7645     } else if (*appData.remoteShell == NULLCHAR) {
7646         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7647     } else {
7648         if (*appData.remoteUser == NULLCHAR) {
7649           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7650                     cps->program);
7651         } else {
7652           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7653                     cps->host, appData.remoteUser, cps->program);
7654         }
7655         err = StartChildProcess(buf, "", &cps->pr);
7656     }
7657
7658     if (err != 0) {
7659         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7660         DisplayFatalError(buf, err, 1);
7661         cps->pr = NoProc;
7662         cps->isr = NULL;
7663         return;
7664     }
7665
7666     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7667     if (cps->protocolVersion > 1) {
7668       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7669       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7670       cps->comboCnt = 0;  //                and values of combo boxes
7671       SendToProgram(buf, cps);
7672     } else {
7673       SendToProgram("xboard\n", cps);
7674     }
7675 }
7676
7677
7678 void
7679 TwoMachinesEventIfReady P((void))
7680 {
7681   if (first.lastPing != first.lastPong) {
7682     DisplayMessage("", _("Waiting for first chess program"));
7683     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7684     return;
7685   }
7686   if (second.lastPing != second.lastPong) {
7687     DisplayMessage("", _("Waiting for second chess program"));
7688     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7689     return;
7690   }
7691   ThawUI();
7692   TwoMachinesEvent();
7693 }
7694
7695 void
7696 NextMatchGame P((void))
7697 {
7698     int index; /* [HGM] autoinc: step lod index during match */
7699     Reset(FALSE, TRUE);
7700     if (*appData.loadGameFile != NULLCHAR) {
7701         index = appData.loadGameIndex;
7702         if(index < 0) { // [HGM] autoinc
7703             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7704             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7705         }
7706         LoadGameFromFile(appData.loadGameFile,
7707                          index,
7708                          appData.loadGameFile, FALSE);
7709     } else if (*appData.loadPositionFile != NULLCHAR) {
7710         index = appData.loadPositionIndex;
7711         if(index < 0) { // [HGM] autoinc
7712             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7713             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7714         }
7715         LoadPositionFromFile(appData.loadPositionFile,
7716                              index,
7717                              appData.loadPositionFile);
7718     }
7719     TwoMachinesEventIfReady();
7720 }
7721
7722 void UserAdjudicationEvent( int result )
7723 {
7724     ChessMove gameResult = GameIsDrawn;
7725
7726     if( result > 0 ) {
7727         gameResult = WhiteWins;
7728     }
7729     else if( result < 0 ) {
7730         gameResult = BlackWins;
7731     }
7732
7733     if( gameMode == TwoMachinesPlay ) {
7734         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7735     }
7736 }
7737
7738
7739 // [HGM] save: calculate checksum of game to make games easily identifiable
7740 int StringCheckSum(char *s)
7741 {
7742         int i = 0;
7743         if(s==NULL) return 0;
7744         while(*s) i = i*259 + *s++;
7745         return i;
7746 }
7747
7748 int GameCheckSum()
7749 {
7750         int i, sum=0;
7751         for(i=backwardMostMove; i<forwardMostMove; i++) {
7752                 sum += pvInfoList[i].depth;
7753                 sum += StringCheckSum(parseList[i]);
7754                 sum += StringCheckSum(commentList[i]);
7755                 sum *= 261;
7756         }
7757         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7758         return sum + StringCheckSum(commentList[i]);
7759 } // end of save patch
7760
7761 void
7762 GameEnds(result, resultDetails, whosays)
7763      ChessMove result;
7764      char *resultDetails;
7765      int whosays;
7766 {
7767     GameMode nextGameMode;
7768     int isIcsGame;
7769     char buf[MSG_SIZ];
7770
7771     if(endingGame) return; /* [HGM] crash: forbid recursion */
7772     endingGame = 1;
7773
7774     if (appData.debugMode) {
7775       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7776               result, resultDetails ? resultDetails : "(null)", whosays);
7777     }
7778
7779     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7780         /* If we are playing on ICS, the server decides when the
7781            game is over, but the engine can offer to draw, claim
7782            a draw, or resign.
7783          */
7784 #if ZIPPY
7785         if (appData.zippyPlay && first.initDone) {
7786             if (result == GameIsDrawn) {
7787                 /* In case draw still needs to be claimed */
7788                 SendToICS(ics_prefix);
7789                 SendToICS("draw\n");
7790             } else if (StrCaseStr(resultDetails, "resign")) {
7791                 SendToICS(ics_prefix);
7792                 SendToICS("resign\n");
7793             }
7794         }
7795 #endif
7796         endingGame = 0; /* [HGM] crash */
7797         return;
7798     }
7799
7800     /* If we're loading the game from a file, stop */
7801     if (whosays == GE_FILE) {
7802       (void) StopLoadGameTimer();
7803       gameFileFP = NULL;
7804     }
7805
7806     /* Cancel draw offers */
7807     first.offeredDraw = second.offeredDraw = 0;
7808
7809     /* If this is an ICS game, only ICS can really say it's done;
7810        if not, anyone can. */
7811     isIcsGame = (gameMode == IcsPlayingWhite ||
7812                  gameMode == IcsPlayingBlack ||
7813                  gameMode == IcsObserving    ||
7814                  gameMode == IcsExamining);
7815
7816     if (!isIcsGame || whosays == GE_ICS) {
7817         /* OK -- not an ICS game, or ICS said it was done */
7818         StopClocks();
7819         if (!isIcsGame && !appData.noChessProgram)
7820           SetUserThinkingEnables();
7821
7822         /* [HGM] if a machine claims the game end we verify this claim */
7823         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7824             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7825                 char claimer;
7826                 ChessMove trueResult = (ChessMove) -1;
7827
7828                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7829                                             first.twoMachinesColor[0] :
7830                                             second.twoMachinesColor[0] ;
7831
7832                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7833                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7834                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7835                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7836                 } else
7837                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7838                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7839                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7840                 } else
7841                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7842                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7843                 }
7844
7845                 // now verify win claims, but not in drop games, as we don't understand those yet
7846                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7847                                                  || gameInfo.variant == VariantGreat) &&
7848                     (result == WhiteWins && claimer == 'w' ||
7849                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7850                       if (appData.debugMode) {
7851                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7852                                 result, epStatus[forwardMostMove], forwardMostMove);
7853                       }
7854                       if(result != trueResult) {
7855                               sprintf(buf, "False win claim: '%s'", resultDetails);
7856                               result = claimer == 'w' ? BlackWins : WhiteWins;
7857                               resultDetails = buf;
7858                       }
7859                 } else
7860                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7861                     && (forwardMostMove <= backwardMostMove ||
7862                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7863                         (claimer=='b')==(forwardMostMove&1))
7864                                                                                   ) {
7865                       /* [HGM] verify: draws that were not flagged are false claims */
7866                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7867                       result = claimer == 'w' ? BlackWins : WhiteWins;
7868                       resultDetails = buf;
7869                 }
7870                 /* (Claiming a loss is accepted no questions asked!) */
7871             }
7872
7873             /* [HGM] bare: don't allow bare King to win */
7874             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7875                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7876                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7877                && result != GameIsDrawn)
7878             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7879                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7880                         int p = (int)boards[forwardMostMove][i][j] - color;
7881                         if(p >= 0 && p <= (int)WhiteKing) k++;
7882                 }
7883                 if (appData.debugMode) {
7884                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7885                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7886                 }
7887                 if(k <= 1) {
7888                         result = GameIsDrawn;
7889                         sprintf(buf, "%s but bare king", resultDetails);
7890                         resultDetails = buf;
7891                 }
7892             }
7893         }
7894
7895         if(serverMoves != NULL && !loadFlag) { char c = '=';
7896             if(result==WhiteWins) c = '+';
7897             if(result==BlackWins) c = '-';
7898             if(resultDetails != NULL)
7899                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7900         }
7901         if (resultDetails != NULL) {
7902             gameInfo.result = result;
7903             gameInfo.resultDetails = StrSave(resultDetails);
7904
7905             /* display last move only if game was not loaded from file */
7906             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7907                 DisplayMove(currentMove - 1);
7908
7909             if (forwardMostMove != 0) {
7910                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7911                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7912                                                                 ) {
7913                     if (*appData.saveGameFile != NULLCHAR) {
7914                         SaveGameToFile(appData.saveGameFile, TRUE);
7915                     } else if (appData.autoSaveGames) {
7916                         AutoSaveGame();
7917                     }
7918                     if (*appData.savePositionFile != NULLCHAR) {
7919                         SavePositionToFile(appData.savePositionFile);
7920                     }
7921                 }
7922             }
7923
7924             /* Tell program how game ended in case it is learning */
7925             /* [HGM] Moved this to after saving the PGN, just in case */
7926             /* engine died and we got here through time loss. In that */
7927             /* case we will get a fatal error writing the pipe, which */
7928             /* would otherwise lose us the PGN.                       */
7929             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7930             /* output during GameEnds should never be fatal anymore   */
7931             if (gameMode == MachinePlaysWhite ||
7932                 gameMode == MachinePlaysBlack ||
7933                 gameMode == TwoMachinesPlay ||
7934                 gameMode == IcsPlayingWhite ||
7935                 gameMode == IcsPlayingBlack ||
7936                 gameMode == BeginningOfGame) {
7937                 char buf[MSG_SIZ];
7938                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7939                         resultDetails);
7940                 if (first.pr != NoProc) {
7941                     SendToProgram(buf, &first);
7942                 }
7943                 if (second.pr != NoProc &&
7944                     gameMode == TwoMachinesPlay) {
7945                     SendToProgram(buf, &second);
7946                 }
7947             }
7948         }
7949
7950         if (appData.icsActive) {
7951             if (appData.quietPlay &&
7952                 (gameMode == IcsPlayingWhite ||
7953                  gameMode == IcsPlayingBlack)) {
7954                 SendToICS(ics_prefix);
7955                 SendToICS("set shout 1\n");
7956             }
7957             nextGameMode = IcsIdle;
7958             ics_user_moved = FALSE;
7959             /* clean up premove.  It's ugly when the game has ended and the
7960              * premove highlights are still on the board.
7961              */
7962             if (gotPremove) {
7963               gotPremove = FALSE;
7964               ClearPremoveHighlights();
7965               DrawPosition(FALSE, boards[currentMove]);
7966             }
7967             if (whosays == GE_ICS) {
7968                 switch (result) {
7969                 case WhiteWins:
7970                     if (gameMode == IcsPlayingWhite)
7971                         PlayIcsWinSound();
7972                     else if(gameMode == IcsPlayingBlack)
7973                         PlayIcsLossSound();
7974                     break;
7975                 case BlackWins:
7976                     if (gameMode == IcsPlayingBlack)
7977                         PlayIcsWinSound();
7978                     else if(gameMode == IcsPlayingWhite)
7979                         PlayIcsLossSound();
7980                     break;
7981                 case GameIsDrawn:
7982                     PlayIcsDrawSound();
7983                     break;
7984                 default:
7985                     PlayIcsUnfinishedSound();
7986                 }
7987             }
7988         } else if (gameMode == EditGame ||
7989                    gameMode == PlayFromGameFile ||
7990                    gameMode == AnalyzeMode ||
7991                    gameMode == AnalyzeFile) {
7992             nextGameMode = gameMode;
7993         } else {
7994             nextGameMode = EndOfGame;
7995         }
7996         pausing = FALSE;
7997         ModeHighlight();
7998     } else {
7999         nextGameMode = gameMode;
8000     }
8001
8002     if (appData.noChessProgram) {
8003         gameMode = nextGameMode;
8004         ModeHighlight();
8005         endingGame = 0; /* [HGM] crash */
8006         return;
8007     }
8008
8009     if (first.reuse) {
8010         /* Put first chess program into idle state */
8011         if (first.pr != NoProc &&
8012             (gameMode == MachinePlaysWhite ||
8013              gameMode == MachinePlaysBlack ||
8014              gameMode == TwoMachinesPlay ||
8015              gameMode == IcsPlayingWhite ||
8016              gameMode == IcsPlayingBlack ||
8017              gameMode == BeginningOfGame)) {
8018             SendToProgram("force\n", &first);
8019             if (first.usePing) {
8020               char buf[MSG_SIZ];
8021               sprintf(buf, "ping %d\n", ++first.lastPing);
8022               SendToProgram(buf, &first);
8023             }
8024         }
8025     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8026         /* Kill off first chess program */
8027         if (first.isr != NULL)
8028           RemoveInputSource(first.isr);
8029         first.isr = NULL;
8030
8031         if (first.pr != NoProc) {
8032             ExitAnalyzeMode();
8033             DoSleep( appData.delayBeforeQuit );
8034             SendToProgram("quit\n", &first);
8035             DoSleep( appData.delayAfterQuit );
8036             DestroyChildProcess(first.pr, first.useSigterm);
8037         }
8038         first.pr = NoProc;
8039     }
8040     if (second.reuse) {
8041         /* Put second chess program into idle state */
8042         if (second.pr != NoProc &&
8043             gameMode == TwoMachinesPlay) {
8044             SendToProgram("force\n", &second);
8045             if (second.usePing) {
8046               char buf[MSG_SIZ];
8047               sprintf(buf, "ping %d\n", ++second.lastPing);
8048               SendToProgram(buf, &second);
8049             }
8050         }
8051     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8052         /* Kill off second chess program */
8053         if (second.isr != NULL)
8054           RemoveInputSource(second.isr);
8055         second.isr = NULL;
8056
8057         if (second.pr != NoProc) {
8058             DoSleep( appData.delayBeforeQuit );
8059             SendToProgram("quit\n", &second);
8060             DoSleep( appData.delayAfterQuit );
8061             DestroyChildProcess(second.pr, second.useSigterm);
8062         }
8063         second.pr = NoProc;
8064     }
8065
8066     if (matchMode && gameMode == TwoMachinesPlay) {
8067         switch (result) {
8068         case WhiteWins:
8069           if (first.twoMachinesColor[0] == 'w') {
8070             first.matchWins++;
8071           } else {
8072             second.matchWins++;
8073           }
8074           break;
8075         case BlackWins:
8076           if (first.twoMachinesColor[0] == 'b') {
8077             first.matchWins++;
8078           } else {
8079             second.matchWins++;
8080           }
8081           break;
8082         default:
8083           break;
8084         }
8085         if (matchGame < appData.matchGames) {
8086             char *tmp;
8087             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8088                 tmp = first.twoMachinesColor;
8089                 first.twoMachinesColor = second.twoMachinesColor;
8090                 second.twoMachinesColor = tmp;
8091             }
8092             gameMode = nextGameMode;
8093             matchGame++;
8094             if(appData.matchPause>10000 || appData.matchPause<10)
8095                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8096             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8097             endingGame = 0; /* [HGM] crash */
8098             return;
8099         } else {
8100             char buf[MSG_SIZ];
8101             gameMode = nextGameMode;
8102             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8103                     first.tidy, second.tidy,
8104                     first.matchWins, second.matchWins,
8105                     appData.matchGames - (first.matchWins + second.matchWins));
8106             DisplayFatalError(buf, 0, 0);
8107         }
8108     }
8109     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8110         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8111       ExitAnalyzeMode();
8112     gameMode = nextGameMode;
8113     ModeHighlight();
8114     endingGame = 0;  /* [HGM] crash */
8115 }
8116
8117 /* Assumes program was just initialized (initString sent).
8118    Leaves program in force mode. */
8119 void
8120 FeedMovesToProgram(cps, upto)
8121      ChessProgramState *cps;
8122      int upto;
8123 {
8124     int i;
8125
8126     if (appData.debugMode)
8127       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8128               startedFromSetupPosition ? "position and " : "",
8129               backwardMostMove, upto, cps->which);
8130     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8131         // [HGM] variantswitch: make engine aware of new variant
8132         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8133                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8134         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8135         SendToProgram(buf, cps);
8136         currentlyInitializedVariant = gameInfo.variant;
8137     }
8138     SendToProgram("force\n", cps);
8139     if (startedFromSetupPosition) {
8140         SendBoard(cps, backwardMostMove);
8141     if (appData.debugMode) {
8142         fprintf(debugFP, "feedMoves\n");
8143     }
8144     }
8145     for (i = backwardMostMove; i < upto; i++) {
8146         SendMoveToProgram(i, cps);
8147     }
8148 }
8149
8150
8151 void
8152 ResurrectChessProgram()
8153 {
8154      /* The chess program may have exited.
8155         If so, restart it and feed it all the moves made so far. */
8156
8157     if (appData.noChessProgram || first.pr != NoProc) return;
8158
8159     StartChessProgram(&first);
8160     InitChessProgram(&first, FALSE);
8161     FeedMovesToProgram(&first, currentMove);
8162
8163     if (!first.sendTime) {
8164         /* can't tell gnuchess what its clock should read,
8165            so we bow to its notion. */
8166         ResetClocks();
8167         timeRemaining[0][currentMove] = whiteTimeRemaining;
8168         timeRemaining[1][currentMove] = blackTimeRemaining;
8169     }
8170
8171     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8172                 appData.icsEngineAnalyze) && first.analysisSupport) {
8173       SendToProgram("analyze\n", &first);
8174       first.analyzing = TRUE;
8175     }
8176 }
8177
8178 /*
8179  * Button procedures
8180  */
8181 void
8182 Reset(redraw, init)
8183      int redraw, init;
8184 {
8185     int i;
8186
8187     if (appData.debugMode) {
8188         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8189                 redraw, init, gameMode);
8190     }
8191     pausing = pauseExamInvalid = FALSE;
8192     startedFromSetupPosition = blackPlaysFirst = FALSE;
8193     firstMove = TRUE;
8194     whiteFlag = blackFlag = FALSE;
8195     userOfferedDraw = FALSE;
8196     hintRequested = bookRequested = FALSE;
8197     first.maybeThinking = FALSE;
8198     second.maybeThinking = FALSE;
8199     first.bookSuspend = FALSE; // [HGM] book
8200     second.bookSuspend = FALSE;
8201     thinkOutput[0] = NULLCHAR;
8202     lastHint[0] = NULLCHAR;
8203     ClearGameInfo(&gameInfo);
8204     gameInfo.variant = StringToVariant(appData.variant);
8205     ics_user_moved = ics_clock_paused = FALSE;
8206     ics_getting_history = H_FALSE;
8207     ics_gamenum = -1;
8208     white_holding[0] = black_holding[0] = NULLCHAR;
8209     ClearProgramStats();
8210     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8211
8212     ResetFrontEnd();
8213     ClearHighlights();
8214     flipView = appData.flipView;
8215     ClearPremoveHighlights();
8216     gotPremove = FALSE;
8217     alarmSounded = FALSE;
8218
8219     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8220     if(appData.serverMovesName != NULL) {
8221         /* [HGM] prepare to make moves file for broadcasting */
8222         clock_t t = clock();
8223         if(serverMoves != NULL) fclose(serverMoves);
8224         serverMoves = fopen(appData.serverMovesName, "r");
8225         if(serverMoves != NULL) {
8226             fclose(serverMoves);
8227             /* delay 15 sec before overwriting, so all clients can see end */
8228             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8229         }
8230         serverMoves = fopen(appData.serverMovesName, "w");
8231     }
8232
8233     ExitAnalyzeMode();
8234     gameMode = BeginningOfGame;
8235     ModeHighlight();
8236
8237     if(appData.icsActive) gameInfo.variant = VariantNormal;
8238     currentMove = forwardMostMove = backwardMostMove = 0;
8239     InitPosition(redraw);
8240     for (i = 0; i < MAX_MOVES; i++) {
8241         if (commentList[i] != NULL) {
8242             free(commentList[i]);
8243             commentList[i] = NULL;
8244         }
8245     }
8246
8247     ResetClocks();
8248     timeRemaining[0][0] = whiteTimeRemaining;
8249     timeRemaining[1][0] = blackTimeRemaining;
8250     if (first.pr == NULL) {
8251         StartChessProgram(&first);
8252     }
8253     if (init) {
8254             InitChessProgram(&first, startedFromSetupPosition);
8255     }
8256
8257     DisplayTitle("");
8258     DisplayMessage("", "");
8259     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8260     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8261     return;
8262 }
8263
8264 void
8265 AutoPlayGameLoop()
8266 {
8267     for (;;) {
8268         if (!AutoPlayOneMove())
8269           return;
8270         if (matchMode || appData.timeDelay == 0)
8271           continue;
8272         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8273           return;
8274         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8275         break;
8276     }
8277 }
8278
8279
8280 int
8281 AutoPlayOneMove()
8282 {
8283     int fromX, fromY, toX, toY;
8284
8285     if (appData.debugMode) {
8286       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8287     }
8288
8289     if (gameMode != PlayFromGameFile)
8290       return FALSE;
8291
8292     if (currentMove >= forwardMostMove) {
8293       gameMode = EditGame;
8294       ModeHighlight();
8295
8296       /* [AS] Clear current move marker at the end of a game */
8297       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8298
8299       return FALSE;
8300     }
8301
8302     toX = moveList[currentMove][2] - AAA;
8303     toY = moveList[currentMove][3] - ONE;
8304
8305     if (moveList[currentMove][1] == '@') {
8306         if (appData.highlightLastMove) {
8307             SetHighlights(-1, -1, toX, toY);
8308         }
8309     } else {
8310         fromX = moveList[currentMove][0] - AAA;
8311         fromY = moveList[currentMove][1] - ONE;
8312
8313         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8314
8315         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8316
8317         if (appData.highlightLastMove) {
8318             SetHighlights(fromX, fromY, toX, toY);
8319         }
8320     }
8321     DisplayMove(currentMove);
8322     SendMoveToProgram(currentMove++, &first);
8323     DisplayBothClocks();
8324     DrawPosition(FALSE, boards[currentMove]);
8325     // [HGM] PV info: always display, routine tests if empty
8326     DisplayComment(currentMove - 1, commentList[currentMove]);
8327     return TRUE;
8328 }
8329
8330
8331 int
8332 LoadGameOneMove(readAhead)
8333      ChessMove readAhead;
8334 {
8335     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8336     char promoChar = NULLCHAR;
8337     ChessMove moveType;
8338     char move[MSG_SIZ];
8339     char *p, *q;
8340
8341     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8342         gameMode != AnalyzeMode && gameMode != Training) {
8343         gameFileFP = NULL;
8344         return FALSE;
8345     }
8346
8347     yyboardindex = forwardMostMove;
8348     if (readAhead != (ChessMove)0) {
8349       moveType = readAhead;
8350     } else {
8351       if (gameFileFP == NULL)
8352           return FALSE;
8353       moveType = (ChessMove) yylex();
8354     }
8355
8356     done = FALSE;
8357     switch (moveType) {
8358       case Comment:
8359         if (appData.debugMode)
8360           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8361         p = yy_text;
8362         if (*p == '{' || *p == '[' || *p == '(') {
8363             p[strlen(p) - 1] = NULLCHAR;
8364             p++;
8365         }
8366
8367         /* append the comment but don't display it */
8368         while (*p == '\n') p++;
8369         AppendComment(currentMove, p);
8370         return TRUE;
8371
8372       case WhiteCapturesEnPassant:
8373       case BlackCapturesEnPassant:
8374       case WhitePromotionChancellor:
8375       case BlackPromotionChancellor:
8376       case WhitePromotionArchbishop:
8377       case BlackPromotionArchbishop:
8378       case WhitePromotionCentaur:
8379       case BlackPromotionCentaur:
8380       case WhitePromotionQueen:
8381       case BlackPromotionQueen:
8382       case WhitePromotionRook:
8383       case BlackPromotionRook:
8384       case WhitePromotionBishop:
8385       case BlackPromotionBishop:
8386       case WhitePromotionKnight:
8387       case BlackPromotionKnight:
8388       case WhitePromotionKing:
8389       case BlackPromotionKing:
8390       case NormalMove:
8391       case WhiteKingSideCastle:
8392       case WhiteQueenSideCastle:
8393       case BlackKingSideCastle:
8394       case BlackQueenSideCastle:
8395       case WhiteKingSideCastleWild:
8396       case WhiteQueenSideCastleWild:
8397       case BlackKingSideCastleWild:
8398       case BlackQueenSideCastleWild:
8399       /* PUSH Fabien */
8400       case WhiteHSideCastleFR:
8401       case WhiteASideCastleFR:
8402       case BlackHSideCastleFR:
8403       case BlackASideCastleFR:
8404       /* POP Fabien */
8405         if (appData.debugMode)
8406           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8407         fromX = currentMoveString[0] - AAA;
8408         fromY = currentMoveString[1] - ONE;
8409         toX = currentMoveString[2] - AAA;
8410         toY = currentMoveString[3] - ONE;
8411         promoChar = currentMoveString[4];
8412         break;
8413
8414       case WhiteDrop:
8415       case BlackDrop:
8416         if (appData.debugMode)
8417           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8418         fromX = moveType == WhiteDrop ?
8419           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8420         (int) CharToPiece(ToLower(currentMoveString[0]));
8421         fromY = DROP_RANK;
8422         toX = currentMoveString[2] - AAA;
8423         toY = currentMoveString[3] - ONE;
8424         break;
8425
8426       case WhiteWins:
8427       case BlackWins:
8428       case GameIsDrawn:
8429       case GameUnfinished:
8430         if (appData.debugMode)
8431           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8432         p = strchr(yy_text, '{');
8433         if (p == NULL) p = strchr(yy_text, '(');
8434         if (p == NULL) {
8435             p = yy_text;
8436             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8437         } else {
8438             q = strchr(p, *p == '{' ? '}' : ')');
8439             if (q != NULL) *q = NULLCHAR;
8440             p++;
8441         }
8442         GameEnds(moveType, p, GE_FILE);
8443         done = TRUE;
8444         if (cmailMsgLoaded) {
8445             ClearHighlights();
8446             flipView = WhiteOnMove(currentMove);
8447             if (moveType == GameUnfinished) flipView = !flipView;
8448             if (appData.debugMode)
8449               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8450         }
8451         break;
8452
8453       case (ChessMove) 0:       /* end of file */
8454         if (appData.debugMode)
8455           fprintf(debugFP, "Parser hit end of file\n");
8456         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8457                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8458           case MT_NONE:
8459           case MT_CHECK:
8460             break;
8461           case MT_CHECKMATE:
8462           case MT_STAINMATE:
8463             if (WhiteOnMove(currentMove)) {
8464                 GameEnds(BlackWins, "Black mates", GE_FILE);
8465             } else {
8466                 GameEnds(WhiteWins, "White mates", GE_FILE);
8467             }
8468             break;
8469           case MT_STALEMATE:
8470             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8471             break;
8472         }
8473         done = TRUE;
8474         break;
8475
8476       case MoveNumberOne:
8477         if (lastLoadGameStart == GNUChessGame) {
8478             /* GNUChessGames have numbers, but they aren't move numbers */
8479             if (appData.debugMode)
8480               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8481                       yy_text, (int) moveType);
8482             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8483         }
8484         /* else fall thru */
8485
8486       case XBoardGame:
8487       case GNUChessGame:
8488       case PGNTag:
8489         /* Reached start of next game in file */
8490         if (appData.debugMode)
8491           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8492         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8493                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8494           case MT_NONE:
8495           case MT_CHECK:
8496             break;
8497           case MT_CHECKMATE:
8498           case MT_STAINMATE:
8499             if (WhiteOnMove(currentMove)) {
8500                 GameEnds(BlackWins, "Black mates", GE_FILE);
8501             } else {
8502                 GameEnds(WhiteWins, "White mates", GE_FILE);
8503             }
8504             break;
8505           case MT_STALEMATE:
8506             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8507             break;
8508         }
8509         done = TRUE;
8510         break;
8511
8512       case PositionDiagram:     /* should not happen; ignore */
8513       case ElapsedTime:         /* ignore */
8514       case NAG:                 /* ignore */
8515         if (appData.debugMode)
8516           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8517                   yy_text, (int) moveType);
8518         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8519
8520       case IllegalMove:
8521         if (appData.testLegality) {
8522             if (appData.debugMode)
8523               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8524             sprintf(move, _("Illegal move: %d.%s%s"),
8525                     (forwardMostMove / 2) + 1,
8526                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8527             DisplayError(move, 0);
8528             done = TRUE;
8529         } else {
8530             if (appData.debugMode)
8531               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8532                       yy_text, currentMoveString);
8533             fromX = currentMoveString[0] - AAA;
8534             fromY = currentMoveString[1] - ONE;
8535             toX = currentMoveString[2] - AAA;
8536             toY = currentMoveString[3] - ONE;
8537             promoChar = currentMoveString[4];
8538         }
8539         break;
8540
8541       case AmbiguousMove:
8542         if (appData.debugMode)
8543           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8544         sprintf(move, _("Ambiguous move: %d.%s%s"),
8545                 (forwardMostMove / 2) + 1,
8546                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8547         DisplayError(move, 0);
8548         done = TRUE;
8549         break;
8550
8551       default:
8552       case ImpossibleMove:
8553         if (appData.debugMode)
8554           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8555         sprintf(move, _("Illegal move: %d.%s%s"),
8556                 (forwardMostMove / 2) + 1,
8557                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8558         DisplayError(move, 0);
8559         done = TRUE;
8560         break;
8561     }
8562
8563     if (done) {
8564         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8565             DrawPosition(FALSE, boards[currentMove]);
8566             DisplayBothClocks();
8567             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8568               DisplayComment(currentMove - 1, commentList[currentMove]);
8569         }
8570         (void) StopLoadGameTimer();
8571         gameFileFP = NULL;
8572         cmailOldMove = forwardMostMove;
8573         return FALSE;
8574     } else {
8575         /* currentMoveString is set as a side-effect of yylex */
8576         strcat(currentMoveString, "\n");
8577         strcpy(moveList[forwardMostMove], currentMoveString);
8578
8579         thinkOutput[0] = NULLCHAR;
8580         MakeMove(fromX, fromY, toX, toY, promoChar);
8581         currentMove = forwardMostMove;
8582         return TRUE;
8583     }
8584 }
8585
8586 /* Load the nth game from the given file */
8587 int
8588 LoadGameFromFile(filename, n, title, useList)
8589      char *filename;
8590      int n;
8591      char *title;
8592      /*Boolean*/ int useList;
8593 {
8594     FILE *f;
8595     char buf[MSG_SIZ];
8596
8597     if (strcmp(filename, "-") == 0) {
8598         f = stdin;
8599         title = "stdin";
8600     } else {
8601         f = fopen(filename, "rb");
8602         if (f == NULL) {
8603           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8604             DisplayError(buf, errno);
8605             return FALSE;
8606         }
8607     }
8608     if (fseek(f, 0, 0) == -1) {
8609         /* f is not seekable; probably a pipe */
8610         useList = FALSE;
8611     }
8612     if (useList && n == 0) {
8613         int error = GameListBuild(f);
8614         if (error) {
8615             DisplayError(_("Cannot build game list"), error);
8616         } else if (!ListEmpty(&gameList) &&
8617                    ((ListGame *) gameList.tailPred)->number > 1) {
8618             GameListPopUp(f, title);
8619             return TRUE;
8620         }
8621         GameListDestroy();
8622         n = 1;
8623     }
8624     if (n == 0) n = 1;
8625     return LoadGame(f, n, title, FALSE);
8626 }
8627
8628
8629 void
8630 MakeRegisteredMove()
8631 {
8632     int fromX, fromY, toX, toY;
8633     char promoChar;
8634     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8635         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8636           case CMAIL_MOVE:
8637           case CMAIL_DRAW:
8638             if (appData.debugMode)
8639               fprintf(debugFP, "Restoring %s for game %d\n",
8640                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8641
8642             thinkOutput[0] = NULLCHAR;
8643             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8644             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8645             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8646             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8647             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8648             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8649             MakeMove(fromX, fromY, toX, toY, promoChar);
8650             ShowMove(fromX, fromY, toX, toY);
8651
8652             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8653                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8654               case MT_NONE:
8655               case MT_CHECK:
8656                 break;
8657
8658               case MT_CHECKMATE:
8659               case MT_STAINMATE:
8660                 if (WhiteOnMove(currentMove)) {
8661                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8662                 } else {
8663                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8664                 }
8665                 break;
8666
8667               case MT_STALEMATE:
8668                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8669                 break;
8670             }
8671
8672             break;
8673
8674           case CMAIL_RESIGN:
8675             if (WhiteOnMove(currentMove)) {
8676                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8677             } else {
8678                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8679             }
8680             break;
8681
8682           case CMAIL_ACCEPT:
8683             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8684             break;
8685
8686           default:
8687             break;
8688         }
8689     }
8690
8691     return;
8692 }
8693
8694 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8695 int
8696 CmailLoadGame(f, gameNumber, title, useList)
8697      FILE *f;
8698      int gameNumber;
8699      char *title;
8700      int useList;
8701 {
8702     int retVal;
8703
8704     if (gameNumber > nCmailGames) {
8705         DisplayError(_("No more games in this message"), 0);
8706         return FALSE;
8707     }
8708     if (f == lastLoadGameFP) {
8709         int offset = gameNumber - lastLoadGameNumber;
8710         if (offset == 0) {
8711             cmailMsg[0] = NULLCHAR;
8712             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8713                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8714                 nCmailMovesRegistered--;
8715             }
8716             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8717             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8718                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8719             }
8720         } else {
8721             if (! RegisterMove()) return FALSE;
8722         }
8723     }
8724
8725     retVal = LoadGame(f, gameNumber, title, useList);
8726
8727     /* Make move registered during previous look at this game, if any */
8728     MakeRegisteredMove();
8729
8730     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8731         commentList[currentMove]
8732           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8733         DisplayComment(currentMove - 1, commentList[currentMove]);
8734     }
8735
8736     return retVal;
8737 }
8738
8739 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8740 int
8741 ReloadGame(offset)
8742      int offset;
8743 {
8744     int gameNumber = lastLoadGameNumber + offset;
8745     if (lastLoadGameFP == NULL) {
8746         DisplayError(_("No game has been loaded yet"), 0);
8747         return FALSE;
8748     }
8749     if (gameNumber <= 0) {
8750         DisplayError(_("Can't back up any further"), 0);
8751         return FALSE;
8752     }
8753     if (cmailMsgLoaded) {
8754         return CmailLoadGame(lastLoadGameFP, gameNumber,
8755                              lastLoadGameTitle, lastLoadGameUseList);
8756     } else {
8757         return LoadGame(lastLoadGameFP, gameNumber,
8758                         lastLoadGameTitle, lastLoadGameUseList);
8759     }
8760 }
8761
8762
8763
8764 /* Load the nth game from open file f */
8765 int
8766 LoadGame(f, gameNumber, title, useList)
8767      FILE *f;
8768      int gameNumber;
8769      char *title;
8770      int useList;
8771 {
8772     ChessMove cm;
8773     char buf[MSG_SIZ];
8774     int gn = gameNumber;
8775     ListGame *lg = NULL;
8776     int numPGNTags = 0;
8777     int err;
8778     GameMode oldGameMode;
8779     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8780
8781     if (appData.debugMode)
8782         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8783
8784     if (gameMode == Training )
8785         SetTrainingModeOff();
8786
8787     oldGameMode = gameMode;
8788     if (gameMode != BeginningOfGame) 
8789       {
8790         Reset(FALSE, TRUE);
8791       };
8792
8793     gameFileFP = f;
8794     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
8795       {
8796         fclose(lastLoadGameFP);
8797       };
8798
8799     if (useList) 
8800       {
8801         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8802         
8803         if (lg) 
8804           {
8805             fseek(f, lg->offset, 0);
8806             GameListHighlight(gameNumber);
8807             gn = 1;
8808           }
8809         else 
8810           {
8811             DisplayError(_("Game number out of range"), 0);
8812             return FALSE;
8813           };
8814       } 
8815     else 
8816       {
8817         GameListDestroy();
8818         if (fseek(f, 0, 0) == -1) 
8819           {
8820             if (f == lastLoadGameFP ?
8821                 gameNumber == lastLoadGameNumber + 1 :
8822                 gameNumber == 1) 
8823               {
8824                 gn = 1;
8825               } 
8826             else 
8827               {
8828                 DisplayError(_("Can't seek on game file"), 0);
8829                 return FALSE;
8830               };
8831           };
8832       };
8833
8834     lastLoadGameFP      = f;
8835     lastLoadGameNumber  = gameNumber;
8836     strcpy(lastLoadGameTitle, title);
8837     lastLoadGameUseList = useList;
8838
8839     yynewfile(f);
8840
8841     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
8842       {
8843         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8844                  lg->gameInfo.black);
8845         DisplayTitle(buf);
8846       } 
8847     else if (*title != NULLCHAR) 
8848       {
8849         if (gameNumber > 1) 
8850           {
8851             sprintf(buf, "%s %d", title, gameNumber);
8852             DisplayTitle(buf);
8853           } 
8854         else 
8855           {
8856             DisplayTitle(title);
8857           };
8858       };
8859
8860     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
8861       {
8862         gameMode = PlayFromGameFile;
8863         ModeHighlight();
8864       };
8865
8866     currentMove = forwardMostMove = backwardMostMove = 0;
8867     CopyBoard(boards[0], initialPosition);
8868     StopClocks();
8869
8870     /*
8871      * Skip the first gn-1 games in the file.
8872      * Also skip over anything that precedes an identifiable
8873      * start of game marker, to avoid being confused by
8874      * garbage at the start of the file.  Currently
8875      * recognized start of game markers are the move number "1",
8876      * the pattern "gnuchess .* game", the pattern
8877      * "^[#;%] [^ ]* game file", and a PGN tag block.
8878      * A game that starts with one of the latter two patterns
8879      * will also have a move number 1, possibly
8880      * following a position diagram.
8881      * 5-4-02: Let's try being more lenient and allowing a game to
8882      * start with an unnumbered move.  Does that break anything?
8883      */
8884     cm = lastLoadGameStart = (ChessMove) 0;
8885     while (gn > 0) {
8886         yyboardindex = forwardMostMove;
8887         cm = (ChessMove) yylex();
8888         switch (cm) {
8889           case (ChessMove) 0:
8890             if (cmailMsgLoaded) {
8891                 nCmailGames = CMAIL_MAX_GAMES - gn;
8892             } else {
8893                 Reset(TRUE, TRUE);
8894                 DisplayError(_("Game not found in file"), 0);
8895             }
8896             return FALSE;
8897
8898           case GNUChessGame:
8899           case XBoardGame:
8900             gn--;
8901             lastLoadGameStart = cm;
8902             break;
8903
8904           case MoveNumberOne:
8905             switch (lastLoadGameStart) {
8906               case GNUChessGame:
8907               case XBoardGame:
8908               case PGNTag:
8909                 break;
8910               case MoveNumberOne:
8911               case (ChessMove) 0:
8912                 gn--;           /* count this game */
8913                 lastLoadGameStart = cm;
8914                 break;
8915               default:
8916                 /* impossible */
8917                 break;
8918             }
8919             break;
8920
8921           case PGNTag:
8922             switch (lastLoadGameStart) {
8923               case GNUChessGame:
8924               case PGNTag:
8925               case MoveNumberOne:
8926               case (ChessMove) 0:
8927                 gn--;           /* count this game */
8928                 lastLoadGameStart = cm;
8929                 break;
8930               case XBoardGame:
8931                 lastLoadGameStart = cm; /* game counted already */
8932                 break;
8933               default:
8934                 /* impossible */
8935                 break;
8936             }
8937             if (gn > 0) {
8938                 do {
8939                     yyboardindex = forwardMostMove;
8940                     cm = (ChessMove) yylex();
8941                 } while (cm == PGNTag || cm == Comment);
8942             }
8943             break;
8944
8945           case WhiteWins:
8946           case BlackWins:
8947           case GameIsDrawn:
8948             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8949                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8950                     != CMAIL_OLD_RESULT) {
8951                     nCmailResults ++ ;
8952                     cmailResult[  CMAIL_MAX_GAMES
8953                                 - gn - 1] = CMAIL_OLD_RESULT;
8954                 }
8955             }
8956             break;
8957
8958           case NormalMove:
8959             /* Only a NormalMove can be at the start of a game
8960              * without a position diagram. */
8961             if (lastLoadGameStart == (ChessMove) 0) {
8962               gn--;
8963               lastLoadGameStart = MoveNumberOne;
8964             }
8965             break;
8966
8967           default:
8968             break;
8969         }
8970     }
8971
8972     if (appData.debugMode)
8973       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8974
8975     if (cm == XBoardGame) {
8976         /* Skip any header junk before position diagram and/or move 1 */
8977         for (;;) {
8978             yyboardindex = forwardMostMove;
8979             cm = (ChessMove) yylex();
8980
8981             if (cm == (ChessMove) 0 ||
8982                 cm == GNUChessGame || cm == XBoardGame) {
8983                 /* Empty game; pretend end-of-file and handle later */
8984                 cm = (ChessMove) 0;
8985                 break;
8986             }
8987
8988             if (cm == MoveNumberOne || cm == PositionDiagram ||
8989                 cm == PGNTag || cm == Comment)
8990               break;
8991         }
8992     } else if (cm == GNUChessGame) {
8993         if (gameInfo.event != NULL) {
8994             free(gameInfo.event);
8995         }
8996         gameInfo.event = StrSave(yy_text);
8997     }
8998
8999     startedFromSetupPosition = FALSE;
9000     while (cm == PGNTag) {
9001         if (appData.debugMode)
9002           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9003         err = ParsePGNTag(yy_text, &gameInfo);
9004         if (!err) numPGNTags++;
9005
9006         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9007         if(gameInfo.variant != oldVariant) {
9008             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9009             InitPosition(TRUE);
9010             oldVariant = gameInfo.variant;
9011             if (appData.debugMode)
9012               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9013         }
9014
9015
9016         if (gameInfo.fen != NULL) {
9017           Board initial_position;
9018           startedFromSetupPosition = TRUE;
9019           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9020             Reset(TRUE, TRUE);
9021             DisplayError(_("Bad FEN position in file"), 0);
9022             return FALSE;
9023           }
9024           CopyBoard(boards[0], initial_position);
9025           if (blackPlaysFirst) {
9026             currentMove = forwardMostMove = backwardMostMove = 1;
9027             CopyBoard(boards[1], initial_position);
9028             strcpy(moveList[0], "");
9029             strcpy(parseList[0], "");
9030             timeRemaining[0][1] = whiteTimeRemaining;
9031             timeRemaining[1][1] = blackTimeRemaining;
9032             if (commentList[0] != NULL) {
9033               commentList[1] = commentList[0];
9034               commentList[0] = NULL;
9035             }
9036           } else {
9037             currentMove = forwardMostMove = backwardMostMove = 0;
9038           }
9039           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9040           {   int i;
9041               initialRulePlies = FENrulePlies;
9042               epStatus[forwardMostMove] = FENepStatus;
9043               for( i=0; i< nrCastlingRights; i++ )
9044                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9045           }
9046           yyboardindex = forwardMostMove;
9047           free(gameInfo.fen);
9048           gameInfo.fen = NULL;
9049         }
9050
9051         yyboardindex = forwardMostMove;
9052         cm = (ChessMove) yylex();
9053
9054         /* Handle comments interspersed among the tags */
9055         while (cm == Comment) {
9056             char *p;
9057             if (appData.debugMode)
9058               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9059             p = yy_text;
9060             if (*p == '{' || *p == '[' || *p == '(') {
9061                 p[strlen(p) - 1] = NULLCHAR;
9062                 p++;
9063             }
9064             while (*p == '\n') p++;
9065             AppendComment(currentMove, p);
9066             yyboardindex = forwardMostMove;
9067             cm = (ChessMove) yylex();
9068         }
9069     }
9070
9071     /* don't rely on existence of Event tag since if game was
9072      * pasted from clipboard the Event tag may not exist
9073      */
9074     if (numPGNTags > 0){
9075         char *tags;
9076         if (gameInfo.variant == VariantNormal) {
9077           gameInfo.variant = StringToVariant(gameInfo.event);
9078         }
9079         if (!matchMode) {
9080           if( appData.autoDisplayTags ) {
9081             tags = PGNTags(&gameInfo);
9082             TagsPopUp(tags, CmailMsg());
9083             free(tags);
9084           }
9085         }
9086     } else {
9087         /* Make something up, but don't display it now */
9088         SetGameInfo();
9089         TagsPopDown();
9090     }
9091
9092     if (cm == PositionDiagram) {
9093         int i, j;
9094         char *p;
9095         Board initial_position;
9096
9097         if (appData.debugMode)
9098           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9099
9100         if (!startedFromSetupPosition) {
9101             p = yy_text;
9102             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9103               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9104                 switch (*p) {
9105                   case '[':
9106                   case '-':
9107                   case ' ':
9108                   case '\t':
9109                   case '\n':
9110                   case '\r':
9111                     break;
9112                   default:
9113                     initial_position[i][j++] = CharToPiece(*p);
9114                     break;
9115                 }
9116             while (*p == ' ' || *p == '\t' ||
9117                    *p == '\n' || *p == '\r') p++;
9118
9119             if (strncmp(p, "black", strlen("black"))==0)
9120               blackPlaysFirst = TRUE;
9121             else
9122               blackPlaysFirst = FALSE;
9123             startedFromSetupPosition = TRUE;
9124
9125             CopyBoard(boards[0], initial_position);
9126             if (blackPlaysFirst) {
9127                 currentMove = forwardMostMove = backwardMostMove = 1;
9128                 CopyBoard(boards[1], initial_position);
9129                 strcpy(moveList[0], "");
9130                 strcpy(parseList[0], "");
9131                 timeRemaining[0][1] = whiteTimeRemaining;
9132                 timeRemaining[1][1] = blackTimeRemaining;
9133                 if (commentList[0] != NULL) {
9134                     commentList[1] = commentList[0];
9135                     commentList[0] = NULL;
9136                 }
9137             } else {
9138                 currentMove = forwardMostMove = backwardMostMove = 0;
9139             }
9140         }
9141         yyboardindex = forwardMostMove;
9142         cm = (ChessMove) yylex();
9143     }
9144
9145     if (first.pr == NoProc) {
9146         StartChessProgram(&first);
9147     }
9148     InitChessProgram(&first, FALSE);
9149     SendToProgram("force\n", &first);
9150     if (startedFromSetupPosition) {
9151         SendBoard(&first, forwardMostMove);
9152     if (appData.debugMode) {
9153         fprintf(debugFP, "Load Game\n");
9154     }
9155         DisplayBothClocks();
9156     }
9157
9158     /* [HGM] server: flag to write setup moves in broadcast file as one */
9159     loadFlag = appData.suppressLoadMoves;
9160
9161     while (cm == Comment) {
9162         char *p;
9163         if (appData.debugMode)
9164           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9165         p = yy_text;
9166         if (*p == '{' || *p == '[' || *p == '(') {
9167             p[strlen(p) - 1] = NULLCHAR;
9168             p++;
9169         }
9170         while (*p == '\n') p++;
9171         AppendComment(currentMove, p);
9172         yyboardindex = forwardMostMove;
9173         cm = (ChessMove) yylex();
9174     }
9175
9176     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9177         cm == WhiteWins || cm == BlackWins ||
9178         cm == GameIsDrawn || cm == GameUnfinished) {
9179         DisplayMessage("", _("No moves in game"));
9180         if (cmailMsgLoaded) {
9181             if (appData.debugMode)
9182               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9183             ClearHighlights();
9184             flipView = FALSE;
9185         }
9186         DrawPosition(FALSE, boards[currentMove]);
9187         DisplayBothClocks();
9188         gameMode = EditGame;
9189         ModeHighlight();
9190         gameFileFP = NULL;
9191         cmailOldMove = 0;
9192         return TRUE;
9193     }
9194
9195     // [HGM] PV info: routine tests if comment empty
9196     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9197         DisplayComment(currentMove - 1, commentList[currentMove]);
9198     }
9199     if (!matchMode && appData.timeDelay != 0)
9200       DrawPosition(FALSE, boards[currentMove]);
9201
9202     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9203       programStats.ok_to_send = 1;
9204     }
9205
9206     /* if the first token after the PGN tags is a move
9207      * and not move number 1, retrieve it from the parser
9208      */
9209     if (cm != MoveNumberOne)
9210         LoadGameOneMove(cm);
9211
9212     /* load the remaining moves from the file */
9213     while (LoadGameOneMove((ChessMove)0)) {
9214       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9215       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9216     }
9217
9218     /* rewind to the start of the game */
9219     currentMove = backwardMostMove;
9220
9221     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9222
9223     if (oldGameMode == AnalyzeFile ||
9224         oldGameMode == AnalyzeMode) {
9225       AnalyzeFileEvent();
9226     }
9227
9228     if (matchMode || appData.timeDelay == 0) {
9229       ToEndEvent();
9230       gameMode = EditGame;
9231       ModeHighlight();
9232     } else if (appData.timeDelay > 0) {
9233       AutoPlayGameLoop();
9234     }
9235
9236     if (appData.debugMode)
9237         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9238
9239     loadFlag = 0; /* [HGM] true game starts */
9240     return TRUE;
9241 }
9242
9243 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9244 int
9245 ReloadPosition(offset)
9246      int offset;
9247 {
9248     int positionNumber = lastLoadPositionNumber + offset;
9249     if (lastLoadPositionFP == NULL) {
9250         DisplayError(_("No position has been loaded yet"), 0);
9251         return FALSE;
9252     }
9253     if (positionNumber <= 0) {
9254         DisplayError(_("Can't back up any further"), 0);
9255         return FALSE;
9256     }
9257     return LoadPosition(lastLoadPositionFP, positionNumber,
9258                         lastLoadPositionTitle);
9259 }
9260
9261 /* Load the nth position from the given file */
9262 int
9263 LoadPositionFromFile(filename, n, title)
9264      char *filename;
9265      int n;
9266      char *title;
9267 {
9268     FILE *f;
9269     char buf[MSG_SIZ];
9270
9271     if (strcmp(filename, "-") == 0) {
9272         return LoadPosition(stdin, n, "stdin");
9273     } else {
9274         f = fopen(filename, "rb");
9275         if (f == NULL) {
9276             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9277             DisplayError(buf, errno);
9278             return FALSE;
9279         } else {
9280             return LoadPosition(f, n, title);
9281         }
9282     }
9283 }
9284
9285 /* Load the nth position from the given open file, and close it */
9286 int
9287 LoadPosition(f, positionNumber, title)
9288      FILE *f;
9289      int positionNumber;
9290      char *title;
9291 {
9292     char *p, line[MSG_SIZ];
9293     Board initial_position;
9294     int i, j, fenMode, pn;
9295
9296     if (gameMode == Training )
9297         SetTrainingModeOff();
9298
9299     if (gameMode != BeginningOfGame) {
9300         Reset(FALSE, TRUE);
9301     }
9302     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9303         fclose(lastLoadPositionFP);
9304     }
9305     if (positionNumber == 0) positionNumber = 1;
9306     lastLoadPositionFP = f;
9307     lastLoadPositionNumber = positionNumber;
9308     strcpy(lastLoadPositionTitle, title);
9309     if (first.pr == NoProc) {
9310       StartChessProgram(&first);
9311       InitChessProgram(&first, FALSE);
9312     }
9313     pn = positionNumber;
9314     if (positionNumber < 0) {
9315         /* Negative position number means to seek to that byte offset */
9316         if (fseek(f, -positionNumber, 0) == -1) {
9317             DisplayError(_("Can't seek on position file"), 0);
9318             return FALSE;
9319         };
9320         pn = 1;
9321     } else {
9322         if (fseek(f, 0, 0) == -1) {
9323             if (f == lastLoadPositionFP ?
9324                 positionNumber == lastLoadPositionNumber + 1 :
9325                 positionNumber == 1) {
9326                 pn = 1;
9327             } else {
9328                 DisplayError(_("Can't seek on position file"), 0);
9329                 return FALSE;
9330             }
9331         }
9332     }
9333     /* See if this file is FEN or old-style xboard */
9334     if (fgets(line, MSG_SIZ, f) == NULL) {
9335         DisplayError(_("Position not found in file"), 0);
9336         return FALSE;
9337     }
9338     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9339     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
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, SavePart(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
9659         /* Print move */
9660         blank = linelen > 0 && movelen > 0;
9661         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9662             fprintf(f, "\n");
9663             linelen = 0;
9664             blank = 0;
9665         }
9666         if (blank) {
9667             fprintf(f, " ");
9668             linelen++;
9669         }
9670         fprintf(f, move_buffer);
9671         linelen += movelen;
9672
9673         /* [AS] Add PV info if present */
9674         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9675             /* [HGM] add time */
9676             char buf[MSG_SIZ]; int seconds = 0;
9677
9678             if(i >= backwardMostMove) {
9679                 if(WhiteOnMove(i))
9680                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9681                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9682                 else
9683                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9684                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9685             }
9686             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9687
9688             if( seconds <= 0) buf[0] = 0; else
9689             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9690                 seconds = (seconds + 4)/10; // round to full seconds
9691                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9692                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9693             }
9694
9695             sprintf( move_buffer, "{%s%.2f/%d%s}",
9696                 pvInfoList[i].score >= 0 ? "+" : "",
9697                 pvInfoList[i].score / 100.0,
9698                 pvInfoList[i].depth,
9699                 buf );
9700
9701             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9702
9703             /* Print score/depth */
9704             blank = linelen > 0 && movelen > 0;
9705             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9706                 fprintf(f, "\n");
9707                 linelen = 0;
9708                 blank = 0;
9709             }
9710             if (blank) {
9711                 fprintf(f, " ");
9712                 linelen++;
9713             }
9714             fprintf(f, move_buffer);
9715             linelen += movelen;
9716         }
9717
9718         i++;
9719     }
9720
9721     /* Start a new line */
9722     if (linelen > 0) fprintf(f, "\n");
9723
9724     /* Print comments after last move */
9725     if (commentList[i] != NULL) {
9726         fprintf(f, "{\n%s}\n", commentList[i]);
9727     }
9728
9729     /* Print result */
9730     if (gameInfo.resultDetails != NULL &&
9731         gameInfo.resultDetails[0] != NULLCHAR) {
9732         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9733                 PGNResult(gameInfo.result));
9734     } else {
9735         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9736     }
9737
9738     fclose(f);
9739     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9740     return TRUE;
9741 }
9742
9743 /* Save game in old style and close the file */
9744 int
9745 SaveGameOldStyle(f)
9746      FILE *f;
9747 {
9748     int i, offset;
9749     time_t tm;
9750
9751     tm = time((time_t *) NULL);
9752
9753     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9754     PrintOpponents(f);
9755
9756     if (backwardMostMove > 0 || startedFromSetupPosition) {
9757         fprintf(f, "\n[--------------\n");
9758         PrintPosition(f, backwardMostMove);
9759         fprintf(f, "--------------]\n");
9760     } else {
9761         fprintf(f, "\n");
9762     }
9763
9764     i = backwardMostMove;
9765     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9766
9767     while (i < forwardMostMove) {
9768         if (commentList[i] != NULL) {
9769             fprintf(f, "[%s]\n", commentList[i]);
9770         }
9771
9772         if ((i % 2) == 1) {
9773             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9774             i++;
9775         } else {
9776             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9777             i++;
9778             if (commentList[i] != NULL) {
9779                 fprintf(f, "\n");
9780                 continue;
9781             }
9782             if (i >= forwardMostMove) {
9783                 fprintf(f, "\n");
9784                 break;
9785             }
9786             fprintf(f, "%s\n", parseList[i]);
9787             i++;
9788         }
9789     }
9790
9791     if (commentList[i] != NULL) {
9792         fprintf(f, "[%s]\n", commentList[i]);
9793     }
9794
9795     /* This isn't really the old style, but it's close enough */
9796     if (gameInfo.resultDetails != NULL &&
9797         gameInfo.resultDetails[0] != NULLCHAR) {
9798         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9799                 gameInfo.resultDetails);
9800     } else {
9801         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9802     }
9803
9804     fclose(f);
9805     return TRUE;
9806 }
9807
9808 /* Save the current game to open file f and close the file */
9809 int
9810 SaveGame(f, dummy, dummy2)
9811      FILE *f;
9812      int dummy;
9813      char *dummy2;
9814 {
9815     if (gameMode == EditPosition) EditPositionDone();
9816     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9817     if (appData.oldSaveStyle)
9818       return SaveGameOldStyle(f);
9819     else
9820       return SaveGamePGN(f);
9821 }
9822
9823 /* Save the current position to the given file */
9824 int
9825 SavePositionToFile(filename)
9826      char *filename;
9827 {
9828     FILE *f;
9829     char buf[MSG_SIZ];
9830
9831     if (strcmp(filename, "-") == 0) {
9832         return SavePosition(stdout, 0, NULL);
9833     } else {
9834         f = fopen(filename, "a");
9835         if (f == NULL) {
9836             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9837             DisplayError(buf, errno);
9838             return FALSE;
9839         } else {
9840             SavePosition(f, 0, NULL);
9841             return TRUE;
9842         }
9843     }
9844 }
9845
9846 /* Save the current position to the given open file and close the file */
9847 int
9848 SavePosition(f, dummy, dummy2)
9849      FILE *f;
9850      int dummy;
9851      char *dummy2;
9852 {
9853     time_t tm;
9854     char *fen;
9855
9856     if (appData.oldSaveStyle) {
9857         tm = time((time_t *) NULL);
9858
9859         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9860         PrintOpponents(f);
9861         fprintf(f, "[--------------\n");
9862         PrintPosition(f, currentMove);
9863         fprintf(f, "--------------]\n");
9864     } else {
9865         fen = PositionToFEN(currentMove, NULL);
9866         fprintf(f, "%s\n", fen);
9867         free(fen);
9868     }
9869     fclose(f);
9870     return TRUE;
9871 }
9872
9873 void
9874 ReloadCmailMsgEvent(unregister)
9875      int unregister;
9876 {
9877 #if !WIN32
9878     static char *inFilename = NULL;
9879     static char *outFilename;
9880     int i;
9881     struct stat inbuf, outbuf;
9882     int status;
9883
9884     /* Any registered moves are unregistered if unregister is set, */
9885     /* i.e. invoked by the signal handler */
9886     if (unregister) {
9887         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9888             cmailMoveRegistered[i] = FALSE;
9889             if (cmailCommentList[i] != NULL) {
9890                 free(cmailCommentList[i]);
9891                 cmailCommentList[i] = NULL;
9892             }
9893         }
9894         nCmailMovesRegistered = 0;
9895     }
9896
9897     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9898         cmailResult[i] = CMAIL_NOT_RESULT;
9899     }
9900     nCmailResults = 0;
9901
9902     if (inFilename == NULL) {
9903         /* Because the filenames are static they only get malloced once  */
9904         /* and they never get freed                                      */
9905         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9906         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9907
9908         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9909         sprintf(outFilename, "%s.out", appData.cmailGameName);
9910     }
9911
9912     status = stat(outFilename, &outbuf);
9913     if (status < 0) {
9914         cmailMailedMove = FALSE;
9915     } else {
9916         status = stat(inFilename, &inbuf);
9917         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9918     }
9919
9920     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9921        counts the games, notes how each one terminated, etc.
9922
9923        It would be nice to remove this kludge and instead gather all
9924        the information while building the game list.  (And to keep it
9925        in the game list nodes instead of having a bunch of fixed-size
9926        parallel arrays.)  Note this will require getting each game's
9927        termination from the PGN tags, as the game list builder does
9928        not process the game moves.  --mann
9929        */
9930     cmailMsgLoaded = TRUE;
9931     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9932
9933     /* Load first game in the file or popup game menu */
9934     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9935
9936 #endif /* !WIN32 */
9937     return;
9938 }
9939
9940 int
9941 RegisterMove()
9942 {
9943     FILE *f;
9944     char string[MSG_SIZ];
9945
9946     if (   cmailMailedMove
9947         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9948         return TRUE;            /* Allow free viewing  */
9949     }
9950
9951     /* Unregister move to ensure that we don't leave RegisterMove        */
9952     /* with the move registered when the conditions for registering no   */
9953     /* longer hold                                                       */
9954     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9955         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9956         nCmailMovesRegistered --;
9957
9958         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9959           {
9960               free(cmailCommentList[lastLoadGameNumber - 1]);
9961               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9962           }
9963     }
9964
9965     if (cmailOldMove == -1) {
9966         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9967         return FALSE;
9968     }
9969
9970     if (currentMove > cmailOldMove + 1) {
9971         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9972         return FALSE;
9973     }
9974
9975     if (currentMove < cmailOldMove) {
9976         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9977         return FALSE;
9978     }
9979
9980     if (forwardMostMove > currentMove) {
9981         /* Silently truncate extra moves */
9982         TruncateGame();
9983     }
9984
9985     if (   (currentMove == cmailOldMove + 1)
9986         || (   (currentMove == cmailOldMove)
9987             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9988                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9989         if (gameInfo.result != GameUnfinished) {
9990             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9991         }
9992
9993         if (commentList[currentMove] != NULL) {
9994             cmailCommentList[lastLoadGameNumber - 1]
9995               = StrSave(commentList[currentMove]);
9996         }
9997         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9998
9999         if (appData.debugMode)
10000           fprintf(debugFP, "Saving %s for game %d\n",
10001                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10002
10003         sprintf(string,
10004                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10005
10006         f = fopen(string, "w");
10007         if (appData.oldSaveStyle) {
10008             SaveGameOldStyle(f); /* also closes the file */
10009
10010             sprintf(string, "%s.pos.out", appData.cmailGameName);
10011             f = fopen(string, "w");
10012             SavePosition(f, 0, NULL); /* also closes the file */
10013         } else {
10014             fprintf(f, "{--------------\n");
10015             PrintPosition(f, currentMove);
10016             fprintf(f, "--------------}\n\n");
10017
10018             SaveGame(f, 0, NULL); /* also closes the file*/
10019         }
10020
10021         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10022         nCmailMovesRegistered ++;
10023     } else if (nCmailGames == 1) {
10024         DisplayError(_("You have not made a move yet"), 0);
10025         return FALSE;
10026     }
10027
10028     return TRUE;
10029 }
10030
10031 void
10032 MailMoveEvent()
10033 {
10034 #if !WIN32
10035     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10036     FILE *commandOutput;
10037     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10038     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10039     int nBuffers;
10040     int i;
10041     int archived;
10042     char *arcDir;
10043
10044     if (! cmailMsgLoaded) {
10045         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10046         return;
10047     }
10048
10049     if (nCmailGames == nCmailResults) {
10050         DisplayError(_("No unfinished games"), 0);
10051         return;
10052     }
10053
10054 #if CMAIL_PROHIBIT_REMAIL
10055     if (cmailMailedMove) {
10056         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);
10057         DisplayError(msg, 0);
10058         return;
10059     }
10060 #endif
10061
10062     if (! (cmailMailedMove || RegisterMove())) return;
10063
10064     if (   cmailMailedMove
10065         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10066         sprintf(string, partCommandString,
10067                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10068         commandOutput = popen(string, "r");
10069
10070         if (commandOutput == NULL) {
10071             DisplayError(_("Failed to invoke cmail"), 0);
10072         } else {
10073             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10074                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10075             }
10076             if (nBuffers > 1) {
10077                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10078                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10079                 nBytes = MSG_SIZ - 1;
10080             } else {
10081                 (void) memcpy(msg, buffer, nBytes);
10082             }
10083             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10084
10085             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10086                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10087
10088                 archived = TRUE;
10089                 for (i = 0; i < nCmailGames; i ++) {
10090                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10091                         archived = FALSE;
10092                     }
10093                 }
10094                 if (   archived
10095                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10096                         != NULL)) {
10097                     sprintf(buffer, "%s/%s.%s.archive",
10098                             arcDir,
10099                             appData.cmailGameName,
10100                             gameInfo.date);
10101                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10102                     cmailMsgLoaded = FALSE;
10103                 }
10104             }
10105
10106             DisplayInformation(msg);
10107             pclose(commandOutput);
10108         }
10109     } else {
10110         if ((*cmailMsg) != '\0') {
10111             DisplayInformation(cmailMsg);
10112         }
10113     }
10114
10115     return;
10116 #endif /* !WIN32 */
10117 }
10118
10119 char *
10120 CmailMsg()
10121 {
10122 #if WIN32
10123     return NULL;
10124 #else
10125     int  prependComma = 0;
10126     char number[5];
10127     char string[MSG_SIZ];       /* Space for game-list */
10128     int  i;
10129
10130     if (!cmailMsgLoaded) return "";
10131
10132     if (cmailMailedMove) {
10133         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10134     } else {
10135         /* Create a list of games left */
10136         sprintf(string, "[");
10137         for (i = 0; i < nCmailGames; i ++) {
10138             if (! (   cmailMoveRegistered[i]
10139                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10140                 if (prependComma) {
10141                     sprintf(number, ",%d", i + 1);
10142                 } else {
10143                     sprintf(number, "%d", i + 1);
10144                     prependComma = 1;
10145                 }
10146
10147                 strcat(string, number);
10148             }
10149         }
10150         strcat(string, "]");
10151
10152         if (nCmailMovesRegistered + nCmailResults == 0) {
10153             switch (nCmailGames) {
10154               case 1:
10155                 sprintf(cmailMsg,
10156                         _("Still need to make move for game\n"));
10157                 break;
10158
10159               case 2:
10160                 sprintf(cmailMsg,
10161                         _("Still need to make moves for both games\n"));
10162                 break;
10163
10164               default:
10165                 sprintf(cmailMsg,
10166                         _("Still need to make moves for all %d games\n"),
10167                         nCmailGames);
10168                 break;
10169             }
10170         } else {
10171             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10172               case 1:
10173                 sprintf(cmailMsg,
10174                         _("Still need to make a move for game %s\n"),
10175                         string);
10176                 break;
10177
10178               case 0:
10179                 if (nCmailResults == nCmailGames) {
10180                     sprintf(cmailMsg, _("No unfinished games\n"));
10181                 } else {
10182                     sprintf(cmailMsg, _("Ready to send mail\n"));
10183                 }
10184                 break;
10185
10186               default:
10187                 sprintf(cmailMsg,
10188                         _("Still need to make moves for games %s\n"),
10189                         string);
10190             }
10191         }
10192     }
10193     return cmailMsg;
10194 #endif /* WIN32 */
10195 }
10196
10197 void
10198 ResetGameEvent()
10199 {
10200     if (gameMode == Training)
10201       SetTrainingModeOff();
10202
10203     Reset(TRUE, TRUE);
10204     cmailMsgLoaded = FALSE;
10205     if (appData.icsActive) {
10206       SendToICS(ics_prefix);
10207       SendToICS("refresh\n");
10208     }
10209 }
10210
10211 void
10212 ExitEvent(status)
10213      int status;
10214 {
10215     exiting++;
10216     if (exiting > 2) {
10217       /* Give up on clean exit */
10218       exit(status);
10219     }
10220     if (exiting > 1) {
10221       /* Keep trying for clean exit */
10222       return;
10223     }
10224
10225     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10226
10227     if (telnetISR != NULL) {
10228       RemoveInputSource(telnetISR);
10229     }
10230     if (icsPR != NoProc) {
10231       DestroyChildProcess(icsPR, TRUE);
10232     }
10233
10234     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10235     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10236
10237     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10238     /* make sure this other one finishes before killing it!                  */
10239     if(endingGame) { int count = 0;
10240         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10241         while(endingGame && count++ < 10) DoSleep(1);
10242         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10243     }
10244
10245     /* Kill off chess programs */
10246     if (first.pr != NoProc) {
10247         ExitAnalyzeMode();
10248
10249         DoSleep( appData.delayBeforeQuit );
10250         SendToProgram("quit\n", &first);
10251         DoSleep( appData.delayAfterQuit );
10252         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10253     }
10254     if (second.pr != NoProc) {
10255         DoSleep( appData.delayBeforeQuit );
10256         SendToProgram("quit\n", &second);
10257         DoSleep( appData.delayAfterQuit );
10258         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10259     }
10260     if (first.isr != NULL) {
10261         RemoveInputSource(first.isr);
10262     }
10263     if (second.isr != NULL) {
10264         RemoveInputSource(second.isr);
10265     }
10266
10267     ShutDownFrontEnd();
10268     exit(status);
10269 }
10270
10271 void
10272 PauseEvent()
10273 {
10274     if (appData.debugMode)
10275         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10276     if (pausing) {
10277         pausing = FALSE;
10278         ModeHighlight();
10279         if (gameMode == MachinePlaysWhite ||
10280             gameMode == MachinePlaysBlack) {
10281             StartClocks();
10282         } else {
10283             DisplayBothClocks();
10284         }
10285         if (gameMode == PlayFromGameFile) {
10286             if (appData.timeDelay >= 0)
10287                 AutoPlayGameLoop();
10288         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10289             Reset(FALSE, TRUE);
10290             SendToICS(ics_prefix);
10291             SendToICS("refresh\n");
10292         } else if (currentMove < forwardMostMove) {
10293             ForwardInner(forwardMostMove);
10294         }
10295         pauseExamInvalid = FALSE;
10296     } else {
10297         switch (gameMode) {
10298           default:
10299             return;
10300           case IcsExamining:
10301             pauseExamForwardMostMove = forwardMostMove;
10302             pauseExamInvalid = FALSE;
10303             /* fall through */
10304           case IcsObserving:
10305           case IcsPlayingWhite:
10306           case IcsPlayingBlack:
10307             pausing = TRUE;
10308             ModeHighlight();
10309             return;
10310           case PlayFromGameFile:
10311             (void) StopLoadGameTimer();
10312             pausing = TRUE;
10313             ModeHighlight();
10314             break;
10315           case BeginningOfGame:
10316             if (appData.icsActive) return;
10317             /* else fall through */
10318           case MachinePlaysWhite:
10319           case MachinePlaysBlack:
10320           case TwoMachinesPlay:
10321             if (forwardMostMove == 0)
10322               return;           /* don't pause if no one has moved */
10323             if ((gameMode == MachinePlaysWhite &&
10324                  !WhiteOnMove(forwardMostMove)) ||
10325                 (gameMode == MachinePlaysBlack &&
10326                  WhiteOnMove(forwardMostMove))) {
10327                 StopClocks();
10328             }
10329             pausing = TRUE;
10330             ModeHighlight();
10331             break;
10332         }
10333     }
10334 }
10335
10336 void
10337 EditCommentEvent()
10338 {
10339     char title[MSG_SIZ];
10340
10341     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10342         strcpy(title, _("Edit comment"));
10343     } else {
10344         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10345                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10346                 parseList[currentMove - 1]);
10347     }
10348
10349     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10350 }
10351
10352
10353 void
10354 EditTagsEvent()
10355 {
10356     char *tags = PGNTags(&gameInfo);
10357     EditTagsPopUp(tags);
10358     free(tags);
10359 }
10360
10361 void
10362 AnalyzeModeEvent()
10363 {
10364     if (appData.noChessProgram || gameMode == AnalyzeMode)
10365       return;
10366
10367     if (gameMode != AnalyzeFile) {
10368         if (!appData.icsEngineAnalyze) {
10369                EditGameEvent();
10370                if (gameMode != EditGame) return;
10371         }
10372         ResurrectChessProgram();
10373         SendToProgram("analyze\n", &first);
10374         first.analyzing = TRUE;
10375         /*first.maybeThinking = TRUE;*/
10376         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10377         AnalysisPopUp(_("Analysis"),
10378                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10379     }
10380     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10381     pausing = FALSE;
10382     ModeHighlight();
10383     SetGameInfo();
10384
10385     StartAnalysisClock();
10386     GetTimeMark(&lastNodeCountTime);
10387     lastNodeCount = 0;
10388 }
10389
10390 void
10391 AnalyzeFileEvent()
10392 {
10393     if (appData.noChessProgram || gameMode == AnalyzeFile)
10394       return;
10395
10396     if (gameMode != AnalyzeMode) {
10397         EditGameEvent();
10398         if (gameMode != EditGame) return;
10399         ResurrectChessProgram();
10400         SendToProgram("analyze\n", &first);
10401         first.analyzing = TRUE;
10402         /*first.maybeThinking = TRUE;*/
10403         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10404         AnalysisPopUp(_("Analysis"),
10405                       _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10406     }
10407     gameMode = AnalyzeFile;
10408     pausing = FALSE;
10409     ModeHighlight();
10410     SetGameInfo();
10411
10412     StartAnalysisClock();
10413     GetTimeMark(&lastNodeCountTime);
10414     lastNodeCount = 0;
10415 }
10416
10417 void
10418 MachineWhiteEvent()
10419 {
10420     char buf[MSG_SIZ];
10421     char *bookHit = NULL;
10422
10423     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10424       return;
10425
10426
10427     if (gameMode == PlayFromGameFile ||
10428         gameMode == TwoMachinesPlay  ||
10429         gameMode == Training         ||
10430         gameMode == AnalyzeMode      ||
10431         gameMode == EndOfGame)
10432         EditGameEvent();
10433
10434     if (gameMode == EditPosition)
10435         EditPositionDone();
10436
10437     if (!WhiteOnMove(currentMove)) {
10438         DisplayError(_("It is not White's turn"), 0);
10439         return;
10440     }
10441
10442     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10443       ExitAnalyzeMode();
10444
10445     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10446         gameMode == AnalyzeFile)
10447         TruncateGame();
10448
10449     ResurrectChessProgram();    /* in case it isn't running */
10450     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10451         gameMode = MachinePlaysWhite;
10452         ResetClocks();
10453     } else
10454     gameMode = MachinePlaysWhite;
10455     pausing = FALSE;
10456     ModeHighlight();
10457     SetGameInfo();
10458     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10459     DisplayTitle(buf);
10460     if (first.sendName) {
10461       sprintf(buf, "name %s\n", gameInfo.black);
10462       SendToProgram(buf, &first);
10463     }
10464     if (first.sendTime) {
10465       if (first.useColors) {
10466         SendToProgram("black\n", &first); /*gnu kludge*/
10467       }
10468       SendTimeRemaining(&first, TRUE);
10469     }
10470     if (first.useColors) {
10471       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10472     }
10473     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10474     SetMachineThinkingEnables();
10475     first.maybeThinking = TRUE;
10476     StartClocks();
10477     firstMove = FALSE;
10478
10479     if (appData.autoFlipView && !flipView) {
10480       flipView = !flipView;
10481       DrawPosition(FALSE, NULL);
10482       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10483     }
10484
10485     if(bookHit) { // [HGM] book: simulate book reply
10486         static char bookMove[MSG_SIZ]; // a bit generous?
10487
10488         programStats.nodes = programStats.depth = programStats.time =
10489         programStats.score = programStats.got_only_move = 0;
10490         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10491
10492         strcpy(bookMove, "move ");
10493         strcat(bookMove, bookHit);
10494         HandleMachineMove(bookMove, &first);
10495     }
10496 }
10497
10498 void
10499 MachineBlackEvent()
10500 {
10501     char buf[MSG_SIZ];
10502    char *bookHit = NULL;
10503
10504     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10505         return;
10506
10507
10508     if (gameMode == PlayFromGameFile ||
10509         gameMode == TwoMachinesPlay  ||
10510         gameMode == Training         ||
10511         gameMode == AnalyzeMode      ||
10512         gameMode == EndOfGame)
10513         EditGameEvent();
10514
10515     if (gameMode == EditPosition)
10516         EditPositionDone();
10517
10518     if (WhiteOnMove(currentMove)) {
10519         DisplayError(_("It is not Black's turn"), 0);
10520         return;
10521     }
10522
10523     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10524       ExitAnalyzeMode();
10525
10526     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10527         gameMode == AnalyzeFile)
10528         TruncateGame();
10529
10530     ResurrectChessProgram();    /* in case it isn't running */
10531     gameMode = MachinePlaysBlack;
10532     pausing = FALSE;
10533     ModeHighlight();
10534     SetGameInfo();
10535     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10536     DisplayTitle(buf);
10537     if (first.sendName) {
10538       sprintf(buf, "name %s\n", gameInfo.white);
10539       SendToProgram(buf, &first);
10540     }
10541     if (first.sendTime) {
10542       if (first.useColors) {
10543         SendToProgram("white\n", &first); /*gnu kludge*/
10544       }
10545       SendTimeRemaining(&first, FALSE);
10546     }
10547     if (first.useColors) {
10548       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10549     }
10550     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10551     SetMachineThinkingEnables();
10552     first.maybeThinking = TRUE;
10553     StartClocks();
10554
10555     if (appData.autoFlipView && flipView) {
10556       flipView = !flipView;
10557       DrawPosition(FALSE, NULL);
10558       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10559     }
10560     if(bookHit) { // [HGM] book: simulate book reply
10561         static char bookMove[MSG_SIZ]; // a bit generous?
10562
10563         programStats.nodes = programStats.depth = programStats.time =
10564         programStats.score = programStats.got_only_move = 0;
10565         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10566
10567         strcpy(bookMove, "move ");
10568         strcat(bookMove, bookHit);
10569         HandleMachineMove(bookMove, &first);
10570     }
10571 }
10572
10573
10574 void
10575 DisplayTwoMachinesTitle()
10576 {
10577     char buf[MSG_SIZ];
10578     if (appData.matchGames > 0) {
10579         if (first.twoMachinesColor[0] == 'w') {
10580             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10581                     gameInfo.white, gameInfo.black,
10582                     first.matchWins, second.matchWins,
10583                     matchGame - 1 - (first.matchWins + second.matchWins));
10584         } else {
10585             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10586                     gameInfo.white, gameInfo.black,
10587                     second.matchWins, first.matchWins,
10588                     matchGame - 1 - (first.matchWins + second.matchWins));
10589         }
10590     } else {
10591         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10592     }
10593     DisplayTitle(buf);
10594 }
10595
10596 void
10597 TwoMachinesEvent P((void))
10598 {
10599     int i;
10600     char buf[MSG_SIZ];
10601     ChessProgramState *onmove;
10602     char *bookHit = NULL;
10603
10604     if (appData.noChessProgram) return;
10605
10606     switch (gameMode) {
10607       case TwoMachinesPlay:
10608         return;
10609       case MachinePlaysWhite:
10610       case MachinePlaysBlack:
10611         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10612             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10613             return;
10614         }
10615         /* fall through */
10616       case BeginningOfGame:
10617       case PlayFromGameFile:
10618       case EndOfGame:
10619         EditGameEvent();
10620         if (gameMode != EditGame) return;
10621         break;
10622       case EditPosition:
10623         EditPositionDone();
10624         break;
10625       case AnalyzeMode:
10626       case AnalyzeFile:
10627         ExitAnalyzeMode();
10628         break;
10629       case EditGame:
10630       default:
10631         break;
10632     }
10633
10634     forwardMostMove = currentMove;
10635     ResurrectChessProgram();    /* in case first program isn't running */
10636
10637     if (second.pr == NULL) {
10638         StartChessProgram(&second);
10639         if (second.protocolVersion == 1) {
10640           TwoMachinesEventIfReady();
10641         } else {
10642           /* kludge: allow timeout for initial "feature" command */
10643           FreezeUI();
10644           DisplayMessage("", _("Starting second chess program"));
10645           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10646         }
10647         return;
10648     }
10649     DisplayMessage("", "");
10650     InitChessProgram(&second, FALSE);
10651     SendToProgram("force\n", &second);
10652     if (startedFromSetupPosition) {
10653         SendBoard(&second, backwardMostMove);
10654     if (appData.debugMode) {
10655         fprintf(debugFP, "Two Machines\n");
10656     }
10657     }
10658     for (i = backwardMostMove; i < forwardMostMove; i++) {
10659         SendMoveToProgram(i, &second);
10660     }
10661
10662     gameMode = TwoMachinesPlay;
10663     pausing = FALSE;
10664     ModeHighlight();
10665     SetGameInfo();
10666     DisplayTwoMachinesTitle();
10667     firstMove = TRUE;
10668     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10669         onmove = &first;
10670     } else {
10671         onmove = &second;
10672     }
10673
10674     SendToProgram(first.computerString, &first);
10675     if (first.sendName) {
10676       sprintf(buf, "name %s\n", second.tidy);
10677       SendToProgram(buf, &first);
10678     }
10679     SendToProgram(second.computerString, &second);
10680     if (second.sendName) {
10681       sprintf(buf, "name %s\n", first.tidy);
10682       SendToProgram(buf, &second);
10683     }
10684
10685     ResetClocks();
10686     if (!first.sendTime || !second.sendTime) {
10687         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10688         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10689     }
10690     if (onmove->sendTime) {
10691       if (onmove->useColors) {
10692         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10693       }
10694       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10695     }
10696     if (onmove->useColors) {
10697       SendToProgram(onmove->twoMachinesColor, onmove);
10698     }
10699     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10700 //    SendToProgram("go\n", onmove);
10701     onmove->maybeThinking = TRUE;
10702     SetMachineThinkingEnables();
10703
10704     StartClocks();
10705
10706     if(bookHit) { // [HGM] book: simulate book reply
10707         static char bookMove[MSG_SIZ]; // a bit generous?
10708
10709         programStats.nodes = programStats.depth = programStats.time =
10710         programStats.score = programStats.got_only_move = 0;
10711         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10712
10713         strcpy(bookMove, "move ");
10714         strcat(bookMove, bookHit);
10715         HandleMachineMove(bookMove, &first);
10716     }
10717 }
10718
10719 void
10720 TrainingEvent()
10721 {
10722     if (gameMode == Training) {
10723       SetTrainingModeOff();
10724       gameMode = PlayFromGameFile;
10725       DisplayMessage("", _("Training mode off"));
10726     } else {
10727       gameMode = Training;
10728       animateTraining = appData.animate;
10729
10730       /* make sure we are not already at the end of the game */
10731       if (currentMove < forwardMostMove) {
10732         SetTrainingModeOn();
10733         DisplayMessage("", _("Training mode on"));
10734       } else {
10735         gameMode = PlayFromGameFile;
10736         DisplayError(_("Already at end of game"), 0);
10737       }
10738     }
10739     ModeHighlight();
10740 }
10741
10742 void
10743 IcsClientEvent()
10744 {
10745     if (!appData.icsActive) return;
10746     switch (gameMode) {
10747       case IcsPlayingWhite:
10748       case IcsPlayingBlack:
10749       case IcsObserving:
10750       case IcsIdle:
10751       case BeginningOfGame:
10752       case IcsExamining:
10753         return;
10754
10755       case EditGame:
10756         break;
10757
10758       case EditPosition:
10759         EditPositionDone();
10760         break;
10761
10762       case AnalyzeMode:
10763       case AnalyzeFile:
10764         ExitAnalyzeMode();
10765         break;
10766
10767       default:
10768         EditGameEvent();
10769         break;
10770     }
10771
10772     gameMode = IcsIdle;
10773     ModeHighlight();
10774     return;
10775 }
10776
10777
10778 void
10779 EditGameEvent()
10780 {
10781     int i;
10782
10783     switch (gameMode) {
10784       case Training:
10785         SetTrainingModeOff();
10786         break;
10787       case MachinePlaysWhite:
10788       case MachinePlaysBlack:
10789       case BeginningOfGame:
10790         SendToProgram("force\n", &first);
10791         SetUserThinkingEnables();
10792         break;
10793       case PlayFromGameFile:
10794         (void) StopLoadGameTimer();
10795         if (gameFileFP != NULL) {
10796             gameFileFP = NULL;
10797         }
10798         break;
10799       case EditPosition:
10800         EditPositionDone();
10801         break;
10802       case AnalyzeMode:
10803       case AnalyzeFile:
10804         ExitAnalyzeMode();
10805         SendToProgram("force\n", &first);
10806         break;
10807       case TwoMachinesPlay:
10808         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10809         ResurrectChessProgram();
10810         SetUserThinkingEnables();
10811         break;
10812       case EndOfGame:
10813         ResurrectChessProgram();
10814         break;
10815       case IcsPlayingBlack:
10816       case IcsPlayingWhite:
10817         DisplayError(_("Warning: You are still playing a game"), 0);
10818         break;
10819       case IcsObserving:
10820         DisplayError(_("Warning: You are still observing a game"), 0);
10821         break;
10822       case IcsExamining:
10823         DisplayError(_("Warning: You are still examining a game"), 0);
10824         break;
10825       case IcsIdle:
10826         break;
10827       case EditGame:
10828       default:
10829         return;
10830     }
10831
10832     pausing = FALSE;
10833     StopClocks();
10834     first.offeredDraw = second.offeredDraw = 0;
10835
10836     if (gameMode == PlayFromGameFile) {
10837         whiteTimeRemaining = timeRemaining[0][currentMove];
10838         blackTimeRemaining = timeRemaining[1][currentMove];
10839         DisplayTitle("");
10840     }
10841
10842     if (gameMode == MachinePlaysWhite ||
10843         gameMode == MachinePlaysBlack ||
10844         gameMode == TwoMachinesPlay ||
10845         gameMode == EndOfGame) {
10846         i = forwardMostMove;
10847         while (i > currentMove) {
10848             SendToProgram("undo\n", &first);
10849             i--;
10850         }
10851         whiteTimeRemaining = timeRemaining[0][currentMove];
10852         blackTimeRemaining = timeRemaining[1][currentMove];
10853         DisplayBothClocks();
10854         if (whiteFlag || blackFlag) {
10855             whiteFlag = blackFlag = 0;
10856         }
10857         DisplayTitle("");
10858     }
10859
10860     gameMode = EditGame;
10861     ModeHighlight();
10862     SetGameInfo();
10863 }
10864
10865
10866 void
10867 EditPositionEvent()
10868 {
10869     if (gameMode == EditPosition) {
10870         EditGameEvent();
10871         return;
10872     }
10873
10874     EditGameEvent();
10875     if (gameMode != EditGame) return;
10876
10877     gameMode = EditPosition;
10878     ModeHighlight();
10879     SetGameInfo();
10880     if (currentMove > 0)
10881       CopyBoard(boards[0], boards[currentMove]);
10882
10883     blackPlaysFirst = !WhiteOnMove(currentMove);
10884     ResetClocks();
10885     currentMove = forwardMostMove = backwardMostMove = 0;
10886     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10887     DisplayMove(-1);
10888 }
10889
10890 void
10891 ExitAnalyzeMode()
10892 {
10893     /* [DM] icsEngineAnalyze - possible call from other functions */
10894     if (appData.icsEngineAnalyze) {
10895         appData.icsEngineAnalyze = FALSE;
10896
10897         DisplayMessage("",_("Close ICS engine analyze..."));
10898     }
10899     if (first.analysisSupport && first.analyzing) {
10900       SendToProgram("exit\n", &first);
10901       first.analyzing = FALSE;
10902     }
10903     AnalysisPopDown();
10904     thinkOutput[0] = NULLCHAR;
10905 }
10906
10907 void
10908 EditPositionDone()
10909 {
10910     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10911
10912     startedFromSetupPosition = TRUE;
10913     InitChessProgram(&first, FALSE);
10914     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10915     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10916         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10917         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10918     } else castlingRights[0][2] = -1;
10919     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10920         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10921         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10922     } else castlingRights[0][5] = -1;
10923     SendToProgram("force\n", &first);
10924     if (blackPlaysFirst) {
10925         strcpy(moveList[0], "");
10926         strcpy(parseList[0], "");
10927         currentMove = forwardMostMove = backwardMostMove = 1;
10928         CopyBoard(boards[1], boards[0]);
10929         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10930         { int i;
10931           epStatus[1] = epStatus[0];
10932           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10933         }
10934     } else {
10935         currentMove = forwardMostMove = backwardMostMove = 0;
10936     }
10937     SendBoard(&first, forwardMostMove);
10938     if (appData.debugMode) {
10939         fprintf(debugFP, "EditPosDone\n");
10940     }
10941     DisplayTitle("");
10942     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10943     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10944     gameMode = EditGame;
10945     ModeHighlight();
10946     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10947     ClearHighlights(); /* [AS] */
10948 }
10949
10950 /* Pause for `ms' milliseconds */
10951 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10952 void
10953 TimeDelay(ms)
10954      long ms;
10955 {
10956     TimeMark m1, m2;
10957
10958     GetTimeMark(&m1);
10959     do {
10960         GetTimeMark(&m2);
10961     } while (SubtractTimeMarks(&m2, &m1) < ms);
10962 }
10963
10964 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10965 void
10966 SendMultiLineToICS(buf)
10967      char *buf;
10968 {
10969     char temp[MSG_SIZ+1], *p;
10970     int len;
10971
10972     len = strlen(buf);
10973     if (len > MSG_SIZ)
10974       len = MSG_SIZ;
10975
10976     strncpy(temp, buf, len);
10977     temp[len] = 0;
10978
10979     p = temp;
10980     while (*p) {
10981         if (*p == '\n' || *p == '\r')
10982           *p = ' ';
10983         ++p;
10984     }
10985
10986     strcat(temp, "\n");
10987     SendToICS(temp);
10988     SendToPlayer(temp, strlen(temp));
10989 }
10990
10991 void
10992 SetWhiteToPlayEvent()
10993 {
10994     if (gameMode == EditPosition) {
10995         blackPlaysFirst = FALSE;
10996         DisplayBothClocks();    /* works because currentMove is 0 */
10997     } else if (gameMode == IcsExamining) {
10998         SendToICS(ics_prefix);
10999         SendToICS("tomove white\n");
11000     }
11001 }
11002
11003 void
11004 SetBlackToPlayEvent()
11005 {
11006     if (gameMode == EditPosition) {
11007         blackPlaysFirst = TRUE;
11008         currentMove = 1;        /* kludge */
11009         DisplayBothClocks();
11010         currentMove = 0;
11011     } else if (gameMode == IcsExamining) {
11012         SendToICS(ics_prefix);
11013         SendToICS("tomove black\n");
11014     }
11015 }
11016
11017 void
11018 EditPositionMenuEvent(selection, x, y)
11019      ChessSquare selection;
11020      int x, y;
11021 {
11022     char buf[MSG_SIZ];
11023     ChessSquare piece = boards[0][y][x];
11024
11025     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11026
11027     switch (selection) {
11028       case ClearBoard:
11029         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11030             SendToICS(ics_prefix);
11031             SendToICS("bsetup clear\n");
11032         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11033             SendToICS(ics_prefix);
11034             SendToICS("clearboard\n");
11035         } else {
11036             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11037                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11038                 for (y = 0; y < BOARD_HEIGHT; y++) {
11039                     if (gameMode == IcsExamining) {
11040                         if (boards[currentMove][y][x] != EmptySquare) {
11041                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11042                                     AAA + x, ONE + y);
11043                             SendToICS(buf);
11044                         }
11045                     } else {
11046                         boards[0][y][x] = p;
11047                     }
11048                 }
11049             }
11050         }
11051         if (gameMode == EditPosition) {
11052             DrawPosition(FALSE, boards[0]);
11053         }
11054         break;
11055
11056       case WhitePlay:
11057         SetWhiteToPlayEvent();
11058         break;
11059
11060       case BlackPlay:
11061         SetBlackToPlayEvent();
11062         break;
11063
11064       case EmptySquare:
11065         if (gameMode == IcsExamining) {
11066             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11067             SendToICS(buf);
11068         } else {
11069             boards[0][y][x] = EmptySquare;
11070             DrawPosition(FALSE, boards[0]);
11071         }
11072         break;
11073
11074       case PromotePiece:
11075         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11076            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11077             selection = (ChessSquare) (PROMOTED piece);
11078         } else if(piece == EmptySquare) selection = WhiteSilver;
11079         else selection = (ChessSquare)((int)piece - 1);
11080         goto defaultlabel;
11081
11082       case DemotePiece:
11083         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11084            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11085             selection = (ChessSquare) (DEMOTED piece);
11086         } else if(piece == EmptySquare) selection = BlackSilver;
11087         else selection = (ChessSquare)((int)piece + 1);
11088         goto defaultlabel;
11089
11090       case WhiteQueen:
11091       case BlackQueen:
11092         if(gameInfo.variant == VariantShatranj ||
11093            gameInfo.variant == VariantXiangqi  ||
11094            gameInfo.variant == VariantCourier    )
11095             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11096         goto defaultlabel;
11097
11098       case WhiteKing:
11099       case BlackKing:
11100         if(gameInfo.variant == VariantXiangqi)
11101             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11102         if(gameInfo.variant == VariantKnightmate)
11103             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11104       default:
11105         defaultlabel:
11106         if (gameMode == IcsExamining) {
11107             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11108                     PieceToChar(selection), AAA + x, ONE + y);
11109             SendToICS(buf);
11110         } else {
11111             boards[0][y][x] = selection;
11112             DrawPosition(FALSE, boards[0]);
11113         }
11114         break;
11115     }
11116 }
11117
11118
11119 void
11120 DropMenuEvent(selection, x, y)
11121      ChessSquare selection;
11122      int x, y;
11123 {
11124     ChessMove moveType;
11125
11126     switch (gameMode) {
11127       case IcsPlayingWhite:
11128       case MachinePlaysBlack:
11129         if (!WhiteOnMove(currentMove)) {
11130             DisplayMoveError(_("It is Black's turn"));
11131             return;
11132         }
11133         moveType = WhiteDrop;
11134         break;
11135       case IcsPlayingBlack:
11136       case MachinePlaysWhite:
11137         if (WhiteOnMove(currentMove)) {
11138             DisplayMoveError(_("It is White's turn"));
11139             return;
11140         }
11141         moveType = BlackDrop;
11142         break;
11143       case EditGame:
11144         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11145         break;
11146       default:
11147         return;
11148     }
11149
11150     if (moveType == BlackDrop && selection < BlackPawn) {
11151       selection = (ChessSquare) ((int) selection
11152                                  + (int) BlackPawn - (int) WhitePawn);
11153     }
11154     if (boards[currentMove][y][x] != EmptySquare) {
11155         DisplayMoveError(_("That square is occupied"));
11156         return;
11157     }
11158
11159     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11160 }
11161
11162 void
11163 AcceptEvent()
11164 {
11165     /* Accept a pending offer of any kind from opponent */
11166
11167     if (appData.icsActive) {
11168         SendToICS(ics_prefix);
11169         SendToICS("accept\n");
11170     } else if (cmailMsgLoaded) {
11171         if (currentMove == cmailOldMove &&
11172             commentList[cmailOldMove] != NULL &&
11173             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11174                    "Black offers a draw" : "White offers a draw")) {
11175             TruncateGame();
11176             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11177             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11178         } else {
11179             DisplayError(_("There is no pending offer on this move"), 0);
11180             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11181         }
11182     } else {
11183         /* Not used for offers from chess program */
11184     }
11185 }
11186
11187 void
11188 DeclineEvent()
11189 {
11190     /* Decline a pending offer of any kind from opponent */
11191
11192     if (appData.icsActive) {
11193         SendToICS(ics_prefix);
11194         SendToICS("decline\n");
11195     } else if (cmailMsgLoaded) {
11196         if (currentMove == cmailOldMove &&
11197             commentList[cmailOldMove] != NULL &&
11198             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11199                    "Black offers a draw" : "White offers a draw")) {
11200 #ifdef NOTDEF
11201             AppendComment(cmailOldMove, "Draw declined");
11202             DisplayComment(cmailOldMove - 1, "Draw declined");
11203 #endif /*NOTDEF*/
11204         } else {
11205             DisplayError(_("There is no pending offer on this move"), 0);
11206         }
11207     } else {
11208         /* Not used for offers from chess program */
11209     }
11210 }
11211
11212 void
11213 RematchEvent()
11214 {
11215     /* Issue ICS rematch command */
11216     if (appData.icsActive) {
11217         SendToICS(ics_prefix);
11218         SendToICS("rematch\n");
11219     }
11220 }
11221
11222 void
11223 CallFlagEvent()
11224 {
11225     /* Call your opponent's flag (claim a win on time) */
11226     if (appData.icsActive) {
11227         SendToICS(ics_prefix);
11228         SendToICS("flag\n");
11229     } else {
11230         switch (gameMode) {
11231           default:
11232             return;
11233           case MachinePlaysWhite:
11234             if (whiteFlag) {
11235                 if (blackFlag)
11236                   GameEnds(GameIsDrawn, "Both players ran out of time",
11237                            GE_PLAYER);
11238                 else
11239                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11240             } else {
11241                 DisplayError(_("Your opponent is not out of time"), 0);
11242             }
11243             break;
11244           case MachinePlaysBlack:
11245             if (blackFlag) {
11246                 if (whiteFlag)
11247                   GameEnds(GameIsDrawn, "Both players ran out of time",
11248                            GE_PLAYER);
11249                 else
11250                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11251             } else {
11252                 DisplayError(_("Your opponent is not out of time"), 0);
11253             }
11254             break;
11255         }
11256     }
11257 }
11258
11259 void
11260 DrawEvent()
11261 {
11262     /* Offer draw or accept pending draw offer from opponent */
11263
11264     if (appData.icsActive) {
11265         /* Note: tournament rules require draw offers to be
11266            made after you make your move but before you punch
11267            your clock.  Currently ICS doesn't let you do that;
11268            instead, you immediately punch your clock after making
11269            a move, but you can offer a draw at any time. */
11270
11271         SendToICS(ics_prefix);
11272         SendToICS("draw\n");
11273     } else if (cmailMsgLoaded) {
11274         if (currentMove == cmailOldMove &&
11275             commentList[cmailOldMove] != NULL &&
11276             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11277                    "Black offers a draw" : "White offers a draw")) {
11278             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11279             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11280         } else if (currentMove == cmailOldMove + 1) {
11281             char *offer = WhiteOnMove(cmailOldMove) ?
11282               "White offers a draw" : "Black offers a draw";
11283             AppendComment(currentMove, offer);
11284             DisplayComment(currentMove - 1, offer);
11285             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11286         } else {
11287             DisplayError(_("You must make your move before offering a draw"), 0);
11288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11289         }
11290     } else if (first.offeredDraw) {
11291         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11292     } else {
11293         if (first.sendDrawOffers) {
11294             SendToProgram("draw\n", &first);
11295             userOfferedDraw = TRUE;
11296         }
11297     }
11298 }
11299
11300 void
11301 AdjournEvent()
11302 {
11303     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11304
11305     if (appData.icsActive) {
11306         SendToICS(ics_prefix);
11307         SendToICS("adjourn\n");
11308     } else {
11309         /* Currently GNU Chess doesn't offer or accept Adjourns */
11310     }
11311 }
11312
11313
11314 void
11315 AbortEvent()
11316 {
11317     /* Offer Abort or accept pending Abort offer from opponent */
11318
11319     if (appData.icsActive) {
11320         SendToICS(ics_prefix);
11321         SendToICS("abort\n");
11322     } else {
11323         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11324     }
11325 }
11326
11327 void
11328 ResignEvent()
11329 {
11330     /* Resign.  You can do this even if it's not your turn. */
11331
11332     if (appData.icsActive) {
11333         SendToICS(ics_prefix);
11334         SendToICS("resign\n");
11335     } else {
11336         switch (gameMode) {
11337           case MachinePlaysWhite:
11338             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11339             break;
11340           case MachinePlaysBlack:
11341             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11342             break;
11343           case EditGame:
11344             if (cmailMsgLoaded) {
11345                 TruncateGame();
11346                 if (WhiteOnMove(cmailOldMove)) {
11347                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11348                 } else {
11349                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11350                 }
11351                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11352             }
11353             break;
11354           default:
11355             break;
11356         }
11357     }
11358 }
11359
11360
11361 void
11362 StopObservingEvent()
11363 {
11364     /* Stop observing current games */
11365     SendToICS(ics_prefix);
11366     SendToICS("unobserve\n");
11367 }
11368
11369 void
11370 StopExaminingEvent()
11371 {
11372     /* Stop observing current game */
11373     SendToICS(ics_prefix);
11374     SendToICS("unexamine\n");
11375 }
11376
11377 void
11378 ForwardInner(target)
11379      int target;
11380 {
11381     int limit;
11382
11383     if (appData.debugMode)
11384         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11385                 target, currentMove, forwardMostMove);
11386
11387     if (gameMode == EditPosition)
11388       return;
11389
11390     if (gameMode == PlayFromGameFile && !pausing)
11391       PauseEvent();
11392
11393     if (gameMode == IcsExamining && pausing)
11394       limit = pauseExamForwardMostMove;
11395     else
11396       limit = forwardMostMove;
11397
11398     if (target > limit) target = limit;
11399
11400     if (target > 0 && moveList[target - 1][0]) {
11401         int fromX, fromY, toX, toY;
11402         toX = moveList[target - 1][2] - AAA;
11403         toY = moveList[target - 1][3] - ONE;
11404         if (moveList[target - 1][1] == '@') {
11405             if (appData.highlightLastMove) {
11406                 SetHighlights(-1, -1, toX, toY);
11407             }
11408         } else {
11409             fromX = moveList[target - 1][0] - AAA;
11410             fromY = moveList[target - 1][1] - ONE;
11411             if (target == currentMove + 1) {
11412                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11413             }
11414             if (appData.highlightLastMove) {
11415                 SetHighlights(fromX, fromY, toX, toY);
11416             }
11417         }
11418     }
11419     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11420         gameMode == Training || gameMode == PlayFromGameFile ||
11421         gameMode == AnalyzeFile) {
11422         while (currentMove < target) {
11423             SendMoveToProgram(currentMove++, &first);
11424         }
11425     } else {
11426         currentMove = target;
11427     }
11428
11429     if (gameMode == EditGame || gameMode == EndOfGame) {
11430         whiteTimeRemaining = timeRemaining[0][currentMove];
11431         blackTimeRemaining = timeRemaining[1][currentMove];
11432     }
11433     DisplayBothClocks();
11434     DisplayMove(currentMove - 1);
11435     DrawPosition(FALSE, boards[currentMove]);
11436     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11437     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11438         DisplayComment(currentMove - 1, commentList[currentMove]);
11439     }
11440 }
11441
11442
11443 void
11444 ForwardEvent()
11445 {
11446     if (gameMode == IcsExamining && !pausing) {
11447         SendToICS(ics_prefix);
11448         SendToICS("forward\n");
11449     } else {
11450         ForwardInner(currentMove + 1);
11451     }
11452 }
11453
11454 void
11455 ToEndEvent()
11456 {
11457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11458         /* to optimze, we temporarily turn off analysis mode while we feed
11459          * the remaining moves to the engine. Otherwise we get analysis output
11460          * after each move.
11461          */
11462         if (first.analysisSupport) {
11463           SendToProgram("exit\nforce\n", &first);
11464           first.analyzing = FALSE;
11465         }
11466     }
11467
11468     if (gameMode == IcsExamining && !pausing) {
11469         SendToICS(ics_prefix);
11470         SendToICS("forward 999999\n");
11471     } else {
11472         ForwardInner(forwardMostMove);
11473     }
11474
11475     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11476         /* we have fed all the moves, so reactivate analysis mode */
11477         SendToProgram("analyze\n", &first);
11478         first.analyzing = TRUE;
11479         /*first.maybeThinking = TRUE;*/
11480         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11481     }
11482 }
11483
11484 void
11485 BackwardInner(target)
11486      int target;
11487 {
11488     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11489
11490     if (appData.debugMode)
11491         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11492                 target, currentMove, forwardMostMove);
11493
11494     if (gameMode == EditPosition) return;
11495     if (currentMove <= backwardMostMove) {
11496         ClearHighlights();
11497         DrawPosition(full_redraw, boards[currentMove]);
11498         return;
11499     }
11500     if (gameMode == PlayFromGameFile && !pausing)
11501       PauseEvent();
11502
11503     if (moveList[target][0]) {
11504         int fromX, fromY, toX, toY;
11505         toX = moveList[target][2] - AAA;
11506         toY = moveList[target][3] - ONE;
11507         if (moveList[target][1] == '@') {
11508             if (appData.highlightLastMove) {
11509                 SetHighlights(-1, -1, toX, toY);
11510             }
11511         } else {
11512             fromX = moveList[target][0] - AAA;
11513             fromY = moveList[target][1] - ONE;
11514             if (target == currentMove - 1) {
11515                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11516             }
11517             if (appData.highlightLastMove) {
11518                 SetHighlights(fromX, fromY, toX, toY);
11519             }
11520         }
11521     }
11522     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11523         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11524         while (currentMove > target) {
11525             SendToProgram("undo\n", &first);
11526             currentMove--;
11527         }
11528     } else {
11529         currentMove = target;
11530     }
11531
11532     if (gameMode == EditGame || gameMode == EndOfGame) {
11533         whiteTimeRemaining = timeRemaining[0][currentMove];
11534         blackTimeRemaining = timeRemaining[1][currentMove];
11535     }
11536     DisplayBothClocks();
11537     DisplayMove(currentMove - 1);
11538     DrawPosition(full_redraw, boards[currentMove]);
11539     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11540     // [HGM] PV info: routine tests if comment empty
11541     DisplayComment(currentMove - 1, commentList[currentMove]);
11542 }
11543
11544 void
11545 BackwardEvent()
11546 {
11547     if (gameMode == IcsExamining && !pausing) {
11548         SendToICS(ics_prefix);
11549         SendToICS("backward\n");
11550     } else {
11551         BackwardInner(currentMove - 1);
11552     }
11553 }
11554
11555 void
11556 ToStartEvent()
11557 {
11558     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11559         /* to optimze, we temporarily turn off analysis mode while we undo
11560          * all the moves. Otherwise we get analysis output after each undo.
11561          */
11562         if (first.analysisSupport) {
11563           SendToProgram("exit\nforce\n", &first);
11564           first.analyzing = FALSE;
11565         }
11566     }
11567
11568     if (gameMode == IcsExamining && !pausing) {
11569         SendToICS(ics_prefix);
11570         SendToICS("backward 999999\n");
11571     } else {
11572         BackwardInner(backwardMostMove);
11573     }
11574
11575     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11576         /* we have fed all the moves, so reactivate analysis mode */
11577         SendToProgram("analyze\n", &first);
11578         first.analyzing = TRUE;
11579         /*first.maybeThinking = TRUE;*/
11580         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11581     }
11582 }
11583
11584 void
11585 ToNrEvent(int to)
11586 {
11587   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11588   if (to >= forwardMostMove) to = forwardMostMove;
11589   if (to <= backwardMostMove) to = backwardMostMove;
11590   if (to < currentMove) {
11591     BackwardInner(to);
11592   } else {
11593     ForwardInner(to);
11594   }
11595 }
11596
11597 void
11598 RevertEvent()
11599 {
11600     if (gameMode != IcsExamining) {
11601         DisplayError(_("You are not examining a game"), 0);
11602         return;
11603     }
11604     if (pausing) {
11605         DisplayError(_("You can't revert while pausing"), 0);
11606         return;
11607     }
11608     SendToICS(ics_prefix);
11609     SendToICS("revert\n");
11610 }
11611
11612 void
11613 RetractMoveEvent()
11614 {
11615     switch (gameMode) {
11616       case MachinePlaysWhite:
11617       case MachinePlaysBlack:
11618         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11619             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11620             return;
11621         }
11622         if (forwardMostMove < 2) return;
11623         currentMove = forwardMostMove = forwardMostMove - 2;
11624         whiteTimeRemaining = timeRemaining[0][currentMove];
11625         blackTimeRemaining = timeRemaining[1][currentMove];
11626         DisplayBothClocks();
11627         DisplayMove(currentMove - 1);
11628         ClearHighlights();/*!! could figure this out*/
11629         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11630         SendToProgram("remove\n", &first);
11631         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11632         break;
11633
11634       case BeginningOfGame:
11635       default:
11636         break;
11637
11638       case IcsPlayingWhite:
11639       case IcsPlayingBlack:
11640         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11641             SendToICS(ics_prefix);
11642             SendToICS("takeback 2\n");
11643         } else {
11644             SendToICS(ics_prefix);
11645             SendToICS("takeback 1\n");
11646         }
11647         break;
11648     }
11649 }
11650
11651 void
11652 MoveNowEvent()
11653 {
11654     ChessProgramState *cps;
11655
11656     switch (gameMode) {
11657       case MachinePlaysWhite:
11658         if (!WhiteOnMove(forwardMostMove)) {
11659             DisplayError(_("It is your turn"), 0);
11660             return;
11661         }
11662         cps = &first;
11663         break;
11664       case MachinePlaysBlack:
11665         if (WhiteOnMove(forwardMostMove)) {
11666             DisplayError(_("It is your turn"), 0);
11667             return;
11668         }
11669         cps = &first;
11670         break;
11671       case TwoMachinesPlay:
11672         if (WhiteOnMove(forwardMostMove) ==
11673             (first.twoMachinesColor[0] == 'w')) {
11674             cps = &first;
11675         } else {
11676             cps = &second;
11677         }
11678         break;
11679       case BeginningOfGame:
11680       default:
11681         return;
11682     }
11683     SendToProgram("?\n", cps);
11684 }
11685
11686 void
11687 TruncateGameEvent()
11688 {
11689     EditGameEvent();
11690     if (gameMode != EditGame) return;
11691     TruncateGame();
11692 }
11693
11694 void
11695 TruncateGame()
11696 {
11697     if (forwardMostMove > currentMove) {
11698         if (gameInfo.resultDetails != NULL) {
11699             free(gameInfo.resultDetails);
11700             gameInfo.resultDetails = NULL;
11701             gameInfo.result = GameUnfinished;
11702         }
11703         forwardMostMove = currentMove;
11704         HistorySet(parseList, backwardMostMove, forwardMostMove,
11705                    currentMove-1);
11706     }
11707 }
11708
11709 void
11710 HintEvent()
11711 {
11712     if (appData.noChessProgram) return;
11713     switch (gameMode) {
11714       case MachinePlaysWhite:
11715         if (WhiteOnMove(forwardMostMove)) {
11716             DisplayError(_("Wait until your turn"), 0);
11717             return;
11718         }
11719         break;
11720       case BeginningOfGame:
11721       case MachinePlaysBlack:
11722         if (!WhiteOnMove(forwardMostMove)) {
11723             DisplayError(_("Wait until your turn"), 0);
11724             return;
11725         }
11726         break;
11727       default:
11728         DisplayError(_("No hint available"), 0);
11729         return;
11730     }
11731     SendToProgram("hint\n", &first);
11732     hintRequested = TRUE;
11733 }
11734
11735 void
11736 BookEvent()
11737 {
11738     if (appData.noChessProgram) return;
11739     switch (gameMode) {
11740       case MachinePlaysWhite:
11741         if (WhiteOnMove(forwardMostMove)) {
11742             DisplayError(_("Wait until your turn"), 0);
11743             return;
11744         }
11745         break;
11746       case BeginningOfGame:
11747       case MachinePlaysBlack:
11748         if (!WhiteOnMove(forwardMostMove)) {
11749             DisplayError(_("Wait until your turn"), 0);
11750             return;
11751         }
11752         break;
11753       case EditPosition:
11754         EditPositionDone();
11755         break;
11756       case TwoMachinesPlay:
11757         return;
11758       default:
11759         break;
11760     }
11761     SendToProgram("bk\n", &first);
11762     bookOutput[0] = NULLCHAR;
11763     bookRequested = TRUE;
11764 }
11765
11766 void
11767 AboutGameEvent()
11768 {
11769     char *tags = PGNTags(&gameInfo);
11770     TagsPopUp(tags, CmailMsg());
11771     free(tags);
11772 }
11773
11774 /* end button procedures */
11775
11776 void
11777 PrintPosition(fp, move)
11778      FILE *fp;
11779      int move;
11780 {
11781     int i, j;
11782
11783     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11784         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11785             char c = PieceToChar(boards[move][i][j]);
11786             fputc(c == 'x' ? '.' : c, fp);
11787             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11788         }
11789     }
11790     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11791       fprintf(fp, "white to play\n");
11792     else
11793       fprintf(fp, "black to play\n");
11794 }
11795
11796 void
11797 PrintOpponents(fp)
11798      FILE *fp;
11799 {
11800     if (gameInfo.white != NULL) {
11801         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11802     } else {
11803         fprintf(fp, "\n");
11804     }
11805 }
11806
11807 /* Find last component of program's own name, using some heuristics */
11808 void
11809 TidyProgramName(prog, host, buf)
11810      char *prog, *host, buf[MSG_SIZ];
11811 {
11812     char *p, *q;
11813     int local = (strcmp(host, "localhost") == 0);
11814     while (!local && (p = strchr(prog, ';')) != NULL) {
11815         p++;
11816         while (*p == ' ') p++;
11817         prog = p;
11818     }
11819     if (*prog == '"' || *prog == '\'') {
11820         q = strchr(prog + 1, *prog);
11821     } else {
11822         q = strchr(prog, ' ');
11823     }
11824     if (q == NULL) q = prog + strlen(prog);
11825     p = q;
11826     while (p >= prog && *p != '/' && *p != '\\') p--;
11827     p++;
11828     if(p == prog && *p == '"') p++;
11829     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11830     memcpy(buf, p, q - p);
11831     buf[q - p] = NULLCHAR;
11832     if (!local) {
11833         strcat(buf, "@");
11834         strcat(buf, host);
11835     }
11836 }
11837
11838 char *
11839 TimeControlTagValue()
11840 {
11841     char buf[MSG_SIZ];
11842     if (!appData.clockMode) {
11843         strcpy(buf, "-");
11844     } else if (movesPerSession > 0) {
11845         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11846     } else if (timeIncrement == 0) {
11847         sprintf(buf, "%ld", timeControl/1000);
11848     } else {
11849         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11850     }
11851     return StrSave(buf);
11852 }
11853
11854 void
11855 SetGameInfo()
11856 {
11857     /* This routine is used only for certain modes */
11858     VariantClass v = gameInfo.variant;
11859     ClearGameInfo(&gameInfo);
11860     gameInfo.variant = v;
11861
11862     switch (gameMode) {
11863       case MachinePlaysWhite:
11864         gameInfo.event = StrSave( appData.pgnEventHeader );
11865         gameInfo.site = StrSave(HostName());
11866         gameInfo.date = PGNDate();
11867         gameInfo.round = StrSave("-");
11868         gameInfo.white = StrSave(first.tidy);
11869         gameInfo.black = StrSave(UserName());
11870         gameInfo.timeControl = TimeControlTagValue();
11871         break;
11872
11873       case MachinePlaysBlack:
11874         gameInfo.event = StrSave( appData.pgnEventHeader );
11875         gameInfo.site = StrSave(HostName());
11876         gameInfo.date = PGNDate();
11877         gameInfo.round = StrSave("-");
11878         gameInfo.white = StrSave(UserName());
11879         gameInfo.black = StrSave(first.tidy);
11880         gameInfo.timeControl = TimeControlTagValue();
11881         break;
11882
11883       case TwoMachinesPlay:
11884         gameInfo.event = StrSave( appData.pgnEventHeader );
11885         gameInfo.site = StrSave(HostName());
11886         gameInfo.date = PGNDate();
11887         if (matchGame > 0) {
11888             char buf[MSG_SIZ];
11889             sprintf(buf, "%d", matchGame);
11890             gameInfo.round = StrSave(buf);
11891         } else {
11892             gameInfo.round = StrSave("-");
11893         }
11894         if (first.twoMachinesColor[0] == 'w') {
11895             gameInfo.white = StrSave(first.tidy);
11896             gameInfo.black = StrSave(second.tidy);
11897         } else {
11898             gameInfo.white = StrSave(second.tidy);
11899             gameInfo.black = StrSave(first.tidy);
11900         }
11901         gameInfo.timeControl = TimeControlTagValue();
11902         break;
11903
11904       case EditGame:
11905         gameInfo.event = StrSave("Edited game");
11906         gameInfo.site = StrSave(HostName());
11907         gameInfo.date = PGNDate();
11908         gameInfo.round = StrSave("-");
11909         gameInfo.white = StrSave("-");
11910         gameInfo.black = StrSave("-");
11911         break;
11912
11913       case EditPosition:
11914         gameInfo.event = StrSave("Edited position");
11915         gameInfo.site = StrSave(HostName());
11916         gameInfo.date = PGNDate();
11917         gameInfo.round = StrSave("-");
11918         gameInfo.white = StrSave("-");
11919         gameInfo.black = StrSave("-");
11920         break;
11921
11922       case IcsPlayingWhite:
11923       case IcsPlayingBlack:
11924       case IcsObserving:
11925       case IcsExamining:
11926         break;
11927
11928       case PlayFromGameFile:
11929         gameInfo.event = StrSave("Game from non-PGN file");
11930         gameInfo.site = StrSave(HostName());
11931         gameInfo.date = PGNDate();
11932         gameInfo.round = StrSave("-");
11933         gameInfo.white = StrSave("?");
11934         gameInfo.black = StrSave("?");
11935         break;
11936
11937       default:
11938         break;
11939     }
11940 }
11941
11942 void
11943 ReplaceComment(index, text)
11944      int index;
11945      char *text;
11946 {
11947     int len;
11948
11949     while (*text == '\n') text++;
11950     len = strlen(text);
11951     while (len > 0 && text[len - 1] == '\n') len--;
11952
11953     if (commentList[index] != NULL)
11954       free(commentList[index]);
11955
11956     if (len == 0) {
11957         commentList[index] = NULL;
11958         return;
11959     }
11960     commentList[index] = (char *) malloc(len + 2);
11961     strncpy(commentList[index], text, len);
11962     commentList[index][len] = '\n';
11963     commentList[index][len + 1] = NULLCHAR;
11964 }
11965
11966 void
11967 CrushCRs(text)
11968      char *text;
11969 {
11970   char *p = text;
11971   char *q = text;
11972   char ch;
11973
11974   do {
11975     ch = *p++;
11976     if (ch == '\r') continue;
11977     *q++ = ch;
11978   } while (ch != '\0');
11979 }
11980
11981 void
11982 AppendComment(index, text)
11983      int index;
11984      char *text;
11985 {
11986     int oldlen, len;
11987     char *old;
11988
11989     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11990
11991     CrushCRs(text);
11992     while (*text == '\n') text++;
11993     len = strlen(text);
11994     while (len > 0 && text[len - 1] == '\n') len--;
11995
11996     if (len == 0) return;
11997
11998     if (commentList[index] != NULL) {
11999         old = commentList[index];
12000         oldlen = strlen(old);
12001         commentList[index] = (char *) malloc(oldlen + len + 2);
12002         strcpy(commentList[index], old);
12003         free(old);
12004         strncpy(&commentList[index][oldlen], text, len);
12005         commentList[index][oldlen + len] = '\n';
12006         commentList[index][oldlen + len + 1] = NULLCHAR;
12007     } else {
12008         commentList[index] = (char *) malloc(len + 2);
12009         strncpy(commentList[index], text, len);
12010         commentList[index][len] = '\n';
12011         commentList[index][len + 1] = NULLCHAR;
12012     }
12013 }
12014
12015 static char * FindStr( char * text, char * sub_text )
12016 {
12017     char * result = strstr( text, sub_text );
12018
12019     if( result != NULL ) {
12020         result += strlen( sub_text );
12021     }
12022
12023     return result;
12024 }
12025
12026 /* [AS] Try to extract PV info from PGN comment */
12027 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12028 char *GetInfoFromComment( int index, char * text )
12029 {
12030     char * sep = text;
12031
12032     if( text != NULL && index > 0 ) {
12033         int score = 0;
12034         int depth = 0;
12035         int time = -1, sec = 0, deci;
12036         char * s_eval = FindStr( text, "[%eval " );
12037         char * s_emt = FindStr( text, "[%emt " );
12038
12039         if( s_eval != NULL || s_emt != NULL ) {
12040             /* New style */
12041             char delim;
12042
12043             if( s_eval != NULL ) {
12044                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12045                     return text;
12046                 }
12047
12048                 if( delim != ']' ) {
12049                     return text;
12050                 }
12051             }
12052
12053             if( s_emt != NULL ) {
12054             }
12055         }
12056         else {
12057             /* We expect something like: [+|-]nnn.nn/dd */
12058             int score_lo = 0;
12059
12060             sep = strchr( text, '/' );
12061             if( sep == NULL || sep < (text+4) ) {
12062                 return text;
12063             }
12064
12065             time = -1; sec = -1; deci = -1;
12066             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12067                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12068                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12069                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12070                 return text;
12071             }
12072
12073             if( score_lo < 0 || score_lo >= 100 ) {
12074                 return text;
12075             }
12076
12077             if(sec >= 0) time = 600*time + 10*sec; else
12078             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12079
12080             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12081
12082             /* [HGM] PV time: now locate end of PV info */
12083             while( *++sep >= '0' && *sep <= '9'); // strip depth
12084             if(time >= 0)
12085             while( *++sep >= '0' && *sep <= '9'); // strip time
12086             if(sec >= 0)
12087             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12088             if(deci >= 0)
12089             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12090             while(*sep == ' ') sep++;
12091         }
12092
12093         if( depth <= 0 ) {
12094             return text;
12095         }
12096
12097         if( time < 0 ) {
12098             time = -1;
12099         }
12100
12101         pvInfoList[index-1].depth = depth;
12102         pvInfoList[index-1].score = score;
12103         pvInfoList[index-1].time  = 10*time; // centi-sec
12104     }
12105     return sep;
12106 }
12107
12108 void
12109 SendToProgram(message, cps)
12110      char *message;
12111      ChessProgramState *cps;
12112 {
12113     int count, outCount, error;
12114     char buf[MSG_SIZ];
12115
12116     if (cps->pr == NULL) return;
12117     Attention(cps);
12118
12119     if (appData.debugMode) {
12120         TimeMark now;
12121         GetTimeMark(&now);
12122         fprintf(debugFP, "%ld >%-6s: %s",
12123                 SubtractTimeMarks(&now, &programStartTime),
12124                 cps->which, message);
12125     }
12126
12127     count = strlen(message);
12128     outCount = OutputToProcess(cps->pr, message, count, &error);
12129     if (outCount < count && !exiting
12130                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12131         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12132         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12133             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12134                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12135                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12136             } else {
12137                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12138             }
12139             gameInfo.resultDetails = buf;
12140         }
12141         DisplayFatalError(buf, error, 1);
12142     }
12143 }
12144
12145 void
12146 ReceiveFromProgram(isr, closure, message, count, error)
12147      InputSourceRef isr;
12148      VOIDSTAR closure;
12149      char *message;
12150      int count;
12151      int error;
12152 {
12153     char *end_str;
12154     char buf[MSG_SIZ];
12155     ChessProgramState *cps = (ChessProgramState *)closure;
12156
12157     if (isr != cps->isr) return; /* Killed intentionally */
12158     if (count <= 0) {
12159         if (count == 0) {
12160             sprintf(buf,
12161                     _("Error: %s chess program (%s) exited unexpectedly"),
12162                     cps->which, cps->program);
12163         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12164                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12165                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12166                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12167                 } else {
12168                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12169                 }
12170                 gameInfo.resultDetails = buf;
12171             }
12172             RemoveInputSource(cps->isr);
12173             DisplayFatalError(buf, 0, 1);
12174         } else {
12175             sprintf(buf,
12176                     _("Error reading from %s chess program (%s)"),
12177                     cps->which, cps->program);
12178             RemoveInputSource(cps->isr);
12179
12180             /* [AS] Program is misbehaving badly... kill it */
12181             if( count == -2 ) {
12182                 DestroyChildProcess( cps->pr, 9 );
12183                 cps->pr = NoProc;
12184             }
12185
12186             DisplayFatalError(buf, error, 1);
12187         }
12188         return;
12189     }
12190
12191     if ((end_str = strchr(message, '\r')) != NULL)
12192       *end_str = NULLCHAR;
12193     if ((end_str = strchr(message, '\n')) != NULL)
12194       *end_str = NULLCHAR;
12195
12196     if (appData.debugMode) {
12197         TimeMark now; int print = 1;
12198         char *quote = ""; char c; int i;
12199
12200         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12201                 char start = message[0];
12202                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12203                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12204                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12205                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12206                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12207                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12208                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12209                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12210                         { quote = "# "; print = (appData.engineComments == 2); }
12211                 message[0] = start; // restore original message
12212         }
12213         if(print) {
12214                 GetTimeMark(&now);
12215                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12216                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12217                         quote,
12218                         message);
12219         }
12220     }
12221
12222     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12223     if (appData.icsEngineAnalyze) {
12224         if (strstr(message, "whisper") != NULL ||
12225              strstr(message, "kibitz") != NULL ||
12226             strstr(message, "tellics") != NULL) return;
12227     }
12228
12229     HandleMachineMove(message, cps);
12230 }
12231
12232
12233 void
12234 SendTimeControl(cps, mps, tc, inc, sd, st)
12235      ChessProgramState *cps;
12236      int mps, inc, sd, st;
12237      long tc;
12238 {
12239     char buf[MSG_SIZ];
12240     int seconds;
12241
12242     if( timeControl_2 > 0 ) {
12243         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12244             tc = timeControl_2;
12245         }
12246     }
12247     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12248     inc /= cps->timeOdds;
12249     st  /= cps->timeOdds;
12250
12251     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12252
12253     if (st > 0) {
12254       /* Set exact time per move, normally using st command */
12255       if (cps->stKludge) {
12256         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12257         seconds = st % 60;
12258         if (seconds == 0) {
12259           sprintf(buf, "level 1 %d\n", st/60);
12260         } else {
12261           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12262         }
12263       } else {
12264         sprintf(buf, "st %d\n", st);
12265       }
12266     } else {
12267       /* Set conventional or incremental time control, using level command */
12268       if (seconds == 0) {
12269         /* Note old gnuchess bug -- minutes:seconds used to not work.
12270            Fixed in later versions, but still avoid :seconds
12271            when seconds is 0. */
12272         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12273       } else {
12274         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12275                 seconds, inc/1000);
12276       }
12277     }
12278     SendToProgram(buf, cps);
12279
12280     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12281     /* Orthogonally, limit search to given depth */
12282     if (sd > 0) {
12283       if (cps->sdKludge) {
12284         sprintf(buf, "depth\n%d\n", sd);
12285       } else {
12286         sprintf(buf, "sd %d\n", sd);
12287       }
12288       SendToProgram(buf, cps);
12289     }
12290
12291     if(cps->nps > 0) { /* [HGM] nps */
12292         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12293         else {
12294                 sprintf(buf, "nps %d\n", cps->nps);
12295               SendToProgram(buf, cps);
12296         }
12297     }
12298 }
12299
12300 ChessProgramState *WhitePlayer()
12301 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12302 {
12303     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12304        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12305         return &second;
12306     return &first;
12307 }
12308
12309 void
12310 SendTimeRemaining(cps, machineWhite)
12311      ChessProgramState *cps;
12312      int /*boolean*/ machineWhite;
12313 {
12314     char message[MSG_SIZ];
12315     long time, otime;
12316
12317     /* Note: this routine must be called when the clocks are stopped
12318        or when they have *just* been set or switched; otherwise
12319        it will be off by the time since the current tick started.
12320     */
12321     if (machineWhite) {
12322         time = whiteTimeRemaining / 10;
12323         otime = blackTimeRemaining / 10;
12324     } else {
12325         time = blackTimeRemaining / 10;
12326         otime = whiteTimeRemaining / 10;
12327     }
12328     /* [HGM] translate opponent's time by time-odds factor */
12329     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12330     if (appData.debugMode) {
12331         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12332     }
12333
12334     if (time <= 0) time = 1;
12335     if (otime <= 0) otime = 1;
12336
12337     sprintf(message, "time %ld\n", time);
12338     SendToProgram(message, cps);
12339
12340     sprintf(message, "otim %ld\n", otime);
12341     SendToProgram(message, cps);
12342 }
12343
12344 int
12345 BoolFeature(p, name, loc, cps)
12346      char **p;
12347      char *name;
12348      int *loc;
12349      ChessProgramState *cps;
12350 {
12351   char buf[MSG_SIZ];
12352   int len = strlen(name);
12353   int val;
12354   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12355     (*p) += len + 1;
12356     sscanf(*p, "%d", &val);
12357     *loc = (val != 0);
12358     while (**p && **p != ' ') (*p)++;
12359     sprintf(buf, "accepted %s\n", name);
12360     SendToProgram(buf, cps);
12361     return TRUE;
12362   }
12363   return FALSE;
12364 }
12365
12366 int
12367 IntFeature(p, name, loc, cps)
12368      char **p;
12369      char *name;
12370      int *loc;
12371      ChessProgramState *cps;
12372 {
12373   char buf[MSG_SIZ];
12374   int len = strlen(name);
12375   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12376     (*p) += len + 1;
12377     sscanf(*p, "%d", loc);
12378     while (**p && **p != ' ') (*p)++;
12379     sprintf(buf, "accepted %s\n", name);
12380     SendToProgram(buf, cps);
12381     return TRUE;
12382   }
12383   return FALSE;
12384 }
12385
12386 int
12387 StringFeature(p, name, loc, cps)
12388      char **p;
12389      char *name;
12390      char loc[];
12391      ChessProgramState *cps;
12392 {
12393   char buf[MSG_SIZ];
12394   int len = strlen(name);
12395   if (strncmp((*p), name, len) == 0
12396       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12397     (*p) += len + 2;
12398     sscanf(*p, "%[^\"]", loc);
12399     while (**p && **p != '\"') (*p)++;
12400     if (**p == '\"') (*p)++;
12401     sprintf(buf, "accepted %s\n", name);
12402     SendToProgram(buf, cps);
12403     return TRUE;
12404   }
12405   return FALSE;
12406 }
12407
12408 int
12409 ParseOption(Option *opt, ChessProgramState *cps)
12410 // [HGM] options: process the string that defines an engine option, and determine
12411 // name, type, default value, and allowed value range
12412 {
12413         char *p, *q, buf[MSG_SIZ];
12414         int n, min = (-1)<<31, max = 1<<31, def;
12415
12416         if(p = strstr(opt->name, " -spin ")) {
12417             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12418             if(max < min) max = min; // enforce consistency
12419             if(def < min) def = min;
12420             if(def > max) def = max;
12421             opt->value = def;
12422             opt->min = min;
12423             opt->max = max;
12424             opt->type = Spin;
12425         } else if((p = strstr(opt->name, " -slider "))) {
12426             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12427             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12428             if(max < min) max = min; // enforce consistency
12429             if(def < min) def = min;
12430             if(def > max) def = max;
12431             opt->value = def;
12432             opt->min = min;
12433             opt->max = max;
12434             opt->type = Spin; // Slider;
12435         } else if((p = strstr(opt->name, " -string "))) {
12436             opt->textValue = p+9;
12437             opt->type = TextBox;
12438         } else if((p = strstr(opt->name, " -file "))) {
12439             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12440             opt->textValue = p+7;
12441             opt->type = TextBox; // FileName;
12442         } else if((p = strstr(opt->name, " -path "))) {
12443             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12444             opt->textValue = p+7;
12445             opt->type = TextBox; // PathName;
12446         } else if(p = strstr(opt->name, " -check ")) {
12447             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12448             opt->value = (def != 0);
12449             opt->type = CheckBox;
12450         } else if(p = strstr(opt->name, " -combo ")) {
12451             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12452             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12453             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12454             opt->value = n = 0;
12455             while(q = StrStr(q, " /// ")) {
12456                 n++; *q = 0;    // count choices, and null-terminate each of them
12457                 q += 5;
12458                 if(*q == '*') { // remember default, which is marked with * prefix
12459                     q++;
12460                     opt->value = n;
12461                 }
12462                 cps->comboList[cps->comboCnt++] = q;
12463             }
12464             cps->comboList[cps->comboCnt++] = NULL;
12465             opt->max = n + 1;
12466             opt->type = ComboBox;
12467         } else if(p = strstr(opt->name, " -button")) {
12468             opt->type = Button;
12469         } else if(p = strstr(opt->name, " -save")) {
12470             opt->type = SaveButton;
12471         } else return FALSE;
12472         *p = 0; // terminate option name
12473         // now look if the command-line options define a setting for this engine option.
12474         if(cps->optionSettings && cps->optionSettings[0])
12475             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12476         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12477                 sprintf(buf, "option %s", p);
12478                 if(p = strstr(buf, ",")) *p = 0;
12479                 strcat(buf, "\n");
12480                 SendToProgram(buf, cps);
12481         }
12482         return TRUE;
12483 }
12484
12485 void
12486 FeatureDone(cps, val)
12487      ChessProgramState* cps;
12488      int val;
12489 {
12490   DelayedEventCallback cb = GetDelayedEvent();
12491   if ((cb == InitBackEnd3 && cps == &first) ||
12492       (cb == TwoMachinesEventIfReady && cps == &second)) {
12493     CancelDelayedEvent();
12494     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12495   }
12496   cps->initDone = val;
12497 }
12498
12499 /* Parse feature command from engine */
12500 void
12501 ParseFeatures(args, cps)
12502      char* args;
12503      ChessProgramState *cps;
12504 {
12505   char *p = args;
12506   char *q;
12507   int val;
12508   char buf[MSG_SIZ];
12509
12510   for (;;) {
12511     while (*p == ' ') p++;
12512     if (*p == NULLCHAR) return;
12513
12514     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12515     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12516     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12517     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12518     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12519     if (BoolFeature(&p, "reuse", &val, cps)) {
12520       /* Engine can disable reuse, but can't enable it if user said no */
12521       if (!val) cps->reuse = FALSE;
12522       continue;
12523     }
12524     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12525     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12526       if (gameMode == TwoMachinesPlay) {
12527         DisplayTwoMachinesTitle();
12528       } else {
12529         DisplayTitle("");
12530       }
12531       continue;
12532     }
12533     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12534     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12535     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12536     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12537     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12538     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12539     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12540     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12541     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12542     if (IntFeature(&p, "done", &val, cps)) {
12543       FeatureDone(cps, val);
12544       continue;
12545     }
12546     /* Added by Tord: */
12547     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12548     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12549     /* End of additions by Tord */
12550
12551     /* [HGM] added features: */
12552     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12553     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12554     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12555     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12556     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12557     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12558     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12559         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12560             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12561             SendToProgram(buf, cps);
12562             continue;
12563         }
12564         if(cps->nrOptions >= MAX_OPTIONS) {
12565             cps->nrOptions--;
12566             sprintf(buf, "%s engine has too many options\n", cps->which);
12567             DisplayError(buf, 0);
12568         }
12569         continue;
12570     }
12571     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12572     /* End of additions by HGM */
12573
12574     /* unknown feature: complain and skip */
12575     q = p;
12576     while (*q && *q != '=') q++;
12577     sprintf(buf, "rejected %.*s\n", q-p, p);
12578     SendToProgram(buf, cps);
12579     p = q;
12580     if (*p == '=') {
12581       p++;
12582       if (*p == '\"') {
12583         p++;
12584         while (*p && *p != '\"') p++;
12585         if (*p == '\"') p++;
12586       } else {
12587         while (*p && *p != ' ') p++;
12588       }
12589     }
12590   }
12591
12592 }
12593
12594 void
12595 PeriodicUpdatesEvent(newState)
12596      int newState;
12597 {
12598     if (newState == appData.periodicUpdates)
12599       return;
12600
12601     appData.periodicUpdates=newState;
12602
12603     /* Display type changes, so update it now */
12604     DisplayAnalysis();
12605
12606     /* Get the ball rolling again... */
12607     if (newState) {
12608         AnalysisPeriodicEvent(1);
12609         StartAnalysisClock();
12610     }
12611 }
12612
12613 void
12614 PonderNextMoveEvent(newState)
12615      int newState;
12616 {
12617     if (newState == appData.ponderNextMove) return;
12618     if (gameMode == EditPosition) EditPositionDone();
12619     if (newState) {
12620         SendToProgram("hard\n", &first);
12621         if (gameMode == TwoMachinesPlay) {
12622             SendToProgram("hard\n", &second);
12623         }
12624     } else {
12625         SendToProgram("easy\n", &first);
12626         thinkOutput[0] = NULLCHAR;
12627         if (gameMode == TwoMachinesPlay) {
12628             SendToProgram("easy\n", &second);
12629         }
12630     }
12631     appData.ponderNextMove = newState;
12632 }
12633
12634 void
12635 NewSettingEvent(option, command, value)
12636      char *command;
12637      int option, value;
12638 {
12639     char buf[MSG_SIZ];
12640
12641     if (gameMode == EditPosition) EditPositionDone();
12642     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12643     SendToProgram(buf, &first);
12644     if (gameMode == TwoMachinesPlay) {
12645         SendToProgram(buf, &second);
12646     }
12647 }
12648
12649 void
12650 ShowThinkingEvent()
12651 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12652 {
12653     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12654     int newState = appData.showThinking
12655         // [HGM] thinking: other features now need thinking output as well
12656         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12657
12658     if (oldState == newState) return;
12659     oldState = newState;
12660     if (gameMode == EditPosition) EditPositionDone();
12661     if (oldState) {
12662         SendToProgram("post\n", &first);
12663         if (gameMode == TwoMachinesPlay) {
12664             SendToProgram("post\n", &second);
12665         }
12666     } else {
12667         SendToProgram("nopost\n", &first);
12668         thinkOutput[0] = NULLCHAR;
12669         if (gameMode == TwoMachinesPlay) {
12670             SendToProgram("nopost\n", &second);
12671         }
12672     }
12673 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12674 }
12675
12676 void
12677 AskQuestionEvent(title, question, replyPrefix, which)
12678      char *title; char *question; char *replyPrefix; char *which;
12679 {
12680   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12681   if (pr == NoProc) return;
12682   AskQuestion(title, question, replyPrefix, pr);
12683 }
12684
12685 void
12686 DisplayMove(moveNumber)
12687      int moveNumber;
12688 {
12689     char message[MSG_SIZ];
12690     char res[MSG_SIZ];
12691     char cpThinkOutput[MSG_SIZ];
12692
12693     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12694
12695     if (moveNumber == forwardMostMove - 1 ||
12696         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12697
12698         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12699
12700         if (strchr(cpThinkOutput, '\n')) {
12701             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12702         }
12703     } else {
12704         *cpThinkOutput = NULLCHAR;
12705     }
12706
12707     /* [AS] Hide thinking from human user */
12708     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12709         *cpThinkOutput = NULLCHAR;
12710         if( thinkOutput[0] != NULLCHAR ) {
12711             int i;
12712
12713             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12714                 cpThinkOutput[i] = '.';
12715             }
12716             cpThinkOutput[i] = NULLCHAR;
12717             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12718         }
12719     }
12720
12721     if (moveNumber == forwardMostMove - 1 &&
12722         gameInfo.resultDetails != NULL) {
12723         if (gameInfo.resultDetails[0] == NULLCHAR) {
12724             sprintf(res, " %s", PGNResult(gameInfo.result));
12725         } else {
12726             sprintf(res, " {%s} %s",
12727                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12728         }
12729     } else {
12730         res[0] = NULLCHAR;
12731     }
12732
12733     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12734         DisplayMessage(res, cpThinkOutput);
12735     } else {
12736         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12737                 WhiteOnMove(moveNumber) ? " " : ".. ",
12738                 parseList[moveNumber], res);
12739         DisplayMessage(message, cpThinkOutput);
12740     }
12741 }
12742
12743 void
12744 DisplayAnalysisText(text)
12745      char *text;
12746 {
12747     char buf[MSG_SIZ];
12748
12749     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12750                || appData.icsEngineAnalyze) {
12751         sprintf(buf, "Analysis (%s)", first.tidy);
12752         AnalysisPopUp(buf, text);
12753     }
12754 }
12755
12756 static int
12757 only_one_move(str)
12758      char *str;
12759 {
12760     while (*str && isspace(*str)) ++str;
12761     while (*str && !isspace(*str)) ++str;
12762     if (!*str) return 1;
12763     while (*str && isspace(*str)) ++str;
12764     if (!*str) return 1;
12765     return 0;
12766 }
12767
12768 void
12769 DisplayAnalysis()
12770 {
12771     char buf[MSG_SIZ];
12772     char lst[MSG_SIZ / 2];
12773     double nps;
12774     static char *xtra[] = { "", " (--)", " (++)" };
12775     int h, m, s, cs;
12776
12777     if (programStats.time == 0) {
12778         programStats.time = 1;
12779     }
12780
12781     if (programStats.got_only_move) {
12782         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12783     } else {
12784         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12785
12786         nps = (u64ToDouble(programStats.nodes) /
12787              ((double)programStats.time /100.0));
12788
12789         cs = programStats.time % 100;
12790         s = programStats.time / 100;
12791         h = (s / (60*60));
12792         s = s - h*60*60;
12793         m = (s/60);
12794         s = s - m*60;
12795
12796         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12797           if (programStats.move_name[0] != NULLCHAR) {
12798             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12799                     programStats.depth,
12800                     programStats.nr_moves-programStats.moves_left,
12801                     programStats.nr_moves, programStats.move_name,
12802                     ((float)programStats.score)/100.0, lst,
12803                     only_one_move(lst)?
12804                     xtra[programStats.got_fail] : "",
12805                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12806           } else {
12807             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12808                     programStats.depth,
12809                     programStats.nr_moves-programStats.moves_left,
12810                     programStats.nr_moves, ((float)programStats.score)/100.0,
12811                     lst,
12812                     only_one_move(lst)?
12813                     xtra[programStats.got_fail] : "",
12814                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12815           }
12816         } else {
12817             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12818                     programStats.depth,
12819                     ((float)programStats.score)/100.0,
12820                     lst,
12821                     only_one_move(lst)?
12822                     xtra[programStats.got_fail] : "",
12823                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12824         }
12825     }
12826     DisplayAnalysisText(buf);
12827 }
12828
12829 void
12830 DisplayComment(moveNumber, text)
12831      int moveNumber;
12832      char *text;
12833 {
12834     char title[MSG_SIZ];
12835     char buf[8000]; // comment can be long!
12836     int score, depth;
12837
12838     if( appData.autoDisplayComment ) {
12839         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12840             strcpy(title, "Comment");
12841         } else {
12842             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12843                     WhiteOnMove(moveNumber) ? " " : ".. ",
12844                     parseList[moveNumber]);
12845         }
12846         // [HGM] PV info: display PV info together with (or as) comment
12847         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12848             if(text == NULL) text = "";
12849             score = pvInfoList[moveNumber].score;
12850             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12851                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12852             text = buf;
12853         }
12854     } else title[0] = 0;
12855
12856     if (text != NULL)
12857         CommentPopUp(title, text);
12858 }
12859
12860 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12861  * might be busy thinking or pondering.  It can be omitted if your
12862  * gnuchess is configured to stop thinking immediately on any user
12863  * input.  However, that gnuchess feature depends on the FIONREAD
12864  * ioctl, which does not work properly on some flavors of Unix.
12865  */
12866 void
12867 Attention(cps)
12868      ChessProgramState *cps;
12869 {
12870 #if ATTENTION
12871     if (!cps->useSigint) return;
12872     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12873     switch (gameMode) {
12874       case MachinePlaysWhite:
12875       case MachinePlaysBlack:
12876       case TwoMachinesPlay:
12877       case IcsPlayingWhite:
12878       case IcsPlayingBlack:
12879       case AnalyzeMode:
12880       case AnalyzeFile:
12881         /* Skip if we know it isn't thinking */
12882         if (!cps->maybeThinking) return;
12883         if (appData.debugMode)
12884           fprintf(debugFP, "Interrupting %s\n", cps->which);
12885         InterruptChildProcess(cps->pr);
12886         cps->maybeThinking = FALSE;
12887         break;
12888       default:
12889         break;
12890     }
12891 #endif /*ATTENTION*/
12892 }
12893
12894 int
12895 CheckFlags()
12896 {
12897     if (whiteTimeRemaining <= 0) {
12898         if (!whiteFlag) {
12899             whiteFlag = TRUE;
12900             if (appData.icsActive) {
12901                 if (appData.autoCallFlag &&
12902                     gameMode == IcsPlayingBlack && !blackFlag) {
12903                   SendToICS(ics_prefix);
12904                   SendToICS("flag\n");
12905                 }
12906             } else {
12907                 if (blackFlag) {
12908                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12909                 } else {
12910                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12911                     if (appData.autoCallFlag) {
12912                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12913                         return TRUE;
12914                     }
12915                 }
12916             }
12917         }
12918     }
12919     if (blackTimeRemaining <= 0) {
12920         if (!blackFlag) {
12921             blackFlag = TRUE;
12922             if (appData.icsActive) {
12923                 if (appData.autoCallFlag &&
12924                     gameMode == IcsPlayingWhite && !whiteFlag) {
12925                   SendToICS(ics_prefix);
12926                   SendToICS("flag\n");
12927                 }
12928             } else {
12929                 if (whiteFlag) {
12930                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12931                 } else {
12932                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12933                     if (appData.autoCallFlag) {
12934                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12935                         return TRUE;
12936                     }
12937                 }
12938             }
12939         }
12940     }
12941     return FALSE;
12942 }
12943
12944 void
12945 CheckTimeControl()
12946 {
12947     if (!appData.clockMode || appData.icsActive ||
12948         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12949
12950     /*
12951      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12952      */
12953     if ( !WhiteOnMove(forwardMostMove) )
12954         /* White made time control */
12955         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12956         /* [HGM] time odds: correct new time quota for time odds! */
12957                                             / WhitePlayer()->timeOdds;
12958       else
12959         /* Black made time control */
12960         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12961                                             / WhitePlayer()->other->timeOdds;
12962 }
12963
12964 void
12965 DisplayBothClocks()
12966 {
12967     int wom = gameMode == EditPosition ?
12968       !blackPlaysFirst : WhiteOnMove(currentMove);
12969     DisplayWhiteClock(whiteTimeRemaining, wom);
12970     DisplayBlackClock(blackTimeRemaining, !wom);
12971 }
12972
12973
12974 /* Timekeeping seems to be a portability nightmare.  I think everyone
12975    has ftime(), but I'm really not sure, so I'm including some ifdefs
12976    to use other calls if you don't.  Clocks will be less accurate if
12977    you have neither ftime nor gettimeofday.
12978 */
12979
12980 /* VS 2008 requires the #include outside of the function */
12981 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12982 #include <sys/timeb.h>
12983 #endif
12984
12985 /* Get the current time as a TimeMark */
12986 void
12987 GetTimeMark(tm)
12988      TimeMark *tm;
12989 {
12990 #if HAVE_GETTIMEOFDAY
12991
12992     struct timeval timeVal;
12993     struct timezone timeZone;
12994
12995     gettimeofday(&timeVal, &timeZone);
12996     tm->sec = (long) timeVal.tv_sec;
12997     tm->ms = (int) (timeVal.tv_usec / 1000L);
12998
12999 #else /*!HAVE_GETTIMEOFDAY*/
13000 #if HAVE_FTIME
13001
13002 // include <sys/timeb.h> / moved to just above start of function
13003     struct timeb timeB;
13004
13005     ftime(&timeB);
13006     tm->sec = (long) timeB.time;
13007     tm->ms = (int) timeB.millitm;
13008
13009 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13010     tm->sec = (long) time(NULL);
13011     tm->ms = 0;
13012 #endif
13013 #endif
13014 }
13015
13016 /* Return the difference in milliseconds between two
13017    time marks.  We assume the difference will fit in a long!
13018 */
13019 long
13020 SubtractTimeMarks(tm2, tm1)
13021      TimeMark *tm2, *tm1;
13022 {
13023     return 1000L*(tm2->sec - tm1->sec) +
13024            (long) (tm2->ms - tm1->ms);
13025 }
13026
13027
13028 /*
13029  * Code to manage the game clocks.
13030  *
13031  * In tournament play, black starts the clock and then white makes a move.
13032  * We give the human user a slight advantage if he is playing white---the
13033  * clocks don't run until he makes his first move, so it takes zero time.
13034  * Also, we don't account for network lag, so we could get out of sync
13035  * with GNU Chess's clock -- but then, referees are always right.
13036  */
13037
13038 static TimeMark tickStartTM;
13039 static long intendedTickLength;
13040
13041 long
13042 NextTickLength(timeRemaining)
13043      long timeRemaining;
13044 {
13045     long nominalTickLength, nextTickLength;
13046
13047     if (timeRemaining > 0L && timeRemaining <= 10000L)
13048       nominalTickLength = 100L;
13049     else
13050       nominalTickLength = 1000L;
13051     nextTickLength = timeRemaining % nominalTickLength;
13052     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13053
13054     return nextTickLength;
13055 }
13056
13057 /* Adjust clock one minute up or down */
13058 void
13059 AdjustClock(Boolean which, int dir)
13060 {
13061     if(which) blackTimeRemaining += 60000*dir;
13062     else      whiteTimeRemaining += 60000*dir;
13063     DisplayBothClocks();
13064 }
13065
13066 /* Stop clocks and reset to a fresh time control */
13067 void
13068 ResetClocks()
13069 {
13070     (void) StopClockTimer();
13071     if (appData.icsActive) {
13072         whiteTimeRemaining = blackTimeRemaining = 0;
13073     } else { /* [HGM] correct new time quote for time odds */
13074         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13075         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13076     }
13077     if (whiteFlag || blackFlag) {
13078         DisplayTitle("");
13079         whiteFlag = blackFlag = FALSE;
13080     }
13081     DisplayBothClocks();
13082 }
13083
13084 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13085
13086 /* Decrement running clock by amount of time that has passed */
13087 void
13088 DecrementClocks()
13089 {
13090     long timeRemaining;
13091     long lastTickLength, fudge;
13092     TimeMark now;
13093
13094     if (!appData.clockMode) return;
13095     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13096
13097     GetTimeMark(&now);
13098
13099     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13100
13101     /* Fudge if we woke up a little too soon */
13102     fudge = intendedTickLength - lastTickLength;
13103     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13104
13105     if (WhiteOnMove(forwardMostMove)) {
13106         if(whiteNPS >= 0) lastTickLength = 0;
13107         timeRemaining = whiteTimeRemaining -= lastTickLength;
13108         DisplayWhiteClock(whiteTimeRemaining - fudge,
13109                           WhiteOnMove(currentMove));
13110     } else {
13111         if(blackNPS >= 0) lastTickLength = 0;
13112         timeRemaining = blackTimeRemaining -= lastTickLength;
13113         DisplayBlackClock(blackTimeRemaining - fudge,
13114                           !WhiteOnMove(currentMove));
13115     }
13116
13117     if (CheckFlags()) return;
13118
13119     tickStartTM = now;
13120     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13121     StartClockTimer(intendedTickLength);
13122
13123     /* if the time remaining has fallen below the alarm threshold, sound the
13124      * alarm. if the alarm has sounded and (due to a takeback or time control
13125      * with increment) the time remaining has increased to a level above the
13126      * threshold, reset the alarm so it can sound again.
13127      */
13128
13129     if (appData.icsActive && appData.icsAlarm) {
13130
13131         /* make sure we are dealing with the user's clock */
13132         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13133                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13134            )) return;
13135
13136         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13137             alarmSounded = FALSE;
13138         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13139             PlayAlarmSound();
13140             alarmSounded = TRUE;
13141         }
13142     }
13143 }
13144
13145
13146 /* A player has just moved, so stop the previously running
13147    clock and (if in clock mode) start the other one.
13148    We redisplay both clocks in case we're in ICS mode, because
13149    ICS gives us an update to both clocks after every move.
13150    Note that this routine is called *after* forwardMostMove
13151    is updated, so the last fractional tick must be subtracted
13152    from the color that is *not* on move now.
13153 */
13154 void
13155 SwitchClocks()
13156 {
13157     long lastTickLength;
13158     TimeMark now;
13159     int flagged = FALSE;
13160
13161     GetTimeMark(&now);
13162
13163     if (StopClockTimer() && appData.clockMode) {
13164         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13165         if (WhiteOnMove(forwardMostMove)) {
13166             if(blackNPS >= 0) lastTickLength = 0;
13167             blackTimeRemaining -= lastTickLength;
13168            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13169 //         if(pvInfoList[forwardMostMove-1].time == -1)
13170                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13171                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13172         } else {
13173            if(whiteNPS >= 0) lastTickLength = 0;
13174            whiteTimeRemaining -= lastTickLength;
13175            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13176 //         if(pvInfoList[forwardMostMove-1].time == -1)
13177                  pvInfoList[forwardMostMove-1].time =
13178                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13179         }
13180         flagged = CheckFlags();
13181     }
13182     CheckTimeControl();
13183
13184     if (flagged || !appData.clockMode) return;
13185
13186     switch (gameMode) {
13187       case MachinePlaysBlack:
13188       case MachinePlaysWhite:
13189       case BeginningOfGame:
13190         if (pausing) return;
13191         break;
13192
13193       case EditGame:
13194       case PlayFromGameFile:
13195       case IcsExamining:
13196         return;
13197
13198       default:
13199         break;
13200     }
13201
13202     tickStartTM = now;
13203     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13204       whiteTimeRemaining : blackTimeRemaining);
13205     StartClockTimer(intendedTickLength);
13206 }
13207
13208
13209 /* Stop both clocks */
13210 void
13211 StopClocks()
13212 {
13213     long lastTickLength;
13214     TimeMark now;
13215
13216     if (!StopClockTimer()) return;
13217     if (!appData.clockMode) return;
13218
13219     GetTimeMark(&now);
13220
13221     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13222     if (WhiteOnMove(forwardMostMove)) {
13223         if(whiteNPS >= 0) lastTickLength = 0;
13224         whiteTimeRemaining -= lastTickLength;
13225         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13226     } else {
13227         if(blackNPS >= 0) lastTickLength = 0;
13228         blackTimeRemaining -= lastTickLength;
13229         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13230     }
13231     CheckFlags();
13232 }
13233
13234 /* Start clock of player on move.  Time may have been reset, so
13235    if clock is already running, stop and restart it. */
13236 void
13237 StartClocks()
13238 {
13239     (void) StopClockTimer(); /* in case it was running already */
13240     DisplayBothClocks();
13241     if (CheckFlags()) return;
13242
13243     if (!appData.clockMode) return;
13244     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13245
13246     GetTimeMark(&tickStartTM);
13247     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13248       whiteTimeRemaining : blackTimeRemaining);
13249
13250    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13251     whiteNPS = blackNPS = -1;
13252     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13253        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13254         whiteNPS = first.nps;
13255     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13256        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13257         blackNPS = first.nps;
13258     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13259         whiteNPS = second.nps;
13260     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13261         blackNPS = second.nps;
13262     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13263
13264     StartClockTimer(intendedTickLength);
13265 }
13266
13267 char *
13268 TimeString(ms)
13269      long ms;
13270 {
13271     long second, minute, hour, day;
13272     char *sign = "";
13273     static char buf[32];
13274
13275     if (ms > 0 && ms <= 9900) {
13276       /* convert milliseconds to tenths, rounding up */
13277       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13278
13279       sprintf(buf, " %03.1f ", tenths/10.0);
13280       return buf;
13281     }
13282
13283     /* convert milliseconds to seconds, rounding up */
13284     /* use floating point to avoid strangeness of integer division
13285        with negative dividends on many machines */
13286     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13287
13288     if (second < 0) {
13289         sign = "-";
13290         second = -second;
13291     }
13292
13293     day = second / (60 * 60 * 24);
13294     second = second % (60 * 60 * 24);
13295     hour = second / (60 * 60);
13296     second = second % (60 * 60);
13297     minute = second / 60;
13298     second = second % 60;
13299
13300     if (day > 0)
13301       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13302               sign, day, hour, minute, second);
13303     else if (hour > 0)
13304       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13305     else
13306       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13307
13308     return buf;
13309 }
13310
13311
13312 /*
13313  * This is necessary because some C libraries aren't ANSI C compliant yet.
13314  */
13315 char *
13316 StrStr(string, match)
13317      char *string, *match;
13318 {
13319     int i, length;
13320
13321     length = strlen(match);
13322
13323     for (i = strlen(string) - length; i >= 0; i--, string++)
13324       if (!strncmp(match, string, length))
13325         return string;
13326
13327     return NULL;
13328 }
13329
13330 char *
13331 StrCaseStr(string, match)
13332      char *string, *match;
13333 {
13334     int i, j, length;
13335
13336     length = strlen(match);
13337
13338     for (i = strlen(string) - length; i >= 0; i--, string++) {
13339         for (j = 0; j < length; j++) {
13340             if (ToLower(match[j]) != ToLower(string[j]))
13341               break;
13342         }
13343         if (j == length) return string;
13344     }
13345
13346     return NULL;
13347 }
13348
13349 #ifndef _amigados
13350 int
13351 StrCaseCmp(s1, s2)
13352      char *s1, *s2;
13353 {
13354     char c1, c2;
13355
13356     for (;;) {
13357         c1 = ToLower(*s1++);
13358         c2 = ToLower(*s2++);
13359         if (c1 > c2) return 1;
13360         if (c1 < c2) return -1;
13361         if (c1 == NULLCHAR) return 0;
13362     }
13363 }
13364
13365
13366 int
13367 ToLower(c)
13368      int c;
13369 {
13370     return isupper(c) ? tolower(c) : c;
13371 }
13372
13373
13374 int
13375 ToUpper(c)
13376      int c;
13377 {
13378     return islower(c) ? toupper(c) : c;
13379 }
13380 #endif /* !_amigados    */
13381
13382 char *
13383 StrSave(s)
13384      char *s;
13385 {
13386     char *ret;
13387
13388     if ((ret = (char *) malloc(strlen(s) + 1))) {
13389         strcpy(ret, s);
13390     }
13391     return ret;
13392 }
13393
13394 char *
13395 StrSavePtr(s, savePtr)
13396      char *s, **savePtr;
13397 {
13398     if (*savePtr) {
13399         free(*savePtr);
13400     }
13401     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13402         strcpy(*savePtr, s);
13403     }
13404     return(*savePtr);
13405 }
13406
13407 char *
13408 PGNDate()
13409 {
13410     time_t clock;
13411     struct tm *tm;
13412     char buf[MSG_SIZ];
13413
13414     clock = time((time_t *)NULL);
13415     tm = localtime(&clock);
13416     sprintf(buf, "%04d.%02d.%02d",
13417             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13418     return StrSave(buf);
13419 }
13420
13421
13422 char *
13423 PositionToFEN(move, overrideCastling)
13424      int move;
13425      char *overrideCastling;
13426 {
13427     int i, j, fromX, fromY, toX, toY;
13428     int whiteToPlay;
13429     char buf[128];
13430     char *p, *q;
13431     int emptycount;
13432     ChessSquare piece;
13433
13434     whiteToPlay = (gameMode == EditPosition) ?
13435       !blackPlaysFirst : (move % 2 == 0);
13436     p = buf;
13437
13438     /* Piece placement data */
13439     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13440         emptycount = 0;
13441         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13442             if (boards[move][i][j] == EmptySquare) {
13443                 emptycount++;
13444             } else { ChessSquare piece = boards[move][i][j];
13445                 if (emptycount > 0) {
13446                     if(emptycount<10) /* [HGM] can be >= 10 */
13447                         *p++ = '0' + emptycount;
13448                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13449                     emptycount = 0;
13450                 }
13451                 if(PieceToChar(piece) == '+') {
13452                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13453                     *p++ = '+';
13454                     piece = (ChessSquare)(DEMOTED piece);
13455                 }
13456                 *p++ = PieceToChar(piece);
13457                 if(p[-1] == '~') {
13458                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13459                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13460                     *p++ = '~';
13461                 }
13462             }
13463         }
13464         if (emptycount > 0) {
13465             if(emptycount<10) /* [HGM] can be >= 10 */
13466                 *p++ = '0' + emptycount;
13467             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13468             emptycount = 0;
13469         }
13470         *p++ = '/';
13471     }
13472     *(p - 1) = ' ';
13473
13474     /* [HGM] print Crazyhouse or Shogi holdings */
13475     if( gameInfo.holdingsWidth ) {
13476         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13477         q = p;
13478         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13479             piece = boards[move][i][BOARD_WIDTH-1];
13480             if( piece != EmptySquare )
13481               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13482                   *p++ = PieceToChar(piece);
13483         }
13484         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13485             piece = boards[move][BOARD_HEIGHT-i-1][0];
13486             if( piece != EmptySquare )
13487               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13488                   *p++ = PieceToChar(piece);
13489         }
13490
13491         if( q == p ) *p++ = '-';
13492         *p++ = ']';
13493         *p++ = ' ';
13494     }
13495
13496     /* Active color */
13497     *p++ = whiteToPlay ? 'w' : 'b';
13498     *p++ = ' ';
13499
13500   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13501     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13502   } else {
13503   if(nrCastlingRights) {
13504      q = p;
13505      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13506        /* [HGM] write directly from rights */
13507            if(castlingRights[move][2] >= 0 &&
13508               castlingRights[move][0] >= 0   )
13509                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13510            if(castlingRights[move][2] >= 0 &&
13511               castlingRights[move][1] >= 0   )
13512                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13513            if(castlingRights[move][5] >= 0 &&
13514               castlingRights[move][3] >= 0   )
13515                 *p++ = castlingRights[move][3] + AAA;
13516            if(castlingRights[move][5] >= 0 &&
13517               castlingRights[move][4] >= 0   )
13518                 *p++ = castlingRights[move][4] + AAA;
13519      } else {
13520
13521         /* [HGM] write true castling rights */
13522         if( nrCastlingRights == 6 ) {
13523             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13524                castlingRights[move][2] >= 0  ) *p++ = 'K';
13525             if(castlingRights[move][1] == BOARD_LEFT &&
13526                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13527             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13528                castlingRights[move][5] >= 0  ) *p++ = 'k';
13529             if(castlingRights[move][4] == BOARD_LEFT &&
13530                castlingRights[move][5] >= 0  ) *p++ = 'q';
13531         }
13532      }
13533      if (q == p) *p++ = '-'; /* No castling rights */
13534      *p++ = ' ';
13535   }
13536
13537   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13538      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13539     /* En passant target square */
13540     if (move > backwardMostMove) {
13541         fromX = moveList[move - 1][0] - AAA;
13542         fromY = moveList[move - 1][1] - ONE;
13543         toX = moveList[move - 1][2] - AAA;
13544         toY = moveList[move - 1][3] - ONE;
13545         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13546             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13547             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13548             fromX == toX) {
13549             /* 2-square pawn move just happened */
13550             *p++ = toX + AAA;
13551             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13552         } else {
13553             *p++ = '-';
13554         }
13555     } else {
13556         *p++ = '-';
13557     }
13558     *p++ = ' ';
13559   }
13560   }
13561
13562     /* [HGM] find reversible plies */
13563     {   int i = 0, j=move;
13564
13565         if (appData.debugMode) { int k;
13566             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13567             for(k=backwardMostMove; k<=forwardMostMove; k++)
13568                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13569
13570         }
13571
13572         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13573         if( j == backwardMostMove ) i += initialRulePlies;
13574         sprintf(p, "%d ", i);
13575         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13576     }
13577     /* Fullmove number */
13578     sprintf(p, "%d", (move / 2) + 1);
13579
13580     return StrSave(buf);
13581 }
13582
13583 Boolean
13584 ParseFEN(board, blackPlaysFirst, fen)
13585     Board board;
13586      int *blackPlaysFirst;
13587      char *fen;
13588 {
13589     int i, j;
13590     char *p;
13591     int emptycount;
13592     ChessSquare piece;
13593
13594     p = fen;
13595
13596     /* [HGM] by default clear Crazyhouse holdings, if present */
13597     if(gameInfo.holdingsWidth) {
13598        for(i=0; i<BOARD_HEIGHT; i++) {
13599            board[i][0]             = EmptySquare; /* black holdings */
13600            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13601            board[i][1]             = (ChessSquare) 0; /* black counts */
13602            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13603        }
13604     }
13605
13606     /* Piece placement data */
13607     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13608         j = 0;
13609         for (;;) {
13610             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13611                 if (*p == '/') p++;
13612                 emptycount = gameInfo.boardWidth - j;
13613                 while (emptycount--)
13614                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13615                 break;
13616 #if(BOARD_SIZE >= 10)
13617             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13618                 p++; emptycount=10;
13619                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13620                 while (emptycount--)
13621                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13622 #endif
13623             } else if (isdigit(*p)) {
13624                 emptycount = *p++ - '0';
13625                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13626                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13627                 while (emptycount--)
13628                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13629             } else if (*p == '+' || isalpha(*p)) {
13630                 if (j >= gameInfo.boardWidth) return FALSE;
13631                 if(*p=='+') {
13632                     piece = CharToPiece(*++p);
13633                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13634                     piece = (ChessSquare) (PROMOTED piece ); p++;
13635                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13636                 } else piece = CharToPiece(*p++);
13637
13638                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13639                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13640                     piece = (ChessSquare) (PROMOTED piece);
13641                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13642                     p++;
13643                 }
13644                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13645             } else {
13646                 return FALSE;
13647             }
13648         }
13649     }
13650     while (*p == '/' || *p == ' ') p++;
13651
13652     /* [HGM] look for Crazyhouse holdings here */
13653     while(*p==' ') p++;
13654     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13655         if(*p == '[') p++;
13656         if(*p == '-' ) *p++; /* empty holdings */ else {
13657             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13658             /* if we would allow FEN reading to set board size, we would   */
13659             /* have to add holdings and shift the board read so far here   */
13660             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13661                 *p++;
13662                 if((int) piece >= (int) BlackPawn ) {
13663                     i = (int)piece - (int)BlackPawn;
13664                     i = PieceToNumber((ChessSquare)i);
13665                     if( i >= gameInfo.holdingsSize ) return FALSE;
13666                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13667                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13668                 } else {
13669                     i = (int)piece - (int)WhitePawn;
13670                     i = PieceToNumber((ChessSquare)i);
13671                     if( i >= gameInfo.holdingsSize ) return FALSE;
13672                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13673                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13674                 }
13675             }
13676         }
13677         if(*p == ']') *p++;
13678     }
13679
13680     while(*p == ' ') p++;
13681
13682     /* Active color */
13683     switch (*p++) {
13684       case 'w':
13685         *blackPlaysFirst = FALSE;
13686         break;
13687       case 'b':
13688         *blackPlaysFirst = TRUE;
13689         break;
13690       default:
13691         return FALSE;
13692     }
13693
13694     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13695     /* return the extra info in global variiables             */
13696
13697     /* set defaults in case FEN is incomplete */
13698     FENepStatus = EP_UNKNOWN;
13699     for(i=0; i<nrCastlingRights; i++ ) {
13700         FENcastlingRights[i] =
13701             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13702     }   /* assume possible unless obviously impossible */
13703     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13704     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13705     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13706     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13707     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13708     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13709     FENrulePlies = 0;
13710
13711     while(*p==' ') p++;
13712     if(nrCastlingRights) {
13713       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13714           /* castling indicator present, so default becomes no castlings */
13715           for(i=0; i<nrCastlingRights; i++ ) {
13716                  FENcastlingRights[i] = -1;
13717           }
13718       }
13719       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13720              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13721              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13722              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13723         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13724
13725         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13726             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13727             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13728         }
13729         switch(c) {
13730           case'K':
13731               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13732               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13733               FENcastlingRights[2] = whiteKingFile;
13734               break;
13735           case'Q':
13736               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13737               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13738               FENcastlingRights[2] = whiteKingFile;
13739               break;
13740           case'k':
13741               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13742               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13743               FENcastlingRights[5] = blackKingFile;
13744               break;
13745           case'q':
13746               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13747               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13748               FENcastlingRights[5] = blackKingFile;
13749           case '-':
13750               break;
13751           default: /* FRC castlings */
13752               if(c >= 'a') { /* black rights */
13753                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13754                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13755                   if(i == BOARD_RGHT) break;
13756                   FENcastlingRights[5] = i;
13757                   c -= AAA;
13758                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13759                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13760                   if(c > i)
13761                       FENcastlingRights[3] = c;
13762                   else
13763                       FENcastlingRights[4] = c;
13764               } else { /* white rights */
13765                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13766                     if(board[0][i] == WhiteKing) break;
13767                   if(i == BOARD_RGHT) break;
13768                   FENcastlingRights[2] = i;
13769                   c -= AAA - 'a' + 'A';
13770                   if(board[0][c] >= WhiteKing) break;
13771                   if(c > i)
13772                       FENcastlingRights[0] = c;
13773                   else
13774                       FENcastlingRights[1] = c;
13775               }
13776         }
13777       }
13778     if (appData.debugMode) {
13779         fprintf(debugFP, "FEN castling rights:");
13780         for(i=0; i<nrCastlingRights; i++)
13781         fprintf(debugFP, " %d", FENcastlingRights[i]);
13782         fprintf(debugFP, "\n");
13783     }
13784
13785       while(*p==' ') p++;
13786     }
13787
13788     /* read e.p. field in games that know e.p. capture */
13789     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13790        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13791       if(*p=='-') {
13792         p++; FENepStatus = EP_NONE;
13793       } else {
13794          char c = *p++ - AAA;
13795
13796          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13797          if(*p >= '0' && *p <='9') *p++;
13798          FENepStatus = c;
13799       }
13800     }
13801
13802
13803     if(sscanf(p, "%d", &i) == 1) {
13804         FENrulePlies = i; /* 50-move ply counter */
13805         /* (The move number is still ignored)    */
13806     }
13807
13808     return TRUE;
13809 }
13810
13811 void
13812 EditPositionPasteFEN(char *fen)
13813 {
13814   if (fen != NULL) {
13815     Board initial_position;
13816
13817     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13818       DisplayError(_("Bad FEN position in clipboard"), 0);
13819       return ;
13820     } else {
13821       int savedBlackPlaysFirst = blackPlaysFirst;
13822       EditPositionEvent();
13823       blackPlaysFirst = savedBlackPlaysFirst;
13824       CopyBoard(boards[0], initial_position);
13825           /* [HGM] copy FEN attributes as well */
13826           {   int i;
13827               initialRulePlies = FENrulePlies;
13828               epStatus[0] = FENepStatus;
13829               for( i=0; i<nrCastlingRights; i++ )
13830                   castlingRights[0][i] = FENcastlingRights[i];
13831           }
13832       EditPositionDone();
13833       DisplayBothClocks();
13834       DrawPosition(FALSE, boards[currentMove]);
13835     }
13836   }
13837 }