moved a lot of function from the option menu 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 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                   Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187 void DisplayAnalysis P((void));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
221
222 #ifdef WIN32
223        extern void ConsoleCreate();
224 #endif
225
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
229
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
236
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
242 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
246 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
250 int chattingPartner;
251
252 /* States for ics_getting_history */
253 #define H_FALSE 0
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
259
260 /* whosays values for GameEnds */
261 #define GE_ICS 0
262 #define GE_ENGINE 1
263 #define GE_PLAYER 2
264 #define GE_FILE 3
265 #define GE_XBOARD 4
266 #define GE_ENGINE1 5
267 #define GE_ENGINE2 6
268
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
271
272 /* Different types of move when calling RegisterMove */
273 #define CMAIL_MOVE   0
274 #define CMAIL_RESIGN 1
275 #define CMAIL_DRAW   2
276 #define CMAIL_ACCEPT 3
277
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
282
283 /* Telnet protocol constants */
284 #define TN_WILL 0373
285 #define TN_WONT 0374
286 #define TN_DO   0375
287 #define TN_DONT 0376
288 #define TN_IAC  0377
289 #define TN_ECHO 0001
290 #define TN_SGA  0003
291 #define TN_PORT 23
292
293 /* [AS] */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 {
296     assert( dst != NULL );
297     assert( src != NULL );
298     assert( count > 0 );
299
300     strncpy( dst, src, count );
301     dst[ count-1 ] = '\0';
302     return dst;
303 }
304
305 /* Some compiler can't cast u64 to double
306  * This function do the job for us:
307
308  * We use the highest bit for cast, this only
309  * works if the highest bit is not
310  * in use (This should not happen)
311  *
312  * We used this for all compiler
313  */
314 double
315 u64ToDouble(u64 value)
316 {
317   double r;
318   u64 tmp = value & u64Const(0x7fffffffffffffff);
319   r = (double)(s64)tmp;
320   if (value & u64Const(0x8000000000000000))
321        r +=  9.2233720368547758080e18; /* 2^63 */
322  return r;
323 }
324
325 /* Fake up flags for now, as we aren't keeping track of castling
326    availability yet. [HGM] Change of logic: the flag now only
327    indicates the type of castlings allowed by the rule of the game.
328    The actual rights themselves are maintained in the array
329    castlingRights, as part of the game history, and are not probed
330    by this function.
331  */
332 int
333 PosFlags(index)
334 {
335   int flags = F_ALL_CASTLE_OK;
336   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337   switch (gameInfo.variant) {
338   case VariantSuicide:
339     flags &= ~F_ALL_CASTLE_OK;
340   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341     flags |= F_IGNORE_CHECK;
342   case VariantLosers:
343     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
344     break;
345   case VariantAtomic:
346     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347     break;
348   case VariantKriegspiel:
349     flags |= F_KRIEGSPIEL_CAPTURE;
350     break;
351   case VariantCapaRandom:
352   case VariantFischeRandom:
353     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354   case VariantNoCastle:
355   case VariantShatranj:
356   case VariantCourier:
357     flags &= ~F_ALL_CASTLE_OK;
358     break;
359   default:
360     break;
361   }
362   return flags;
363 }
364
365 FILE *gameFileFP, *debugFP;
366
367 /*
368     [AS] Note: sometimes, the sscanf() function is used to parse the input
369     into a fixed-size buffer. Because of this, we must be prepared to
370     receive strings as long as the size of the input buffer, which is currently
371     set to 4K for Windows and 8K for the rest.
372     So, we must either allocate sufficiently large buffers here, or
373     reduce the size of the input buffer in the input reading part.
374 */
375
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
379
380 ChessProgramState first, second;
381
382 /* premove variables */
383 int premoveToX = 0;
384 int premoveToY = 0;
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
388 int gotPremove = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
391
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
394
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
422 int movesPerSession;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
427 int matchGame = 0;
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
431
432 /* animateTraining preserves the state of appData.animate
433  * when Training mode is activated. This allows the
434  * response to be animated when appData.animate == TRUE and
435  * appData.animateDragging == TRUE.
436  */
437 Boolean animateTraining;
438
439 GameInfo gameInfo;
440
441 AppData appData;
442
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char  epStatus[MAX_MOVES];
446 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 char  FENepStatus;
452 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
453 int loadFlag = 0;
454 int shuffleOpenings;
455 int mute; // mute all sounds
456
457 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
458     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461         BlackKing, BlackBishop, BlackKnight, BlackRook }
462 };
463
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468         BlackKing, BlackKing, BlackKnight, BlackRook }
469 };
470
471 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
472     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474     { BlackRook, BlackMan, BlackBishop, BlackQueen,
475         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
476 };
477
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482         BlackKing, BlackBishop, BlackKnight, BlackRook }
483 };
484
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
490 };
491
492
493 #if (BOARD_SIZE>=10)
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
499 };
500
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 };
507
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
512         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
513 };
514
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
517         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
519         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
520 };
521
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
524         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
526         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
527 };
528
529 #ifdef GOTHIC
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
532         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
534         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
535 };
536 #else // !GOTHIC
537 #define GothicArray CapablancaArray
538 #endif // !GOTHIC
539
540 #ifdef FALCON
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
543         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
545         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
546 };
547 #else // !FALCON
548 #define FalconArray CapablancaArray
549 #endif // !FALCON
550
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
557
558 #if (BOARD_SIZE>=12)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 };
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
568
569
570 Board initialPosition;
571
572
573 /* Convert str to a rating. Checks for special cases of "----",
574
575    "++++", etc. Also strips ()'s */
576 int
577 string_to_rating(str)
578   char *str;
579 {
580   while(*str && !isdigit(*str)) ++str;
581   if (!*str)
582     return 0;   /* One of the special "no rating" cases */
583   else
584     return atoi(str);
585 }
586
587 void
588 ClearProgramStats()
589 {
590     /* Init programStats */
591     programStats.movelist[0] = 0;
592     programStats.depth = 0;
593     programStats.nr_moves = 0;
594     programStats.moves_left = 0;
595     programStats.nodes = 0;
596     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
597     programStats.score = 0;
598     programStats.got_only_move = 0;
599     programStats.got_fail = 0;
600     programStats.line_is_book = 0;
601 }
602
603 void
604 InitBackEnd1()
605 {
606     int matched, min, sec;
607
608     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609
610     GetTimeMark(&programStartTime);
611     srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
612
613     ClearProgramStats();
614     programStats.ok_to_send = 1;
615     programStats.seen_stat = 0;
616
617     /*
618      * Initialize game list
619      */
620     ListNew(&gameList);
621
622
623     /*
624      * Internet chess server status
625      */
626     if (appData.icsActive) {
627         appData.matchMode = FALSE;
628         appData.matchGames = 0;
629 #if ZIPPY
630         appData.noChessProgram = !appData.zippyPlay;
631 #else
632         appData.zippyPlay = FALSE;
633         appData.zippyTalk = FALSE;
634         appData.noChessProgram = TRUE;
635 #endif
636         if (*appData.icsHelper != NULLCHAR) {
637             appData.useTelnet = TRUE;
638             appData.telnetProgram = appData.icsHelper;
639         }
640     } else {
641         appData.zippyTalk = appData.zippyPlay = FALSE;
642     }
643
644     /* [AS] Initialize pv info list [HGM] and game state */
645     {
646         int i, j;
647
648         for( i=0; i<MAX_MOVES; i++ ) {
649             pvInfoList[i].depth = -1;
650             epStatus[i]=EP_NONE;
651             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
652         }
653     }
654
655     /*
656      * Parse timeControl resource
657      */
658     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659                           appData.movesPerSession)) {
660         char buf[MSG_SIZ];
661         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662         DisplayFatalError(buf, 0, 2);
663     }
664
665     /*
666      * Parse searchTime resource
667      */
668     if (*appData.searchTime != NULLCHAR) {
669         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670         if (matched == 1) {
671             searchTime = min * 60;
672         } else if (matched == 2) {
673             searchTime = min * 60 + sec;
674         } else {
675             char buf[MSG_SIZ];
676             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677             DisplayFatalError(buf, 0, 2);
678         }
679     }
680
681     /* [AS] Adjudication threshold */
682     adjudicateLossThreshold = appData.adjudicateLossThreshold;
683
684     first.which = "first";
685     second.which = "second";
686     first.maybeThinking = second.maybeThinking = FALSE;
687     first.pr = second.pr = NoProc;
688     first.isr = second.isr = NULL;
689     first.sendTime = second.sendTime = 2;
690     first.sendDrawOffers = 1;
691     if (appData.firstPlaysBlack) {
692         first.twoMachinesColor = "black\n";
693         second.twoMachinesColor = "white\n";
694     } else {
695         first.twoMachinesColor = "white\n";
696         second.twoMachinesColor = "black\n";
697     }
698     first.program = appData.firstChessProgram;
699     second.program = appData.secondChessProgram;
700     first.host = appData.firstHost;
701     second.host = appData.secondHost;
702     first.dir = appData.firstDirectory;
703     second.dir = appData.secondDirectory;
704     first.other = &second;
705     second.other = &first;
706     first.initString = appData.initString;
707     second.initString = appData.secondInitString;
708     first.computerString = appData.firstComputerString;
709     second.computerString = appData.secondComputerString;
710     first.useSigint = second.useSigint = TRUE;
711     first.useSigterm = second.useSigterm = TRUE;
712     first.reuse = appData.reuseFirst;
713     second.reuse = appData.reuseSecond;
714     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
715     second.nps = appData.secondNPS;
716     first.useSetboard = second.useSetboard = FALSE;
717     first.useSAN = second.useSAN = FALSE;
718     first.usePing = second.usePing = FALSE;
719     first.lastPing = second.lastPing = 0;
720     first.lastPong = second.lastPong = 0;
721     first.usePlayother = second.usePlayother = FALSE;
722     first.useColors = second.useColors = TRUE;
723     first.useUsermove = second.useUsermove = FALSE;
724     first.sendICS = second.sendICS = FALSE;
725     first.sendName = second.sendName = appData.icsActive;
726     first.sdKludge = second.sdKludge = FALSE;
727     first.stKludge = second.stKludge = FALSE;
728     TidyProgramName(first.program, first.host, first.tidy);
729     TidyProgramName(second.program, second.host, second.tidy);
730     first.matchWins = second.matchWins = 0;
731     strcpy(first.variants, appData.variant);
732     strcpy(second.variants, appData.variant);
733     first.analysisSupport = second.analysisSupport = 2; /* detect */
734     first.analyzing = second.analyzing = FALSE;
735     first.initDone = second.initDone = FALSE;
736
737     /* New features added by Tord: */
738     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740     /* End of new features added by Tord. */
741     first.fenOverride  = appData.fenOverride1;
742     second.fenOverride = appData.fenOverride2;
743
744     /* [HGM] time odds: set factor for each machine */
745     first.timeOdds  = appData.firstTimeOdds;
746     second.timeOdds = appData.secondTimeOdds;
747     { int norm = 1;
748         if(appData.timeOddsMode) {
749             norm = first.timeOdds;
750             if(norm > second.timeOdds) norm = second.timeOdds;
751         }
752         first.timeOdds /= norm;
753         second.timeOdds /= norm;
754     }
755
756     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757     first.accumulateTC = appData.firstAccumulateTC;
758     second.accumulateTC = appData.secondAccumulateTC;
759     first.maxNrOfSessions = second.maxNrOfSessions = 1;
760
761     /* [HGM] debug */
762     first.debug = second.debug = FALSE;
763     first.supportsNPS = second.supportsNPS = UNKNOWN;
764
765     /* [HGM] options */
766     first.optionSettings  = appData.firstOptions;
767     second.optionSettings = appData.secondOptions;
768
769     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771     first.isUCI = appData.firstIsUCI; /* [AS] */
772     second.isUCI = appData.secondIsUCI; /* [AS] */
773     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775
776     if (appData.firstProtocolVersion > PROTOVER ||
777         appData.firstProtocolVersion < 1) {
778       char buf[MSG_SIZ];
779       sprintf(buf, _("protocol version %d not supported"),
780               appData.firstProtocolVersion);
781       DisplayFatalError(buf, 0, 2);
782     } else {
783       first.protocolVersion = appData.firstProtocolVersion;
784     }
785
786     if (appData.secondProtocolVersion > PROTOVER ||
787         appData.secondProtocolVersion < 1) {
788       char buf[MSG_SIZ];
789       sprintf(buf, _("protocol version %d not supported"),
790               appData.secondProtocolVersion);
791       DisplayFatalError(buf, 0, 2);
792     } else {
793       second.protocolVersion = appData.secondProtocolVersion;
794     }
795
796     if (appData.icsActive) {
797         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
798     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799         appData.clockMode = FALSE;
800         first.sendTime = second.sendTime = 0;
801     }
802
803 #if ZIPPY
804     /* Override some settings from environment variables, for backward
805        compatibility.  Unfortunately it's not feasible to have the env
806        vars just set defaults, at least in xboard.  Ugh.
807     */
808     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
809       ZippyInit();
810     }
811 #endif
812
813     if (appData.noChessProgram) {
814         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815         sprintf(programVersion, "%s", PACKAGE_STRING);
816     } else {
817       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
820     }
821
822     if (!appData.icsActive) {
823       char buf[MSG_SIZ];
824       /* Check for variants that are supported only in ICS mode,
825          or not at all.  Some that are accepted here nevertheless
826          have bugs; see comments below.
827       */
828       VariantClass variant = StringToVariant(appData.variant);
829       switch (variant) {
830       case VariantBughouse:     /* need four players and two boards */
831       case VariantKriegspiel:   /* need to hide pieces and move details */
832       /* case VariantFischeRandom: (Fabien: moved below) */
833         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834         DisplayFatalError(buf, 0, 2);
835         return;
836
837       case VariantUnknown:
838       case VariantLoadable:
839       case Variant29:
840       case Variant30:
841       case Variant31:
842       case Variant32:
843       case Variant33:
844       case Variant34:
845       case Variant35:
846       case Variant36:
847       default:
848         sprintf(buf, _("Unknown variant name %s"), appData.variant);
849         DisplayFatalError(buf, 0, 2);
850         return;
851
852       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
853       case VariantFairy:      /* [HGM] TestLegality definitely off! */
854       case VariantGothic:     /* [HGM] should work */
855       case VariantCapablanca: /* [HGM] should work */
856       case VariantCourier:    /* [HGM] initial forced moves not implemented */
857       case VariantShogi:      /* [HGM] drops not tested for legality */
858       case VariantKnightmate: /* [HGM] should work */
859       case VariantCylinder:   /* [HGM] untested */
860       case VariantFalcon:     /* [HGM] untested */
861       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862                                  offboard interposition not understood */
863       case VariantNormal:     /* definitely works! */
864       case VariantWildCastle: /* pieces not automatically shuffled */
865       case VariantNoCastle:   /* pieces not automatically shuffled */
866       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867       case VariantLosers:     /* should work except for win condition,
868                                  and doesn't know captures are mandatory */
869       case VariantSuicide:    /* should work except for win condition,
870                                  and doesn't know captures are mandatory */
871       case VariantGiveaway:   /* should work except for win condition,
872                                  and doesn't know captures are mandatory */
873       case VariantTwoKings:   /* should work */
874       case VariantAtomic:     /* should work except for win condition */
875       case Variant3Check:     /* should work except for win condition */
876       case VariantShatranj:   /* should work except for all win conditions */
877       case VariantBerolina:   /* might work if TestLegality is off */
878       case VariantCapaRandom: /* should work */
879       case VariantJanus:      /* should work */
880       case VariantSuper:      /* experimental */
881       case VariantGreat:      /* experimental, requires legality testing to be off */
882         break;
883       }
884     }
885
886     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
887     InitEngineUCI( installDir, &second );
888 }
889
890 int NextIntegerFromString( char ** str, long * value )
891 {
892     int result = -1;
893     char * s = *str;
894
895     while( *s == ' ' || *s == '\t' ) {
896         s++;
897     }
898
899     *value = 0;
900
901     if( *s >= '0' && *s <= '9' ) {
902         while( *s >= '0' && *s <= '9' ) {
903             *value = *value * 10 + (*s - '0');
904             s++;
905         }
906
907         result = 0;
908     }
909
910     *str = s;
911
912     return result;
913 }
914
915 int NextTimeControlFromString( char ** str, long * value )
916 {
917     long temp;
918     int result = NextIntegerFromString( str, &temp );
919
920     if( result == 0 ) {
921         *value = temp * 60; /* Minutes */
922         if( **str == ':' ) {
923             (*str)++;
924             result = NextIntegerFromString( str, &temp );
925             *value += temp; /* Seconds */
926         }
927     }
928
929     return result;
930 }
931
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
934     int result = -1; long temp, temp2;
935
936     if(**str != '+') return -1; // old params remain in force!
937     (*str)++;
938     if( NextTimeControlFromString( str, &temp ) ) return -1;
939
940     if(**str != '/') {
941         /* time only: incremental or sudden-death time control */
942         if(**str == '+') { /* increment follows; read it */
943             (*str)++;
944             if(result = NextIntegerFromString( str, &temp2)) return -1;
945             *inc = temp2 * 1000;
946         } else *inc = 0;
947         *moves = 0; *tc = temp * 1000;
948         return 0;
949     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
950
951     (*str)++; /* classical time control */
952     result = NextTimeControlFromString( str, &temp2);
953     if(result == 0) {
954         *moves = temp/60;
955         *tc    = temp2 * 1000;
956         *inc   = 0;
957     }
958     return result;
959 }
960
961 int GetTimeQuota(int movenr)
962 {   /* [HGM] get time to add from the multi-session time-control string */
963     int moves=1; /* kludge to force reading of first session */
964     long time, increment;
965     char *s = fullTimeControlString;
966
967     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968     do {
969         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971         if(movenr == -1) return time;    /* last move before new session     */
972         if(!moves) return increment;     /* current session is incremental   */
973         if(movenr >= 0) movenr -= moves; /* we already finished this session */
974     } while(movenr >= -1);               /* try again for next session       */
975
976     return 0; // no new time quota on this move
977 }
978
979 int
980 ParseTimeControl(tc, ti, mps)
981      char *tc;
982      int ti;
983      int mps;
984 {
985   long tc1;
986   long tc2;
987   char buf[MSG_SIZ];
988   
989   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
990   if(ti > 0) {
991     if(mps)
992       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993     else sprintf(buf, "+%s+%d", tc, ti);
994   } else {
995     if(mps)
996              sprintf(buf, "+%d/%s", mps, tc);
997     else sprintf(buf, "+%s", tc);
998   }
999   fullTimeControlString = StrSave(buf);
1000   
1001   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1002     return FALSE;
1003   }
1004   
1005   if( *tc == '/' ) {
1006     /* Parse second time control */
1007     tc++;
1008     
1009     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1010       return FALSE;
1011     }
1012     
1013     if( tc2 == 0 ) {
1014       return FALSE;
1015     }
1016     
1017     timeControl_2 = tc2 * 1000;
1018   }
1019   else {
1020     timeControl_2 = 0;
1021   }
1022   
1023   if( tc1 == 0 ) {
1024     return FALSE;
1025   }
1026   
1027   timeControl = tc1 * 1000;
1028   
1029   if (ti >= 0) {
1030     timeIncrement = ti * 1000;  /* convert to ms */
1031     movesPerSession = 0;
1032   } else {
1033     timeIncrement = 0;
1034     movesPerSession = mps;
1035   }
1036   return TRUE;
1037 }
1038
1039 void
1040 InitBackEnd2()
1041 {
1042   if (appData.debugMode) {
1043     fprintf(debugFP, "%s\n", programVersion);
1044   }
1045
1046   if (appData.matchGames > 0) {
1047     appData.matchMode = TRUE;
1048   } else if (appData.matchMode) {
1049     appData.matchGames = 1;
1050   }
1051   if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052     appData.matchGames = appData.sameColorGames;
1053   if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054     if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055     if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056   }
1057   Reset(TRUE, FALSE);
1058   if (appData.noChessProgram || first.protocolVersion == 1) {
1059     InitBackEnd3();
1060   } else {
1061     /* kludge: allow timeout for initial "feature" commands */
1062     FreezeUI();
1063     DisplayMessage("", _("Starting chess program"));
1064     ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1065   }
1066 }
1067
1068 void
1069 InitBackEnd3 P((void))
1070 {
1071     GameMode initialMode;
1072     char buf[MSG_SIZ];
1073     int err;
1074
1075     InitChessProgram(&first, startedFromSetupPosition);
1076
1077
1078     if (appData.icsActive) {
1079 #ifdef WIN32
1080         /* [DM] Make a console window if needed [HGM] merged ifs */
1081         ConsoleCreate();
1082 #endif
1083         err = establish();
1084         if (err != 0) {
1085             if (*appData.icsCommPort != NULLCHAR) {
1086                 sprintf(buf, _("Could not open comm port %s"),
1087                         appData.icsCommPort);
1088             } else {
1089                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090                         appData.icsHost, appData.icsPort);
1091             }
1092             DisplayFatalError(buf, err, 1);
1093             return;
1094         }
1095         SetICSMode();
1096         telnetISR =
1097           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098         fromUserISR =
1099           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100     } else if (appData.noChessProgram) {
1101         SetNCPMode();
1102     } else {
1103         SetGNUMode();
1104     }
1105
1106     if (*appData.cmailGameName != NULLCHAR) {
1107         SetCmailMode();
1108         OpenLoopback(&cmailPR);
1109         cmailISR =
1110           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111     }
1112
1113     ThawUI();
1114     DisplayMessage("", "");
1115     if (StrCaseCmp(appData.initialMode, "") == 0) {
1116       initialMode = BeginningOfGame;
1117     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118       initialMode = TwoMachinesPlay;
1119     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120       initialMode = AnalyzeFile;
1121     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122       initialMode = AnalyzeMode;
1123     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124       initialMode = MachinePlaysWhite;
1125     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126       initialMode = MachinePlaysBlack;
1127     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128       initialMode = EditGame;
1129     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130       initialMode = EditPosition;
1131     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132       initialMode = Training;
1133     } else {
1134       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135       DisplayFatalError(buf, 0, 2);
1136       return;
1137     }
1138
1139     if (appData.matchMode) {
1140         /* Set up machine vs. machine match */
1141         if (appData.noChessProgram) {
1142             DisplayFatalError(_("Can't have a match with no chess programs"),
1143                               0, 2);
1144             return;
1145         }
1146         matchMode = TRUE;
1147         matchGame = 1;
1148         if (*appData.loadGameFile != NULLCHAR) {
1149             int index = appData.loadGameIndex; // [HGM] autoinc
1150             if(index<0) lastIndex = index = 1;
1151             if (!LoadGameFromFile(appData.loadGameFile,
1152                                   index,
1153                                   appData.loadGameFile, FALSE)) {
1154                 DisplayFatalError(_("Bad game file"), 0, 1);
1155                 return;
1156             }
1157         } else if (*appData.loadPositionFile != NULLCHAR) {
1158             int index = appData.loadPositionIndex; // [HGM] autoinc
1159             if(index<0) lastIndex = index = 1;
1160             if (!LoadPositionFromFile(appData.loadPositionFile,
1161                                       index,
1162                                       appData.loadPositionFile)) {
1163                 DisplayFatalError(_("Bad position file"), 0, 1);
1164                 return;
1165             }
1166         }
1167         TwoMachinesEvent();
1168     } else if (*appData.cmailGameName != NULLCHAR) {
1169         /* Set up cmail mode */
1170         ReloadCmailMsgEvent(TRUE);
1171     } else {
1172         /* Set up other modes */
1173         if (initialMode == AnalyzeFile) {
1174           if (*appData.loadGameFile == NULLCHAR) {
1175             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176             return;
1177           }
1178         }
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             (void) LoadGameFromFile(appData.loadGameFile,
1181                                     appData.loadGameIndex,
1182                                     appData.loadGameFile, TRUE);
1183         } else if (*appData.loadPositionFile != NULLCHAR) {
1184             (void) LoadPositionFromFile(appData.loadPositionFile,
1185                                         appData.loadPositionIndex,
1186                                         appData.loadPositionFile);
1187             /* [HGM] try to make self-starting even after FEN load */
1188             /* to allow automatic setup of fairy variants with wtm */
1189             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190                 gameMode = BeginningOfGame;
1191                 setboardSpoiledMachineBlack = 1;
1192             }
1193             /* [HGM] loadPos: make that every new game uses the setup */
1194             /* from file as long as we do not switch variant          */
1195             if(!blackPlaysFirst) { int i;
1196                 startedFromPositionFile = TRUE;
1197                 CopyBoard(filePosition, boards[0]);
1198                 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1199             }
1200         }
1201         if (initialMode == AnalyzeMode) {
1202           if (appData.noChessProgram) {
1203             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1204             return;
1205           }
1206           if (appData.icsActive) {
1207             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208             return;
1209           }
1210           AnalyzeModeEvent();
1211         } else if (initialMode == AnalyzeFile) {
1212           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213           ShowThinkingEvent();
1214           AnalyzeFileEvent();
1215           AnalysisPeriodicEvent(1);
1216         } else if (initialMode == MachinePlaysWhite) {
1217           if (appData.noChessProgram) {
1218             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219                               0, 2);
1220             return;
1221           }
1222           if (appData.icsActive) {
1223             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224                               0, 2);
1225             return;
1226           }
1227           MachineWhiteEvent();
1228         } else if (initialMode == MachinePlaysBlack) {
1229           if (appData.noChessProgram) {
1230             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231                               0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236                               0, 2);
1237             return;
1238           }
1239           MachineBlackEvent();
1240         } else if (initialMode == TwoMachinesPlay) {
1241           if (appData.noChessProgram) {
1242             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243                               0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1248                               0, 2);
1249             return;
1250           }
1251           TwoMachinesEvent();
1252         } else if (initialMode == EditGame) {
1253           EditGameEvent();
1254         } else if (initialMode == EditPosition) {
1255           EditPositionEvent();
1256         } else if (initialMode == Training) {
1257           if (*appData.loadGameFile == NULLCHAR) {
1258             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1259             return;
1260           }
1261           TrainingEvent();
1262         }
1263     }
1264 }
1265
1266 /*
1267  * Establish will establish a contact to a remote host.port.
1268  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269  *  used to talk to the host.
1270  * Returns 0 if okay, error code if not.
1271  */
1272 int
1273 establish()
1274 {
1275     char buf[MSG_SIZ];
1276
1277     if (*appData.icsCommPort != NULLCHAR) {
1278         /* Talk to the host through a serial comm port */
1279         return OpenCommPort(appData.icsCommPort, &icsPR);
1280
1281     } else if (*appData.gateway != NULLCHAR) {
1282         if (*appData.remoteShell == NULLCHAR) {
1283             /* Use the rcmd protocol to run telnet program on a gateway host */
1284             snprintf(buf, sizeof(buf), "%s %s %s",
1285                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1286             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1287
1288         } else {
1289             /* Use the rsh program to run telnet program on a gateway host */
1290             if (*appData.remoteUser == NULLCHAR) {
1291                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292                         appData.gateway, appData.telnetProgram,
1293                         appData.icsHost, appData.icsPort);
1294             } else {
1295                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296                         appData.remoteShell, appData.gateway,
1297                         appData.remoteUser, appData.telnetProgram,
1298                         appData.icsHost, appData.icsPort);
1299             }
1300             return StartChildProcess(buf, "", &icsPR);
1301
1302         }
1303     } else if (appData.useTelnet) {
1304         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1305
1306     } else {
1307         /* TCP socket interface differs somewhat between
1308            Unix and NT; handle details in the front end.
1309            */
1310         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1311     }
1312 }
1313
1314 void
1315 show_bytes(fp, buf, count)
1316      FILE *fp;
1317      char *buf;
1318      int count;
1319 {
1320     while (count--) {
1321         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322             fprintf(fp, "\\%03o", *buf & 0xff);
1323         } else {
1324             putc(*buf, fp);
1325         }
1326         buf++;
1327     }
1328     fflush(fp);
1329 }
1330
1331 /* Returns an errno value */
1332 int
1333 OutputMaybeTelnet(pr, message, count, outError)
1334      ProcRef pr;
1335      char *message;
1336      int count;
1337      int *outError;
1338 {
1339     char buf[8192], *p, *q, *buflim;
1340     int left, newcount, outcount;
1341
1342     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343         *appData.gateway != NULLCHAR) {
1344         if (appData.debugMode) {
1345             fprintf(debugFP, ">ICS: ");
1346             show_bytes(debugFP, message, count);
1347             fprintf(debugFP, "\n");
1348         }
1349         return OutputToProcess(pr, message, count, outError);
1350     }
1351
1352     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1353     p = message;
1354     q = buf;
1355     left = count;
1356     newcount = 0;
1357     while (left) {
1358         if (q >= buflim) {
1359             if (appData.debugMode) {
1360                 fprintf(debugFP, ">ICS: ");
1361                 show_bytes(debugFP, buf, newcount);
1362                 fprintf(debugFP, "\n");
1363             }
1364             outcount = OutputToProcess(pr, buf, newcount, outError);
1365             if (outcount < newcount) return -1; /* to be sure */
1366             q = buf;
1367             newcount = 0;
1368         }
1369         if (*p == '\n') {
1370             *q++ = '\r';
1371             newcount++;
1372         } else if (((unsigned char) *p) == TN_IAC) {
1373             *q++ = (char) TN_IAC;
1374             newcount ++;
1375         }
1376         *q++ = *p++;
1377         newcount++;
1378         left--;
1379     }
1380     if (appData.debugMode) {
1381         fprintf(debugFP, ">ICS: ");
1382         show_bytes(debugFP, buf, newcount);
1383         fprintf(debugFP, "\n");
1384     }
1385     outcount = OutputToProcess(pr, buf, newcount, outError);
1386     if (outcount < newcount) return -1; /* to be sure */
1387     return count;
1388 }
1389
1390 void
1391 read_from_player(isr, closure, message, count, error)
1392      InputSourceRef isr;
1393      VOIDSTAR closure;
1394      char *message;
1395      int count;
1396      int error;
1397 {
1398     int outError, outCount;
1399     static int gotEof = 0;
1400
1401     /* Pass data read from player on to ICS */
1402     if (count > 0) {
1403         gotEof = 0;
1404         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405         if (outCount < count) {
1406             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407         }
1408     } else if (count < 0) {
1409         RemoveInputSource(isr);
1410         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411     } else if (gotEof++ > 0) {
1412         RemoveInputSource(isr);
1413         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1414     }
1415 }
1416
1417 void
1418 KeepAlive()
1419 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420     SendToICS("date\n");
1421     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1422 }
1423
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1426 {
1427     char buffer[MSG_SIZ];
1428     va_list args;
1429
1430     va_start(args, format);
1431     vsnprintf(buffer, sizeof(buffer), format, args);
1432     buffer[sizeof(buffer)-1] = '\0';
1433     SendToICS(buffer);
1434     va_end(args);
1435 }
1436
1437 void
1438 SendToICS(s)
1439      char *s;
1440 {
1441     int count, outCount, outError;
1442
1443     if (icsPR == NULL) return;
1444
1445     count = strlen(s);
1446     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447     if (outCount < count) {
1448         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449     }
1450 }
1451
1452 /* This is used for sending logon scripts to the ICS. Sending
1453    without a delay causes problems when using timestamp on ICC
1454    (at least on my machine). */
1455 void
1456 SendToICSDelayed(s,msdelay)
1457      char *s;
1458      long msdelay;
1459 {
1460     int count, outCount, outError;
1461
1462     if (icsPR == NULL) return;
1463
1464     count = strlen(s);
1465     if (appData.debugMode) {
1466         fprintf(debugFP, ">ICS: ");
1467         show_bytes(debugFP, s, count);
1468         fprintf(debugFP, "\n");
1469     }
1470     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1471                                       msdelay);
1472     if (outCount < count) {
1473         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1474     }
1475 }
1476
1477
1478 /* Remove all highlighting escape sequences in s
1479    Also deletes any suffix starting with '('
1480    */
1481 char *
1482 StripHighlightAndTitle(s)
1483      char *s;
1484 {
1485     static char retbuf[MSG_SIZ];
1486     char *p = retbuf;
1487
1488     while (*s != NULLCHAR) {
1489         while (*s == '\033') {
1490             while (*s != NULLCHAR && !isalpha(*s)) s++;
1491             if (*s != NULLCHAR) s++;
1492         }
1493         while (*s != NULLCHAR && *s != '\033') {
1494             if (*s == '(' || *s == '[') {
1495                 *p = NULLCHAR;
1496                 return retbuf;
1497             }
1498             *p++ = *s++;
1499         }
1500     }
1501     *p = NULLCHAR;
1502     return retbuf;
1503 }
1504
1505 /* Remove all highlighting escape sequences in s */
1506 char *
1507 StripHighlight(s)
1508      char *s;
1509 {
1510     static char retbuf[MSG_SIZ];
1511     char *p = retbuf;
1512
1513     while (*s != NULLCHAR) {
1514         while (*s == '\033') {
1515             while (*s != NULLCHAR && !isalpha(*s)) s++;
1516             if (*s != NULLCHAR) s++;
1517         }
1518         while (*s != NULLCHAR && *s != '\033') {
1519             *p++ = *s++;
1520         }
1521     }
1522     *p = NULLCHAR;
1523     return retbuf;
1524 }
1525
1526 char *variantNames[] = VARIANT_NAMES;
1527 char *
1528 VariantName(v)
1529      VariantClass v;
1530 {
1531     return variantNames[v];
1532 }
1533
1534
1535 /* Identify a variant from the strings the chess servers use or the
1536    PGN Variant tag names we use. */
1537 VariantClass
1538 StringToVariant(e)
1539      char *e;
1540 {
1541     char *p;
1542     int wnum = -1;
1543     VariantClass v = VariantNormal;
1544     int i, found = FALSE;
1545     char buf[MSG_SIZ];
1546
1547     if (!e) return v;
1548
1549     /* [HGM] skip over optional board-size prefixes */
1550     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552         while( *e++ != '_');
1553     }
1554
1555     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556         v = VariantNormal;
1557         found = TRUE;
1558     } else
1559     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560       if (StrCaseStr(e, variantNames[i])) {
1561         v = (VariantClass) i;
1562         found = TRUE;
1563         break;
1564       }
1565     }
1566
1567     if (!found) {
1568       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569           || StrCaseStr(e, "wild/fr")
1570           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571         v = VariantFischeRandom;
1572       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573                  (i = 1, p = StrCaseStr(e, "w"))) {
1574         p += i;
1575         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1576         if (isdigit(*p)) {
1577           wnum = atoi(p);
1578         } else {
1579           wnum = -1;
1580         }
1581         switch (wnum) {
1582         case 0: /* FICS only, actually */
1583         case 1:
1584           /* Castling legal even if K starts on d-file */
1585           v = VariantWildCastle;
1586           break;
1587         case 2:
1588         case 3:
1589         case 4:
1590           /* Castling illegal even if K & R happen to start in
1591              normal positions. */
1592           v = VariantNoCastle;
1593           break;
1594         case 5:
1595         case 7:
1596         case 8:
1597         case 10:
1598         case 11:
1599         case 12:
1600         case 13:
1601         case 14:
1602         case 15:
1603         case 18:
1604         case 19:
1605           /* Castling legal iff K & R start in normal positions */
1606           v = VariantNormal;
1607           break;
1608         case 6:
1609         case 20:
1610         case 21:
1611           /* Special wilds for position setup; unclear what to do here */
1612           v = VariantLoadable;
1613           break;
1614         case 9:
1615           /* Bizarre ICC game */
1616           v = VariantTwoKings;
1617           break;
1618         case 16:
1619           v = VariantKriegspiel;
1620           break;
1621         case 17:
1622           v = VariantLosers;
1623           break;
1624         case 22:
1625           v = VariantFischeRandom;
1626           break;
1627         case 23:
1628           v = VariantCrazyhouse;
1629           break;
1630         case 24:
1631           v = VariantBughouse;
1632           break;
1633         case 25:
1634           v = Variant3Check;
1635           break;
1636         case 26:
1637           /* Not quite the same as FICS suicide! */
1638           v = VariantGiveaway;
1639           break;
1640         case 27:
1641           v = VariantAtomic;
1642           break;
1643         case 28:
1644           v = VariantShatranj;
1645           break;
1646
1647         /* Temporary names for future ICC types.  The name *will* change in
1648            the next xboard/WinBoard release after ICC defines it. */
1649         case 29:
1650           v = Variant29;
1651           break;
1652         case 30:
1653           v = Variant30;
1654           break;
1655         case 31:
1656           v = Variant31;
1657           break;
1658         case 32:
1659           v = Variant32;
1660           break;
1661         case 33:
1662           v = Variant33;
1663           break;
1664         case 34:
1665           v = Variant34;
1666           break;
1667         case 35:
1668           v = Variant35;
1669           break;
1670         case 36:
1671           v = Variant36;
1672           break;
1673         case 37:
1674           v = VariantShogi;
1675           break;
1676         case 38:
1677           v = VariantXiangqi;
1678           break;
1679         case 39:
1680           v = VariantCourier;
1681           break;
1682         case 40:
1683           v = VariantGothic;
1684           break;
1685         case 41:
1686           v = VariantCapablanca;
1687           break;
1688         case 42:
1689           v = VariantKnightmate;
1690           break;
1691         case 43:
1692           v = VariantFairy;
1693           break;
1694         case 44:
1695           v = VariantCylinder;
1696           break;
1697         case 45:
1698           v = VariantFalcon;
1699           break;
1700         case 46:
1701           v = VariantCapaRandom;
1702           break;
1703         case 47:
1704           v = VariantBerolina;
1705           break;
1706         case 48:
1707           v = VariantJanus;
1708           break;
1709         case 49:
1710           v = VariantSuper;
1711           break;
1712         case 50:
1713           v = VariantGreat;
1714           break;
1715         case -1:
1716           /* Found "wild" or "w" in the string but no number;
1717              must assume it's normal chess. */
1718           v = VariantNormal;
1719           break;
1720         default:
1721           sprintf(buf, _("Unknown wild type %d"), wnum);
1722           DisplayError(buf, 0);
1723           v = VariantUnknown;
1724           break;
1725         }
1726       }
1727     }
1728     if (appData.debugMode) {
1729       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730               e, wnum, VariantName(v));
1731     }
1732     return v;
1733 }
1734
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1737
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739    advance *index beyond it, and set leftover_start to the new value of
1740    *index; else return FALSE.  If pattern contains the character '*', it
1741    matches any sequence of characters not containing '\r', '\n', or the
1742    character following the '*' (if any), and the matched sequence(s) are
1743    copied into star_match.
1744    */
1745 int
1746 looking_at(buf, index, pattern)
1747      char *buf;
1748      int *index;
1749      char *pattern;
1750 {
1751     char *bufp = &buf[*index], *patternp = pattern;
1752     int star_count = 0;
1753     char *matchp = star_match[0];
1754
1755     for (;;) {
1756         if (*patternp == NULLCHAR) {
1757             *index = leftover_start = bufp - buf;
1758             *matchp = NULLCHAR;
1759             return TRUE;
1760         }
1761         if (*bufp == NULLCHAR) return FALSE;
1762         if (*patternp == '*') {
1763             if (*bufp == *(patternp + 1)) {
1764                 *matchp = NULLCHAR;
1765                 matchp = star_match[++star_count];
1766                 patternp += 2;
1767                 bufp++;
1768                 continue;
1769             } else if (*bufp == '\n' || *bufp == '\r') {
1770                 patternp++;
1771                 if (*patternp == NULLCHAR)
1772                   continue;
1773                 else
1774                   return FALSE;
1775             } else {
1776                 *matchp++ = *bufp++;
1777                 continue;
1778             }
1779         }
1780         if (*patternp != *bufp) return FALSE;
1781         patternp++;
1782         bufp++;
1783     }
1784 }
1785
1786 void
1787 SendToPlayer(data, length)
1788      char *data;
1789      int length;
1790 {
1791     int error, outCount;
1792     outCount = OutputToProcess(NoProc, data, length, &error);
1793     if (outCount < length) {
1794         DisplayFatalError(_("Error writing to display"), error, 1);
1795     }
1796 }
1797
1798 void
1799 PackHolding(packed, holding)
1800      char packed[];
1801      char *holding;
1802 {
1803     char *p = holding;
1804     char *q = packed;
1805     int runlength = 0;
1806     int curr = 9999;
1807     do {
1808         if (*p == curr) {
1809             runlength++;
1810         } else {
1811             switch (runlength) {
1812               case 0:
1813                 break;
1814               case 1:
1815                 *q++ = curr;
1816                 break;
1817               case 2:
1818                 *q++ = curr;
1819                 *q++ = curr;
1820                 break;
1821               default:
1822                 sprintf(q, "%d", runlength);
1823                 while (*q) q++;
1824                 *q++ = curr;
1825                 break;
1826             }
1827             runlength = 1;
1828             curr = *p;
1829         }
1830     } while (*p++);
1831     *q = NULLCHAR;
1832 }
1833
1834 /* Telnet protocol requests from the front end */
1835 void
1836 TelnetRequest(ddww, option)
1837      unsigned char ddww, option;
1838 {
1839     unsigned char msg[3];
1840     int outCount, outError;
1841
1842     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1843
1844     if (appData.debugMode) {
1845         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1846         switch (ddww) {
1847           case TN_DO:
1848             ddwwStr = "DO";
1849             break;
1850           case TN_DONT:
1851             ddwwStr = "DONT";
1852             break;
1853           case TN_WILL:
1854             ddwwStr = "WILL";
1855             break;
1856           case TN_WONT:
1857             ddwwStr = "WONT";
1858             break;
1859           default:
1860             ddwwStr = buf1;
1861             sprintf(buf1, "%d", ddww);
1862             break;
1863         }
1864         switch (option) {
1865           case TN_ECHO:
1866             optionStr = "ECHO";
1867             break;
1868           default:
1869             optionStr = buf2;
1870             sprintf(buf2, "%d", option);
1871             break;
1872         }
1873         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1874     }
1875     msg[0] = TN_IAC;
1876     msg[1] = ddww;
1877     msg[2] = option;
1878     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1879     if (outCount < 3) {
1880         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881     }
1882 }
1883
1884 void
1885 DoEcho()
1886 {
1887     if (!appData.icsActive) return;
1888     TelnetRequest(TN_DO, TN_ECHO);
1889 }
1890
1891 void
1892 DontEcho()
1893 {
1894     if (!appData.icsActive) return;
1895     TelnetRequest(TN_DONT, TN_ECHO);
1896 }
1897
1898 void
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1900 {
1901     /* put the holdings sent to us by the server on the board holdings area */
1902     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903     char p;
1904     ChessSquare piece;
1905
1906     if(gameInfo.holdingsWidth < 2)  return;
1907
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        return;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2014    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2015    
2016    DrawPosition(TRUE, boards[currentMove]);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2029
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2032
2033 void
2034 read_from_ics(isr, closure, data, count, error)
2035      InputSourceRef isr;
2036      VOIDSTAR closure;
2037      char *data;
2038      int count;
2039      int error;
2040 {
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2050
2051     static int started = STARTED_NONE;
2052     static char parse[20000];
2053     static int parse_pos = 0;
2054     static char buf[BUF_SIZE + 1];
2055     static int firstTime = TRUE, intfSet = FALSE;
2056     static ColorClass prevColor = ColorNormal;
2057     static int savingComment = FALSE;
2058     char str[500];
2059     int i, oldi;
2060     int buf_len;
2061     int next_out;
2062     int tkind;
2063     int backup;    /* [DM] For zippy color lines */
2064     char *p;
2065     char talker[MSG_SIZ]; // [HGM] chat
2066     int channel;
2067
2068     if (appData.debugMode) {
2069       if (!error) {
2070         fprintf(debugFP, "<ICS: ");
2071         show_bytes(debugFP, data, count);
2072         fprintf(debugFP, "\n");
2073       }
2074     }
2075
2076     if (appData.debugMode) { int f = forwardMostMove;
2077         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2078                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2079     }
2080     if (count > 0) {
2081         /* If last read ended with a partial line that we couldn't parse,
2082            prepend it to the new read and try again. */
2083         if (leftover_len > 0) {
2084             for (i=0; i<leftover_len; i++)
2085               buf[i] = buf[leftover_start + i];
2086         }
2087
2088         /* Copy in new characters, removing nulls and \r's */
2089         buf_len = leftover_len;
2090         for (i = 0; i < count; i++) {
2091             if (data[i] != NULLCHAR && data[i] != '\r')
2092               buf[buf_len++] = data[i];
2093             if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && 
2094                                buf[buf_len-3]==' '  && buf[buf_len-2]==' '  && buf[buf_len-1]==' ') {
2095                 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2096                 if(buf_len == 0 || buf[buf_len-1] != ' ')
2097                    buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2098             }
2099         }
2100
2101         buf[buf_len] = NULLCHAR;
2102         next_out = leftover_len;
2103         leftover_start = 0;
2104
2105         i = 0;
2106         while (i < buf_len) {
2107             /* Deal with part of the TELNET option negotiation
2108                protocol.  We refuse to do anything beyond the
2109                defaults, except that we allow the WILL ECHO option,
2110                which ICS uses to turn off password echoing when we are
2111                directly connected to it.  We reject this option
2112                if localLineEditing mode is on (always on in xboard)
2113                and we are talking to port 23, which might be a real
2114                telnet server that will try to keep WILL ECHO on permanently.
2115              */
2116             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2117                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2118                 unsigned char option;
2119                 oldi = i;
2120                 switch ((unsigned char) buf[++i]) {
2121                   case TN_WILL:
2122                     if (appData.debugMode)
2123                       fprintf(debugFP, "\n<WILL ");
2124                     switch (option = (unsigned char) buf[++i]) {
2125                       case TN_ECHO:
2126                         if (appData.debugMode)
2127                           fprintf(debugFP, "ECHO ");
2128                         /* Reply only if this is a change, according
2129                            to the protocol rules. */
2130                         if (remoteEchoOption) break;
2131                         if (appData.localLineEditing &&
2132                             atoi(appData.icsPort) == TN_PORT) {
2133                             TelnetRequest(TN_DONT, TN_ECHO);
2134                         } else {
2135                             EchoOff();
2136                             TelnetRequest(TN_DO, TN_ECHO);
2137                             remoteEchoOption = TRUE;
2138                         }
2139                         break;
2140                       default:
2141                         if (appData.debugMode)
2142                           fprintf(debugFP, "%d ", option);
2143                         /* Whatever this is, we don't want it. */
2144                         TelnetRequest(TN_DONT, option);
2145                         break;
2146                     }
2147                     break;
2148                   case TN_WONT:
2149                     if (appData.debugMode)
2150                       fprintf(debugFP, "\n<WONT ");
2151                     switch (option = (unsigned char) buf[++i]) {
2152                       case TN_ECHO:
2153                         if (appData.debugMode)
2154                           fprintf(debugFP, "ECHO ");
2155                         /* Reply only if this is a change, according
2156                            to the protocol rules. */
2157                         if (!remoteEchoOption) break;
2158                         EchoOn();
2159                         TelnetRequest(TN_DONT, TN_ECHO);
2160                         remoteEchoOption = FALSE;
2161                         break;
2162                       default:
2163                         if (appData.debugMode)
2164                           fprintf(debugFP, "%d ", (unsigned char) option);
2165                         /* Whatever this is, it must already be turned
2166                            off, because we never agree to turn on
2167                            anything non-default, so according to the
2168                            protocol rules, we don't reply. */
2169                         break;
2170                     }
2171                     break;
2172                   case TN_DO:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<DO ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       default:
2177                         /* Whatever this is, we refuse to do it. */
2178                         if (appData.debugMode)
2179                           fprintf(debugFP, "%d ", option);
2180                         TelnetRequest(TN_WONT, option);
2181                         break;
2182                     }
2183                     break;
2184                   case TN_DONT:
2185                     if (appData.debugMode)
2186                       fprintf(debugFP, "\n<DONT ");
2187                     switch (option = (unsigned char) buf[++i]) {
2188                       default:
2189                         if (appData.debugMode)
2190                           fprintf(debugFP, "%d ", option);
2191                         /* Whatever this is, we are already not doing
2192                            it, because we never agree to do anything
2193                            non-default, so according to the protocol
2194                            rules, we don't reply. */
2195                         break;
2196                     }
2197                     break;
2198                   case TN_IAC:
2199                     if (appData.debugMode)
2200                       fprintf(debugFP, "\n<IAC ");
2201                     /* Doubled IAC; pass it through */
2202                     i--;
2203                     break;
2204                   default:
2205                     if (appData.debugMode)
2206                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2207                     /* Drop all other telnet commands on the floor */
2208                     break;
2209                 }
2210                 if (oldi > next_out)
2211                   SendToPlayer(&buf[next_out], oldi - next_out);
2212                 if (++i > next_out)
2213                   next_out = i;
2214                 continue;
2215             }
2216
2217             /* OK, this at least will *usually* work */
2218             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2219                 loggedOn = TRUE;
2220             }
2221
2222             if (loggedOn && !intfSet) {
2223                 if (ics_type == ICS_ICC) {
2224                   sprintf(str,
2225                           "/set-quietly interface %s\n/set-quietly style 12\n",
2226                           programVersion);
2227           if (!appData.noJoin)
2228               strcat(str, "/set-quietly wrap 0\n");
2229                 } else if (ics_type == ICS_CHESSNET) {
2230                   sprintf(str, "/style 12\n");
2231                 } else {
2232                   strcpy(str, "alias $ @\n$set interface ");
2233                   strcat(str, programVersion);
2234                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2235 #ifdef WIN32
2236                   strcat(str, "$iset nohighlight 1\n");
2237 #endif
2238           if (!appData.noJoin)
2239               strcat(str, "$iset nowrap 1\n");
2240                   strcat(str, "$iset lock 1\n$style 12\n");
2241                 }
2242                 SendToICS(str);
2243                 NotifyFrontendLogin();
2244                 intfSet = TRUE;
2245             }
2246
2247             if (started == STARTED_COMMENT) {
2248                 /* Accumulate characters in comment */
2249                 parse[parse_pos++] = buf[i];
2250                 if (buf[i] == '\n') {
2251                     parse[parse_pos] = NULLCHAR;
2252                     if(chattingPartner>=0) {
2253                         char mess[MSG_SIZ];
2254                         sprintf(mess, "%s%s", talker, parse);
2255                         OutputChatMessage(chattingPartner, mess);
2256                         chattingPartner = -1;
2257                     } else
2258                     if(!suppressKibitz) // [HGM] kibitz
2259                         AppendComment(forwardMostMove, StripHighlight(parse));
2260                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2261                         int nrDigit = 0, nrAlph = 0, i;
2262                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2263                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2264                         parse[parse_pos] = NULLCHAR;
2265                         // try to be smart: if it does not look like search info, it should go to
2266                         // ICS interaction window after all, not to engine-output window.
2267                         for(i=0; i<parse_pos; i++) { // count letters and digits
2268                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2269                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2270                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2271                         }
2272                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2273                             int depth=0; float score;
2274                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2275                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2276                                 pvInfoList[forwardMostMove-1].depth = depth;
2277                                 pvInfoList[forwardMostMove-1].score = 100*score;
2278                             }
2279                             OutputKibitz(suppressKibitz, parse);
2280                         } else {
2281                             char tmp[MSG_SIZ];
2282                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2283                             SendToPlayer(tmp, strlen(tmp));
2284                         }
2285                     }
2286                     started = STARTED_NONE;
2287                 } else {
2288                     /* Don't match patterns against characters in chatter */
2289                     i++;
2290                     continue;
2291                 }
2292             }
2293             if (started == STARTED_CHATTER) {
2294                 if (buf[i] != '\n') {
2295                     /* Don't match patterns against characters in chatter */
2296                     i++;
2297                     continue;
2298                 }
2299                 started = STARTED_NONE;
2300             }
2301
2302             /* Kludge to deal with rcmd protocol */
2303             if (firstTime && looking_at(buf, &i, "\001*")) {
2304                 DisplayFatalError(&buf[1], 0, 1);
2305                 continue;
2306             } else {
2307                 firstTime = FALSE;
2308             }
2309
2310             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2311                 ics_type = ICS_ICC;
2312                 ics_prefix = "/";
2313                 if (appData.debugMode)
2314                   fprintf(debugFP, "ics_type %d\n", ics_type);
2315                 continue;
2316             }
2317             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2318                 ics_type = ICS_FICS;
2319                 ics_prefix = "$";
2320                 if (appData.debugMode)
2321                   fprintf(debugFP, "ics_type %d\n", ics_type);
2322                 continue;
2323             }
2324             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2325                 ics_type = ICS_CHESSNET;
2326                 ics_prefix = "/";
2327                 if (appData.debugMode)
2328                   fprintf(debugFP, "ics_type %d\n", ics_type);
2329                 continue;
2330             }
2331
2332             if (!loggedOn &&
2333                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2334                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2335                  looking_at(buf, &i, "will be \"*\""))) {
2336               strcpy(ics_handle, star_match[0]);
2337               continue;
2338             }
2339
2340             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2341               char buf[MSG_SIZ];
2342               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2343               DisplayIcsInteractionTitle(buf);
2344               have_set_title = TRUE;
2345             }
2346
2347             /* skip finger notes */
2348             if (started == STARTED_NONE &&
2349                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2350                  (buf[i] == '1' && buf[i+1] == '0')) &&
2351                 buf[i+2] == ':' && buf[i+3] == ' ') {
2352               started = STARTED_CHATTER;
2353               i += 3;
2354               continue;
2355             }
2356
2357             /* skip formula vars */
2358             if (started == STARTED_NONE &&
2359                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2360               started = STARTED_CHATTER;
2361               i += 3;
2362               continue;
2363             }
2364
2365             oldi = i;
2366             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2367             if (appData.autoKibitz && started == STARTED_NONE &&
2368                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2369                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2370                 if(looking_at(buf, &i, "* kibitzes: ") &&
2371                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2372                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2373                         suppressKibitz = TRUE;
2374                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2375                                 && (gameMode == IcsPlayingWhite)) ||
2376                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2377                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2378                             started = STARTED_CHATTER; // own kibitz we simply discard
2379                         else {
2380                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2381                             parse_pos = 0; parse[0] = NULLCHAR;
2382                             savingComment = TRUE;
2383                             suppressKibitz = gameMode != IcsObserving ? 2 :
2384                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2385                         }
2386                         continue;
2387                 } else
2388                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2389                     started = STARTED_CHATTER;
2390                     suppressKibitz = TRUE;
2391                 }
2392             } // [HGM] kibitz: end of patch
2393
2394 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2395
2396             // [HGM] chat: intercept tells by users for which we have an open chat window
2397             channel = -1;
2398             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2399                                            looking_at(buf, &i, "* whispers:") ||
2400                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2401                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2402                 int p;
2403                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2404                 chattingPartner = -1;
2405
2406                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2407                 for(p=0; p<MAX_CHAT; p++) {
2408                     if(channel == atoi(chatPartner[p])) {
2409                     talker[0] = '['; strcat(talker, "]");
2410                     chattingPartner = p; break;
2411                     }
2412                 } else
2413                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2414                 for(p=0; p<MAX_CHAT; p++) {
2415                     if(!strcmp("WHISPER", chatPartner[p])) {
2416                         talker[0] = '['; strcat(talker, "]");
2417                         chattingPartner = p; break;
2418                     }
2419                 }
2420                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2421                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2422                     talker[0] = 0;
2423                     chattingPartner = p; break;
2424                 }
2425                 if(chattingPartner<0) i = oldi; else {
2426                     started = STARTED_COMMENT;
2427                     parse_pos = 0; parse[0] = NULLCHAR;
2428                     savingComment = TRUE;
2429                     suppressKibitz = TRUE;
2430                 }
2431             } // [HGM] chat: end of patch
2432
2433             if (appData.zippyTalk || appData.zippyPlay) {
2434                 /* [DM] Backup address for color zippy lines */
2435                 backup = i;
2436 #if ZIPPY
2437        #ifdef WIN32
2438                if (loggedOn == TRUE)
2439                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2440                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2441        #else
2442                 if (ZippyControl(buf, &i) ||
2443                     ZippyConverse(buf, &i) ||
2444                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2445                       loggedOn = TRUE;
2446                       if (!appData.colorize) continue;
2447                 }
2448        #endif
2449 #endif
2450             } // [DM] 'else { ' deleted
2451                 if (
2452                     /* Regular tells and says */
2453                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2454                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2455                     looking_at(buf, &i, "* says: ") ||
2456                     /* Don't color "message" or "messages" output */
2457                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2458                     looking_at(buf, &i, "*. * at *:*: ") ||
2459                     looking_at(buf, &i, "--* (*:*): ") ||
2460                     /* Message notifications (same color as tells) */
2461                     looking_at(buf, &i, "* has left a message ") ||
2462                     looking_at(buf, &i, "* just sent you a message:\n") ||
2463                     /* Whispers and kibitzes */
2464                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2465                     looking_at(buf, &i, "* kibitzes: ") ||
2466                     /* Channel tells */
2467                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2468
2469                   if (tkind == 1 && strchr(star_match[0], ':')) {
2470                       /* Avoid "tells you:" spoofs in channels */
2471                      tkind = 3;
2472                   }
2473                   if (star_match[0][0] == NULLCHAR ||
2474                       strchr(star_match[0], ' ') ||
2475                       (tkind == 3 && strchr(star_match[1], ' '))) {
2476                     /* Reject bogus matches */
2477                     i = oldi;
2478                   } else {
2479                     if (appData.colorize) {
2480                       if (oldi > next_out) {
2481                         SendToPlayer(&buf[next_out], oldi - next_out);
2482                         next_out = oldi;
2483                       }
2484                       switch (tkind) {
2485                       case 1:
2486                         Colorize(ColorTell, FALSE);
2487                         curColor = ColorTell;
2488                         break;
2489                       case 2:
2490                         Colorize(ColorKibitz, FALSE);
2491                         curColor = ColorKibitz;
2492                         break;
2493                       case 3:
2494                         p = strrchr(star_match[1], '(');
2495                         if (p == NULL) {
2496                           p = star_match[1];
2497                         } else {
2498                           p++;
2499                         }
2500                         if (atoi(p) == 1) {
2501                           Colorize(ColorChannel1, FALSE);
2502                           curColor = ColorChannel1;
2503                         } else {
2504                           Colorize(ColorChannel, FALSE);
2505                           curColor = ColorChannel;
2506                         }
2507                         break;
2508                       case 5:
2509                         curColor = ColorNormal;
2510                         break;
2511                       }
2512                     }
2513                     if (started == STARTED_NONE && appData.autoComment &&
2514                         (gameMode == IcsObserving ||
2515                          gameMode == IcsPlayingWhite ||
2516                          gameMode == IcsPlayingBlack)) {
2517                       parse_pos = i - oldi;
2518                       memcpy(parse, &buf[oldi], parse_pos);
2519                       parse[parse_pos] = NULLCHAR;
2520                       started = STARTED_COMMENT;
2521                       savingComment = TRUE;
2522                     } else {
2523                       started = STARTED_CHATTER;
2524                       savingComment = FALSE;
2525                     }
2526                     loggedOn = TRUE;
2527                     continue;
2528                   }
2529                 }
2530
2531                 if (looking_at(buf, &i, "* s-shouts: ") ||
2532                     looking_at(buf, &i, "* c-shouts: ")) {
2533                     if (appData.colorize) {
2534                         if (oldi > next_out) {
2535                             SendToPlayer(&buf[next_out], oldi - next_out);
2536                             next_out = oldi;
2537                         }
2538                         Colorize(ColorSShout, FALSE);
2539                         curColor = ColorSShout;
2540                     }
2541                     loggedOn = TRUE;
2542                     started = STARTED_CHATTER;
2543                     continue;
2544                 }
2545
2546                 if (looking_at(buf, &i, "--->")) {
2547                     loggedOn = TRUE;
2548                     continue;
2549                 }
2550
2551                 if (looking_at(buf, &i, "* shouts: ") ||
2552                     looking_at(buf, &i, "--> ")) {
2553                     if (appData.colorize) {
2554                         if (oldi > next_out) {
2555                             SendToPlayer(&buf[next_out], oldi - next_out);
2556                             next_out = oldi;
2557                         }
2558                         Colorize(ColorShout, FALSE);
2559                         curColor = ColorShout;
2560                     }
2561                     loggedOn = TRUE;
2562                     started = STARTED_CHATTER;
2563                     continue;
2564                 }
2565
2566                 if (looking_at( buf, &i, "Challenge:")) {
2567                     if (appData.colorize) {
2568                         if (oldi > next_out) {
2569                             SendToPlayer(&buf[next_out], oldi - next_out);
2570                             next_out = oldi;
2571                         }
2572                         Colorize(ColorChallenge, FALSE);
2573                         curColor = ColorChallenge;
2574                     }
2575                     loggedOn = TRUE;
2576                     continue;
2577                 }
2578
2579                 if (looking_at(buf, &i, "* offers you") ||
2580                     looking_at(buf, &i, "* offers to be") ||
2581                     looking_at(buf, &i, "* would like to") ||
2582                     looking_at(buf, &i, "* requests to") ||
2583                     looking_at(buf, &i, "Your opponent offers") ||
2584                     looking_at(buf, &i, "Your opponent requests")) {
2585
2586                     if (appData.colorize) {
2587                         if (oldi > next_out) {
2588                             SendToPlayer(&buf[next_out], oldi - next_out);
2589                             next_out = oldi;
2590                         }
2591                         Colorize(ColorRequest, FALSE);
2592                         curColor = ColorRequest;
2593                     }
2594                     continue;
2595                 }
2596
2597                 if (looking_at(buf, &i, "* (*) seeking")) {
2598                     if (appData.colorize) {
2599                         if (oldi > next_out) {
2600                             SendToPlayer(&buf[next_out], oldi - next_out);
2601                             next_out = oldi;
2602                         }
2603                         Colorize(ColorSeek, FALSE);
2604                         curColor = ColorSeek;
2605                     }
2606                     continue;
2607             }
2608
2609             if (looking_at(buf, &i, "\\   ")) {
2610                 if (prevColor != ColorNormal) {
2611                     if (oldi > next_out) {
2612                         SendToPlayer(&buf[next_out], oldi - next_out);
2613                         next_out = oldi;
2614                     }
2615                     Colorize(prevColor, TRUE);
2616                     curColor = prevColor;
2617                 }
2618                 if (savingComment) {
2619                     parse_pos = i - oldi;
2620                     memcpy(parse, &buf[oldi], parse_pos);
2621                     parse[parse_pos] = NULLCHAR;
2622                     started = STARTED_COMMENT;
2623                 } else {
2624                     started = STARTED_CHATTER;
2625                 }
2626                 continue;
2627             }
2628
2629             if (looking_at(buf, &i, "Black Strength :") ||
2630                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2631                 looking_at(buf, &i, "<10>") ||
2632                 looking_at(buf, &i, "#@#")) {
2633                 /* Wrong board style */
2634                 loggedOn = TRUE;
2635                 SendToICS(ics_prefix);
2636                 SendToICS("set style 12\n");
2637                 SendToICS(ics_prefix);
2638                 SendToICS("refresh\n");
2639                 continue;
2640             }
2641
2642             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2643                 ICSInitScript();
2644                 have_sent_ICS_logon = 1;
2645                 continue;
2646             }
2647
2648             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2649                 (looking_at(buf, &i, "\n<12> ") ||
2650                  looking_at(buf, &i, "<12> "))) {
2651                 loggedOn = TRUE;
2652                 if (oldi > next_out) {
2653                     SendToPlayer(&buf[next_out], oldi - next_out);
2654                 }
2655                 next_out = i;
2656                 started = STARTED_BOARD;
2657                 parse_pos = 0;
2658                 continue;
2659             }
2660
2661             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2662                 looking_at(buf, &i, "<b1> ")) {
2663                 if (oldi > next_out) {
2664                     SendToPlayer(&buf[next_out], oldi - next_out);
2665                 }
2666                 next_out = i;
2667                 started = STARTED_HOLDINGS;
2668                 parse_pos = 0;
2669                 continue;
2670             }
2671
2672             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2673                 loggedOn = TRUE;
2674                 /* Header for a move list -- first line */
2675
2676                 switch (ics_getting_history) {
2677                   case H_FALSE:
2678                     switch (gameMode) {
2679                       case IcsIdle:
2680                       case BeginningOfGame:
2681                         /* User typed "moves" or "oldmoves" while we
2682                            were idle.  Pretend we asked for these
2683                            moves and soak them up so user can step
2684                            through them and/or save them.
2685                            */
2686                         Reset(FALSE, TRUE);
2687                         gameMode = IcsObserving;
2688                         ModeHighlight();
2689                         ics_gamenum = -1;
2690                         ics_getting_history = H_GOT_UNREQ_HEADER;
2691                         break;
2692                       case EditGame: /*?*/
2693                       case EditPosition: /*?*/
2694                         /* Should above feature work in these modes too? */
2695                         /* For now it doesn't */
2696                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2697                         break;
2698                       default:
2699                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2700                         break;
2701                     }
2702                     break;
2703                   case H_REQUESTED:
2704                     /* Is this the right one? */
2705                     if (gameInfo.white && gameInfo.black &&
2706                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2707                         strcmp(gameInfo.black, star_match[2]) == 0) {
2708                         /* All is well */
2709                         ics_getting_history = H_GOT_REQ_HEADER;
2710                     }
2711                     break;
2712                   case H_GOT_REQ_HEADER:
2713                   case H_GOT_UNREQ_HEADER:
2714                   case H_GOT_UNWANTED_HEADER:
2715                   case H_GETTING_MOVES:
2716                     /* Should not happen */
2717                     DisplayError(_("Error gathering move list: two headers"), 0);
2718                     ics_getting_history = H_FALSE;
2719                     break;
2720                 }
2721
2722                 /* Save player ratings into gameInfo if needed */
2723                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2724                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2725                     (gameInfo.whiteRating == -1 ||
2726                      gameInfo.blackRating == -1)) {
2727
2728                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2729                     gameInfo.blackRating = string_to_rating(star_match[3]);
2730                     if (appData.debugMode)
2731                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2732                               gameInfo.whiteRating, gameInfo.blackRating);
2733                 }
2734                 continue;
2735             }
2736
2737             if (looking_at(buf, &i,
2738               "* * match, initial time: * minute*, increment: * second")) {
2739                 /* Header for a move list -- second line */
2740                 /* Initial board will follow if this is a wild game */
2741                 if (gameInfo.event != NULL) free(gameInfo.event);
2742                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2743                 gameInfo.event = StrSave(str);
2744                 /* [HGM] we switched variant. Translate boards if needed. */
2745                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2746                 continue;
2747             }
2748
2749             if (looking_at(buf, &i, "Move  ")) {
2750                 /* Beginning of a move list */
2751                 switch (ics_getting_history) {
2752                   case H_FALSE:
2753                     /* Normally should not happen */
2754                     /* Maybe user hit reset while we were parsing */
2755                     break;
2756                   case H_REQUESTED:
2757                     /* Happens if we are ignoring a move list that is not
2758                      * the one we just requested.  Common if the user
2759                      * tries to observe two games without turning off
2760                      * getMoveList */
2761                     break;
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: nested"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                   case H_GOT_REQ_HEADER:
2768                     ics_getting_history = H_GETTING_MOVES;
2769                     started = STARTED_MOVES;
2770                     parse_pos = 0;
2771                     if (oldi > next_out) {
2772                         SendToPlayer(&buf[next_out], oldi - next_out);
2773                     }
2774                     break;
2775                   case H_GOT_UNREQ_HEADER:
2776                     ics_getting_history = H_GETTING_MOVES;
2777                     started = STARTED_MOVES_NOHIDE;
2778                     parse_pos = 0;
2779                     break;
2780                   case H_GOT_UNWANTED_HEADER:
2781                     ics_getting_history = H_FALSE;
2782                     break;
2783                 }
2784                 continue;
2785             }
2786
2787             if (looking_at(buf, &i, "% ") ||
2788                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2789                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2790                 savingComment = FALSE;
2791                 switch (started) {
2792                   case STARTED_MOVES:
2793                   case STARTED_MOVES_NOHIDE:
2794                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2795                     parse[parse_pos + i - oldi] = NULLCHAR;
2796                     ParseGameHistory(parse);
2797 #if ZIPPY
2798                     if (appData.zippyPlay && first.initDone) {
2799                         FeedMovesToProgram(&first, forwardMostMove);
2800                         if (gameMode == IcsPlayingWhite) {
2801                             if (WhiteOnMove(forwardMostMove)) {
2802                                 if (first.sendTime) {
2803                                   if (first.useColors) {
2804                                     SendToProgram("black\n", &first);
2805                                   }
2806                                   SendTimeRemaining(&first, TRUE);
2807                                 }
2808                                 if (first.useColors) {
2809                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2810                                 }
2811                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2812                                 first.maybeThinking = TRUE;
2813                             } else {
2814                                 if (first.usePlayother) {
2815                                   if (first.sendTime) {
2816                                     SendTimeRemaining(&first, TRUE);
2817                                   }
2818                                   SendToProgram("playother\n", &first);
2819                                   firstMove = FALSE;
2820                                 } else {
2821                                   firstMove = TRUE;
2822                                 }
2823                             }
2824                         } else if (gameMode == IcsPlayingBlack) {
2825                             if (!WhiteOnMove(forwardMostMove)) {
2826                                 if (first.sendTime) {
2827                                   if (first.useColors) {
2828                                     SendToProgram("white\n", &first);
2829                                   }
2830                                   SendTimeRemaining(&first, FALSE);
2831                                 }
2832                                 if (first.useColors) {
2833                                   SendToProgram("black\n", &first);
2834                                 }
2835                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2836                                 first.maybeThinking = TRUE;
2837                             } else {
2838                                 if (first.usePlayother) {
2839                                   if (first.sendTime) {
2840                                     SendTimeRemaining(&first, FALSE);
2841                                   }
2842                                   SendToProgram("playother\n", &first);
2843                                   firstMove = FALSE;
2844                                 } else {
2845                                   firstMove = TRUE;
2846                                 }
2847                             }
2848                         }
2849                     }
2850 #endif
2851                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2852                         /* Moves came from oldmoves or moves command
2853                            while we weren't doing anything else.
2854                            */
2855                         currentMove = forwardMostMove;
2856                         ClearHighlights();/*!!could figure this out*/
2857                         flipView = appData.flipView;
2858                         DrawPosition(FALSE, boards[currentMove]);
2859                         DisplayBothClocks();
2860                         sprintf(str, "%s vs. %s",
2861                                 gameInfo.white, gameInfo.black);
2862                         DisplayTitle(str);
2863                         gameMode = IcsIdle;
2864                     } else {
2865                         /* Moves were history of an active game */
2866                         if (gameInfo.resultDetails != NULL) {
2867                             free(gameInfo.resultDetails);
2868                             gameInfo.resultDetails = NULL;
2869                         }
2870                     }
2871                     HistorySet(parseList, backwardMostMove,
2872                                forwardMostMove, currentMove-1);
2873                     DisplayMove(currentMove - 1);
2874                     if (started == STARTED_MOVES) next_out = i;
2875                     started = STARTED_NONE;
2876                     ics_getting_history = H_FALSE;
2877                     break;
2878
2879                   case STARTED_OBSERVE:
2880                     started = STARTED_NONE;
2881                     SendToICS(ics_prefix);
2882                     SendToICS("refresh\n");
2883                     break;
2884
2885                   default:
2886                     break;
2887                 }
2888                 if(bookHit) { // [HGM] book: simulate book reply
2889                     static char bookMove[MSG_SIZ]; // a bit generous?
2890
2891                     programStats.nodes = programStats.depth = programStats.time =
2892                     programStats.score = programStats.got_only_move = 0;
2893                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2894
2895                     strcpy(bookMove, "move ");
2896                     strcat(bookMove, bookHit);
2897                     HandleMachineMove(bookMove, &first);
2898                 }
2899                 continue;
2900             }
2901
2902             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2903                  started == STARTED_HOLDINGS ||
2904                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2905                 /* Accumulate characters in move list or board */
2906                 parse[parse_pos++] = buf[i];
2907             }
2908
2909             /* Start of game messages.  Mostly we detect start of game
2910                when the first board image arrives.  On some versions
2911                of the ICS, though, we need to do a "refresh" after starting
2912                to observe in order to get the current board right away. */
2913             if (looking_at(buf, &i, "Adding game * to observation list")) {
2914                 started = STARTED_OBSERVE;
2915                 continue;
2916             }
2917
2918             /* Handle auto-observe */
2919             if (appData.autoObserve &&
2920                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2921                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2922                 char *player;
2923                 /* Choose the player that was highlighted, if any. */
2924                 if (star_match[0][0] == '\033' ||
2925                     star_match[1][0] != '\033') {
2926                     player = star_match[0];
2927                 } else {
2928                     player = star_match[2];
2929                 }
2930                 sprintf(str, "%sobserve %s\n",
2931                         ics_prefix, StripHighlightAndTitle(player));
2932                 SendToICS(str);
2933
2934                 /* Save ratings from notify string */
2935                 strcpy(player1Name, star_match[0]);
2936                 player1Rating = string_to_rating(star_match[1]);
2937                 strcpy(player2Name, star_match[2]);
2938                 player2Rating = string_to_rating(star_match[3]);
2939
2940                 if (appData.debugMode)
2941                   fprintf(debugFP,
2942                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2943                           player1Name, player1Rating,
2944                           player2Name, player2Rating);
2945
2946                 continue;
2947             }
2948
2949             /* Deal with automatic examine mode after a game,
2950                and with IcsObserving -> IcsExamining transition */
2951             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2952                 looking_at(buf, &i, "has made you an examiner of game *")) {
2953
2954                 int gamenum = atoi(star_match[0]);
2955                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2956                     gamenum == ics_gamenum) {
2957                     /* We were already playing or observing this game;
2958                        no need to refetch history */
2959                     gameMode = IcsExamining;
2960                     if (pausing) {
2961                         pauseExamForwardMostMove = forwardMostMove;
2962                     } else if (currentMove < forwardMostMove) {
2963                         ForwardInner(forwardMostMove);
2964                     }
2965                 } else {
2966                     /* I don't think this case really can happen */
2967                     SendToICS(ics_prefix);
2968                     SendToICS("refresh\n");
2969                 }
2970                 continue;
2971             }
2972
2973             /* Error messages */
2974 //          if (ics_user_moved) {
2975             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2976                 if (looking_at(buf, &i, "Illegal move") ||
2977                     looking_at(buf, &i, "Not a legal move") ||
2978                     looking_at(buf, &i, "Your king is in check") ||
2979                     looking_at(buf, &i, "It isn't your turn") ||
2980                     looking_at(buf, &i, "It is not your move")) {
2981                     /* Illegal move */
2982                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2983                         currentMove = --forwardMostMove;
2984                         DisplayMove(currentMove - 1); /* before DMError */
2985                         DrawPosition(FALSE, boards[currentMove]);
2986                         SwitchClocks();
2987                         DisplayBothClocks();
2988                     }
2989                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2990                     ics_user_moved = 0;
2991                     continue;
2992                 }
2993             }
2994
2995             if (looking_at(buf, &i, "still have time") ||
2996                 looking_at(buf, &i, "not out of time") ||
2997                 looking_at(buf, &i, "either player is out of time") ||
2998                 looking_at(buf, &i, "has timeseal; checking")) {
2999                 /* We must have called his flag a little too soon */
3000                 whiteFlag = blackFlag = FALSE;
3001                 continue;
3002             }
3003
3004             if (looking_at(buf, &i, "added * seconds to") ||
3005                 looking_at(buf, &i, "seconds were added to")) {
3006                 /* Update the clocks */
3007                 SendToICS(ics_prefix);
3008                 SendToICS("refresh\n");
3009                 continue;
3010             }
3011
3012             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3013                 ics_clock_paused = TRUE;
3014                 StopClocks();
3015                 continue;
3016             }
3017
3018             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3019                 ics_clock_paused = FALSE;
3020                 StartClocks();
3021                 continue;
3022             }
3023
3024             /* Grab player ratings from the Creating: message.
3025                Note we have to check for the special case when
3026                the ICS inserts things like [white] or [black]. */
3027             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3028                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3029                 /* star_matches:
3030                    0    player 1 name (not necessarily white)
3031                    1    player 1 rating
3032                    2    empty, white, or black (IGNORED)
3033                    3    player 2 name (not necessarily black)
3034                    4    player 2 rating
3035
3036                    The names/ratings are sorted out when the game
3037                    actually starts (below).
3038                 */
3039                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3040                 player1Rating = string_to_rating(star_match[1]);
3041                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3042                 player2Rating = string_to_rating(star_match[4]);
3043
3044                 if (appData.debugMode)
3045                   fprintf(debugFP,
3046                           "Ratings from 'Creating:' %s %d, %s %d\n",
3047                           player1Name, player1Rating,
3048                           player2Name, player2Rating);
3049
3050                 continue;
3051             }
3052
3053             /* Improved generic start/end-of-game messages */
3054             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3055                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3056                 /* If tkind == 0: */
3057                 /* star_match[0] is the game number */
3058                 /*           [1] is the white player's name */
3059                 /*           [2] is the black player's name */
3060                 /* For end-of-game: */
3061                 /*           [3] is the reason for the game end */
3062                 /*           [4] is a PGN end game-token, preceded by " " */
3063                 /* For start-of-game: */
3064                 /*           [3] begins with "Creating" or "Continuing" */
3065                 /*           [4] is " *" or empty (don't care). */
3066                 int gamenum = atoi(star_match[0]);
3067                 char *whitename, *blackname, *why, *endtoken;
3068                 ChessMove endtype = (ChessMove) 0;
3069
3070                 if (tkind == 0) {
3071                   whitename = star_match[1];
3072                   blackname = star_match[2];
3073                   why = star_match[3];
3074                   endtoken = star_match[4];
3075                 } else {
3076                   whitename = star_match[1];
3077                   blackname = star_match[3];
3078                   why = star_match[5];
3079                   endtoken = star_match[6];
3080                 }
3081
3082                 /* Game start messages */
3083                 if (strncmp(why, "Creating ", 9) == 0 ||
3084                     strncmp(why, "Continuing ", 11) == 0) {
3085                     gs_gamenum = gamenum;
3086                     strcpy(gs_kind, strchr(why, ' ') + 1);
3087 #if ZIPPY
3088                     if (appData.zippyPlay) {
3089                         ZippyGameStart(whitename, blackname);
3090                     }
3091 #endif /*ZIPPY*/
3092                     continue;
3093                 }
3094
3095                 /* Game end messages */
3096                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3097                     ics_gamenum != gamenum) {
3098                     continue;
3099                 }
3100                 while (endtoken[0] == ' ') endtoken++;
3101                 switch (endtoken[0]) {
3102                   case '*':
3103                   default:
3104                     endtype = GameUnfinished;
3105                     break;
3106                   case '0':
3107                     endtype = BlackWins;
3108                     break;
3109                   case '1':
3110                     if (endtoken[1] == '/')
3111                       endtype = GameIsDrawn;
3112                     else
3113                       endtype = WhiteWins;
3114                     break;
3115                 }
3116                 GameEnds(endtype, why, GE_ICS);
3117 #if ZIPPY
3118                 if (appData.zippyPlay && first.initDone) {
3119                     ZippyGameEnd(endtype, why);
3120                     if (first.pr == NULL) {
3121                       /* Start the next process early so that we'll
3122                          be ready for the next challenge */
3123                       StartChessProgram(&first);
3124                     }
3125                     /* Send "new" early, in case this command takes
3126                        a long time to finish, so that we'll be ready
3127                        for the next challenge. */
3128                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3129                     Reset(TRUE, TRUE);
3130                 }
3131 #endif /*ZIPPY*/
3132                 continue;
3133             }
3134
3135             if (looking_at(buf, &i, "Removing game * from observation") ||
3136                 looking_at(buf, &i, "no longer observing game *") ||
3137                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3138                 if (gameMode == IcsObserving &&
3139                     atoi(star_match[0]) == ics_gamenum)
3140                   {
3141                       /* icsEngineAnalyze */
3142                       if (appData.icsEngineAnalyze) {
3143                             ExitAnalyzeMode();
3144                             ModeHighlight();
3145                       }
3146                       StopClocks();
3147                       gameMode = IcsIdle;
3148                       ics_gamenum = -1;
3149                       ics_user_moved = FALSE;
3150                   }
3151                 continue;
3152             }
3153
3154             if (looking_at(buf, &i, "no longer examining game *")) {
3155                 if (gameMode == IcsExamining &&
3156                     atoi(star_match[0]) == ics_gamenum)
3157                   {
3158                       gameMode = IcsIdle;
3159                       ics_gamenum = -1;
3160                       ics_user_moved = FALSE;
3161                   }
3162                 continue;
3163             }
3164
3165             /* Advance leftover_start past any newlines we find,
3166                so only partial lines can get reparsed */
3167             if (looking_at(buf, &i, "\n")) {
3168                 prevColor = curColor;
3169                 if (curColor != ColorNormal) {
3170                     if (oldi > next_out) {
3171                         SendToPlayer(&buf[next_out], oldi - next_out);
3172                         next_out = oldi;
3173                     }
3174                     Colorize(ColorNormal, FALSE);
3175                     curColor = ColorNormal;
3176                 }
3177                 if (started == STARTED_BOARD) {
3178                     started = STARTED_NONE;
3179                     parse[parse_pos] = NULLCHAR;
3180                     ParseBoard12(parse);
3181                     ics_user_moved = 0;
3182
3183                     /* Send premove here */
3184                     if (appData.premove) {
3185                       char str[MSG_SIZ];
3186                       if (currentMove == 0 &&
3187                           gameMode == IcsPlayingWhite &&
3188                           appData.premoveWhite) {
3189                         sprintf(str, "%s%s\n", ics_prefix,
3190                                 appData.premoveWhiteText);
3191                         if (appData.debugMode)
3192                           fprintf(debugFP, "Sending premove:\n");
3193                         SendToICS(str);
3194                       } else if (currentMove == 1 &&
3195                                  gameMode == IcsPlayingBlack &&
3196                                  appData.premoveBlack) {
3197                         sprintf(str, "%s%s\n", ics_prefix,
3198                                 appData.premoveBlackText);
3199                         if (appData.debugMode)
3200                           fprintf(debugFP, "Sending premove:\n");
3201                         SendToICS(str);
3202                       } else if (gotPremove) {
3203                         gotPremove = 0;
3204                         ClearPremoveHighlights();
3205                         if (appData.debugMode)
3206                           fprintf(debugFP, "Sending premove:\n");
3207                           UserMoveEvent(premoveFromX, premoveFromY,
3208                                         premoveToX, premoveToY,
3209                                         premovePromoChar);
3210                       }
3211                     }
3212
3213                     /* Usually suppress following prompt */
3214                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3215                         if (looking_at(buf, &i, "*% ")) {
3216                             savingComment = FALSE;
3217                         }
3218                     }
3219                     next_out = i;
3220                 } else if (started == STARTED_HOLDINGS) {
3221                     int gamenum;
3222                     char new_piece[MSG_SIZ];
3223                     started = STARTED_NONE;
3224                     parse[parse_pos] = NULLCHAR;
3225                     if (appData.debugMode)
3226                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3227                                                         parse, currentMove);
3228                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3229                         gamenum == ics_gamenum) {
3230                         if (gameInfo.variant == VariantNormal) {
3231                           /* [HGM] We seem to switch variant during a game!
3232                            * Presumably no holdings were displayed, so we have
3233                            * to move the position two files to the right to
3234                            * create room for them!
3235                            */
3236                           VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3237                           /* Get a move list just to see the header, which
3238                              will tell us whether this is really bug or zh */
3239                           if (ics_getting_history == H_FALSE) {
3240                             ics_getting_history = H_REQUESTED;
3241                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3242                             SendToICS(str);
3243                           }
3244                         }
3245                         new_piece[0] = NULLCHAR;
3246                         sscanf(parse, "game %d white [%s black [%s <- %s",
3247                                &gamenum, white_holding, black_holding,
3248                                new_piece);
3249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3251                         /* [HGM] copy holdings to board holdings area */
3252                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3253                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3254 #if ZIPPY
3255                         if (appData.zippyPlay && first.initDone) {
3256                             ZippyHoldings(white_holding, black_holding,
3257                                           new_piece);
3258                         }
3259 #endif /*ZIPPY*/
3260                         if (tinyLayout || smallLayout) {
3261                             char wh[16], bh[16];
3262                             PackHolding(wh, white_holding);
3263                             PackHolding(bh, black_holding);
3264                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3265                                     gameInfo.white, gameInfo.black);
3266                         } else {
3267                             sprintf(str, "%s [%s] vs. %s [%s]",
3268                                     gameInfo.white, white_holding,
3269                                     gameInfo.black, black_holding);
3270                         }
3271
3272                         DrawPosition(FALSE, boards[currentMove]);
3273                         DisplayTitle(str);
3274                     }
3275                     /* Suppress following prompt */
3276                     if (looking_at(buf, &i, "*% ")) {
3277                         savingComment = FALSE;
3278                     }
3279                     next_out = i;
3280                 }
3281                 continue;
3282             }
3283
3284             i++;                /* skip unparsed character and loop back */
3285         }
3286
3287         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3288             started != STARTED_HOLDINGS && i > next_out) {
3289             SendToPlayer(&buf[next_out], i - next_out);
3290             next_out = i;
3291         }
3292         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3293
3294         leftover_len = buf_len - leftover_start;
3295         /* if buffer ends with something we couldn't parse,
3296            reparse it after appending the next read */
3297
3298     } else if (count == 0) {
3299         RemoveInputSource(isr);
3300         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3301     } else {
3302         DisplayFatalError(_("Error reading from ICS"), error, 1);
3303     }
3304 }
3305
3306
3307 /* Board style 12 looks like this:
3308
3309    <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
3310
3311  * The "<12> " is stripped before it gets to this routine.  The two
3312  * trailing 0's (flip state and clock ticking) are later addition, and
3313  * some chess servers may not have them, or may have only the first.
3314  * Additional trailing fields may be added in the future.
3315  */
3316
3317 #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"
3318
3319 #define RELATION_OBSERVING_PLAYED    0
3320 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3321 #define RELATION_PLAYING_MYMOVE      1
3322 #define RELATION_PLAYING_NOTMYMOVE  -1
3323 #define RELATION_EXAMINING           2
3324 #define RELATION_ISOLATED_BOARD     -3
3325 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3326
3327 void
3328 ParseBoard12(string)
3329      char *string;
3330 {
3331     GameMode newGameMode;
3332     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3333     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3334     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3335     char to_play, board_chars[200];
3336     char move_str[500], str[500], elapsed_time[500];
3337     char black[32], white[32];
3338     Board board;
3339     int prevMove = currentMove;
3340     int ticking = 2;
3341     ChessMove moveType;
3342     int fromX, fromY, toX, toY;
3343     char promoChar;
3344     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3345     char *bookHit = NULL; // [HGM] book
3346
3347     fromX = fromY = toX = toY = -1;
3348
3349     newGame = FALSE;
3350
3351     if (appData.debugMode)
3352       fprintf(debugFP, _("Parsing board: %s\n"), string);
3353
3354     move_str[0] = NULLCHAR;
3355     elapsed_time[0] = NULLCHAR;
3356     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3357         int  i = 0, j;
3358         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3359             if(string[i] == ' ') { ranks++; files = 0; }
3360             else files++;
3361             i++;
3362         }
3363         for(j = 0; j <i; j++) board_chars[j] = string[j];
3364         board_chars[i] = '\0';
3365         string += i + 1;
3366     }
3367     n = sscanf(string, PATTERN, &to_play, &double_push,
3368                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3369                &gamenum, white, black, &relation, &basetime, &increment,
3370                &white_stren, &black_stren, &white_time, &black_time,
3371                &moveNum, str, elapsed_time, move_str, &ics_flip,
3372                &ticking);
3373
3374     if (n < 21) {
3375         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3376         DisplayError(str, 0);
3377         return;
3378     }
3379
3380     /* Convert the move number to internal form */
3381     moveNum = (moveNum - 1) * 2;
3382     if (to_play == 'B') moveNum++;
3383     if (moveNum >= MAX_MOVES) {
3384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3385                         0, 1);
3386       return;
3387     }
3388
3389     switch (relation) {
3390       case RELATION_OBSERVING_PLAYED:
3391       case RELATION_OBSERVING_STATIC:
3392         if (gamenum == -1) {
3393             /* Old ICC buglet */
3394             relation = RELATION_OBSERVING_STATIC;
3395         }
3396         newGameMode = IcsObserving;
3397         break;
3398       case RELATION_PLAYING_MYMOVE:
3399       case RELATION_PLAYING_NOTMYMOVE:
3400         newGameMode =
3401           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3402             IcsPlayingWhite : IcsPlayingBlack;
3403         break;
3404       case RELATION_EXAMINING:
3405         newGameMode = IcsExamining;
3406         break;
3407       case RELATION_ISOLATED_BOARD:
3408       default:
3409         /* Just display this board.  If user was doing something else,
3410            we will forget about it until the next board comes. */
3411         newGameMode = IcsIdle;
3412         break;
3413       case RELATION_STARTING_POSITION:
3414         newGameMode = gameMode;
3415         break;
3416     }
3417
3418     /* Modify behavior for initial board display on move listing
3419        of wild games.
3420        */
3421     switch (ics_getting_history) {
3422       case H_FALSE:
3423       case H_REQUESTED:
3424         break;
3425       case H_GOT_REQ_HEADER:
3426       case H_GOT_UNREQ_HEADER:
3427         /* This is the initial position of the current game */
3428         gamenum = ics_gamenum;
3429         moveNum = 0;            /* old ICS bug workaround */
3430         if (to_play == 'B') {
3431           startedFromSetupPosition = TRUE;
3432           blackPlaysFirst = TRUE;
3433           moveNum = 1;
3434           if (forwardMostMove == 0) forwardMostMove = 1;
3435           if (backwardMostMove == 0) backwardMostMove = 1;
3436           if (currentMove == 0) currentMove = 1;
3437         }
3438         newGameMode = gameMode;
3439         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3440         break;
3441       case H_GOT_UNWANTED_HEADER:
3442         /* This is an initial board that we don't want */
3443         return;
3444       case H_GETTING_MOVES:
3445         /* Should not happen */
3446         DisplayError(_("Error gathering move list: extra board"), 0);
3447         ics_getting_history = H_FALSE;
3448         return;
3449     }
3450
3451     /* Take action if this is the first board of a new game, or of a
3452        different game than is currently being displayed.  */
3453     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3454         relation == RELATION_ISOLATED_BOARD) {
3455
3456         /* Forget the old game and get the history (if any) of the new one */
3457         if (gameMode != BeginningOfGame) {
3458           Reset(FALSE, TRUE);
3459         }
3460         newGame = TRUE;
3461         if (appData.autoRaiseBoard) BoardToTop();
3462         prevMove = -3;
3463         if (gamenum == -1) {
3464             newGameMode = IcsIdle;
3465         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3466                    appData.getMoveList) {
3467             /* Need to get game history */
3468             ics_getting_history = H_REQUESTED;
3469             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3470             SendToICS(str);
3471         }
3472
3473         /* Initially flip the board to have black on the bottom if playing
3474            black or if the ICS flip flag is set, but let the user change
3475            it with the Flip View button. */
3476         flipView = appData.autoFlipView ?
3477           (newGameMode == IcsPlayingBlack) || ics_flip :
3478           appData.flipView;
3479
3480         /* Done with values from previous mode; copy in new ones */
3481         gameMode = newGameMode;
3482         ModeHighlight();
3483         ics_gamenum = gamenum;
3484         if (gamenum == gs_gamenum) {
3485             int klen = strlen(gs_kind);
3486             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3487             sprintf(str, "ICS %s", gs_kind);
3488             gameInfo.event = StrSave(str);
3489         } else {
3490             gameInfo.event = StrSave("ICS game");
3491         }
3492         gameInfo.site = StrSave(appData.icsHost);
3493         gameInfo.date = PGNDate();
3494         gameInfo.round = StrSave("-");
3495         gameInfo.white = StrSave(white);
3496         gameInfo.black = StrSave(black);
3497         timeControl = basetime * 60 * 1000;
3498         timeControl_2 = 0;
3499         timeIncrement = increment * 1000;
3500         movesPerSession = 0;
3501         gameInfo.timeControl = TimeControlTagValue();
3502         VariantSwitch(board, StringToVariant(gameInfo.event) );
3503   if (appData.debugMode) {
3504     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3505     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3506     setbuf(debugFP, NULL);
3507   }
3508
3509         gameInfo.outOfBook = NULL;
3510
3511         /* Do we have the ratings? */
3512         if (strcmp(player1Name, white) == 0 &&
3513             strcmp(player2Name, black) == 0) {
3514             if (appData.debugMode)
3515               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3516                       player1Rating, player2Rating);
3517             gameInfo.whiteRating = player1Rating;
3518             gameInfo.blackRating = player2Rating;
3519         } else if (strcmp(player2Name, white) == 0 &&
3520                    strcmp(player1Name, black) == 0) {
3521             if (appData.debugMode)
3522               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3523                       player2Rating, player1Rating);
3524             gameInfo.whiteRating = player2Rating;
3525             gameInfo.blackRating = player1Rating;
3526         }
3527         player1Name[0] = player2Name[0] = NULLCHAR;
3528
3529         /* Silence shouts if requested */
3530         if (appData.quietPlay &&
3531             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3532             SendToICS(ics_prefix);
3533             SendToICS("set shout 0\n");
3534         }
3535     }
3536
3537     /* Deal with midgame name changes */
3538     if (!newGame) {
3539         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3540             if (gameInfo.white) free(gameInfo.white);
3541             gameInfo.white = StrSave(white);
3542         }
3543         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3544             if (gameInfo.black) free(gameInfo.black);
3545             gameInfo.black = StrSave(black);
3546         }
3547     }
3548
3549     /* Throw away game result if anything actually changes in examine mode */
3550     if (gameMode == IcsExamining && !newGame) {
3551         gameInfo.result = GameUnfinished;
3552         if (gameInfo.resultDetails != NULL) {
3553             free(gameInfo.resultDetails);
3554             gameInfo.resultDetails = NULL;
3555         }
3556     }
3557
3558     /* In pausing && IcsExamining mode, we ignore boards coming
3559        in if they are in a different variation than we are. */
3560     if (pauseExamInvalid) return;
3561     if (pausing && gameMode == IcsExamining) {
3562         if (moveNum <= pauseExamForwardMostMove) {
3563             pauseExamInvalid = TRUE;
3564             forwardMostMove = pauseExamForwardMostMove;
3565             return;
3566         }
3567     }
3568
3569   if (appData.debugMode) {
3570     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3571   }
3572     /* Parse the board */
3573     for (k = 0; k < ranks; k++) {
3574       for (j = 0; j < files; j++)
3575         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3576       if(gameInfo.holdingsWidth > 1) {
3577            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3578            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3579       }
3580     }
3581     CopyBoard(boards[moveNum], board);
3582     if (moveNum == 0) {
3583         startedFromSetupPosition =
3584           !CompareBoards(board, initialPosition);
3585         if(startedFromSetupPosition)
3586             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3587     }
3588
3589     /* [HGM] Set castling rights. Take the outermost Rooks,
3590        to make it also work for FRC opening positions. Note that board12
3591        is really defective for later FRC positions, as it has no way to
3592        indicate which Rook can castle if they are on the same side of King.
3593        For the initial position we grant rights to the outermost Rooks,
3594        and remember thos rights, and we then copy them on positions
3595        later in an FRC game. This means WB might not recognize castlings with
3596        Rooks that have moved back to their original position as illegal,
3597        but in ICS mode that is not its job anyway.
3598     */
3599     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3600     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3601
3602         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3603             if(board[0][i] == WhiteRook) j = i;
3604         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3606             if(board[0][i] == WhiteRook) j = i;
3607         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3609             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3611         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3612             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3613         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3614
3615         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3616         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3617             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3618         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3619             if(board[BOARD_HEIGHT-1][k] == bKing)
3620                 initialRights[5] = castlingRights[moveNum][5] = k;
3621     } else { int r;
3622         r = castlingRights[moveNum][0] = initialRights[0];
3623         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3624         r = castlingRights[moveNum][1] = initialRights[1];
3625         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3626         r = castlingRights[moveNum][3] = initialRights[3];
3627         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3628         r = castlingRights[moveNum][4] = initialRights[4];
3629         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3630         /* wildcastle kludge: always assume King has rights */
3631         r = castlingRights[moveNum][2] = initialRights[2];
3632         r = castlingRights[moveNum][5] = initialRights[5];
3633     }
3634     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3635     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3636
3637
3638     if (ics_getting_history == H_GOT_REQ_HEADER ||
3639         ics_getting_history == H_GOT_UNREQ_HEADER) {
3640         /* This was an initial position from a move list, not
3641            the current position */
3642         return;
3643     }
3644
3645     /* Update currentMove and known move number limits */
3646     newMove = newGame || moveNum > forwardMostMove;
3647
3648     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3649     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3650         takeback = forwardMostMove - moveNum;
3651         for (i = 0; i < takeback; i++) {
3652              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3653              SendToProgram("undo\n", &first);
3654         }
3655     }
3656
3657     if (newGame) {
3658         forwardMostMove = backwardMostMove = currentMove = moveNum;
3659         if (gameMode == IcsExamining && moveNum == 0) {
3660           /* Workaround for ICS limitation: we are not told the wild
3661              type when starting to examine a game.  But if we ask for
3662              the move list, the move list header will tell us */
3663             ics_getting_history = H_REQUESTED;
3664             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3665             SendToICS(str);
3666         }
3667     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3668                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3669         forwardMostMove = moveNum;
3670         if (!pausing || currentMove > forwardMostMove)
3671           currentMove = forwardMostMove;
3672     } else {
3673         /* New part of history that is not contiguous with old part */
3674         if (pausing && gameMode == IcsExamining) {
3675             pauseExamInvalid = TRUE;
3676             forwardMostMove = pauseExamForwardMostMove;
3677             return;
3678         }
3679         forwardMostMove = backwardMostMove = currentMove = moveNum;
3680         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3681             ics_getting_history = H_REQUESTED;
3682             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3683             SendToICS(str);
3684         }
3685     }
3686
3687     /* Update the clocks */
3688     if (strchr(elapsed_time, '.')) {
3689       /* Time is in ms */
3690       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3691       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3692     } else {
3693       /* Time is in seconds */
3694       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3695       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3696     }
3697
3698
3699 #if ZIPPY
3700     if (appData.zippyPlay && newGame &&
3701         gameMode != IcsObserving && gameMode != IcsIdle &&
3702         gameMode != IcsExamining)
3703       ZippyFirstBoard(moveNum, basetime, increment);
3704 #endif
3705
3706     /* Put the move on the move list, first converting
3707        to canonical algebraic form. */
3708     if (moveNum > 0) {
3709   if (appData.debugMode) {
3710     if (appData.debugMode) { int f = forwardMostMove;
3711         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3712                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3713     }
3714     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3715     fprintf(debugFP, "moveNum = %d\n", moveNum);
3716     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3717     setbuf(debugFP, NULL);
3718   }
3719         if (moveNum <= backwardMostMove) {
3720             /* We don't know what the board looked like before
3721                this move.  Punt. */
3722             strcpy(parseList[moveNum - 1], move_str);
3723             strcat(parseList[moveNum - 1], " ");
3724             strcat(parseList[moveNum - 1], elapsed_time);
3725             moveList[moveNum - 1][0] = NULLCHAR;
3726         } else if (strcmp(move_str, "none") == 0) {
3727             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3728             /* Again, we don't know what the board looked like;
3729                this is really the start of the game. */
3730             parseList[moveNum - 1][0] = NULLCHAR;
3731             moveList[moveNum - 1][0] = NULLCHAR;
3732             backwardMostMove = moveNum;
3733             startedFromSetupPosition = TRUE;
3734             fromX = fromY = toX = toY = -1;
3735         } else {
3736           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3737           //                 So we parse the long-algebraic move string in stead of the SAN move
3738           int valid; char buf[MSG_SIZ], *prom;
3739
3740           // str looks something like "Q/a1-a2"; kill the slash
3741           if(str[1] == '/')
3742                 sprintf(buf, "%c%s", str[0], str+2);
3743           else  strcpy(buf, str); // might be castling
3744           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3745                 strcat(buf, prom); // long move lacks promo specification!
3746           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3747                 if(appData.debugMode)
3748                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3749                 strcpy(move_str, buf);
3750           }
3751           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3752                                 &fromX, &fromY, &toX, &toY, &promoChar)
3753                || ParseOneMove(buf, moveNum - 1, &moveType,
3754                                 &fromX, &fromY, &toX, &toY, &promoChar);
3755           // end of long SAN patch
3756           if (valid) {
3757             (void) CoordsToAlgebraic(boards[moveNum - 1],
3758                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3759                                      fromY, fromX, toY, toX, promoChar,
3760                                      parseList[moveNum-1]);
3761             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3762                              castlingRights[moveNum]) ) {
3763               case MT_NONE:
3764               case MT_STALEMATE:
3765               default:
3766                 break;
3767               case MT_CHECK:
3768                 if(gameInfo.variant != VariantShogi)
3769                     strcat(parseList[moveNum - 1], "+");
3770                 break;
3771               case MT_CHECKMATE:
3772               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3773                 strcat(parseList[moveNum - 1], "#");
3774                 break;
3775             }
3776             strcat(parseList[moveNum - 1], " ");
3777             strcat(parseList[moveNum - 1], elapsed_time);
3778             /* currentMoveString is set as a side-effect of ParseOneMove */
3779             strcpy(moveList[moveNum - 1], currentMoveString);
3780             strcat(moveList[moveNum - 1], "\n");
3781           } else {
3782             /* Move from ICS was illegal!?  Punt. */
3783   if (appData.debugMode) {
3784     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3785     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3786   }
3787             strcpy(parseList[moveNum - 1], move_str);
3788             strcat(parseList[moveNum - 1], " ");
3789             strcat(parseList[moveNum - 1], elapsed_time);
3790             moveList[moveNum - 1][0] = NULLCHAR;
3791             fromX = fromY = toX = toY = -1;
3792           }
3793         }
3794   if (appData.debugMode) {
3795     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3796     setbuf(debugFP, NULL);
3797   }
3798
3799 #if ZIPPY
3800         /* Send move to chess program (BEFORE animating it). */
3801         if (appData.zippyPlay && !newGame && newMove &&
3802            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3803
3804             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3805                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3806                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3807                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3808                             move_str);
3809                     DisplayError(str, 0);
3810                 } else {
3811                     if (first.sendTime) {
3812                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3813                     }
3814                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3815                     if (firstMove && !bookHit) {
3816                         firstMove = FALSE;
3817                         if (first.useColors) {
3818                           SendToProgram(gameMode == IcsPlayingWhite ?
3819                                         "white\ngo\n" :
3820                                         "black\ngo\n", &first);
3821                         } else {
3822                           SendToProgram("go\n", &first);
3823                         }
3824                         first.maybeThinking = TRUE;
3825                     }
3826                 }
3827             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3828               if (moveList[moveNum - 1][0] == NULLCHAR) {
3829                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3830                 DisplayError(str, 0);
3831               } else {
3832                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3833                 SendMoveToProgram(moveNum - 1, &first);
3834               }
3835             }
3836         }
3837 #endif
3838     }
3839
3840     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3841         /* If move comes from a remote source, animate it.  If it
3842            isn't remote, it will have already been animated. */
3843         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3844             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3845         }
3846         if (!pausing && appData.highlightLastMove) {
3847             SetHighlights(fromX, fromY, toX, toY);
3848         }
3849     }
3850
3851     /* Start the clocks */
3852     whiteFlag = blackFlag = FALSE;
3853     appData.clockMode = !(basetime == 0 && increment == 0);
3854     if (ticking == 0) {
3855       ics_clock_paused = TRUE;
3856       StopClocks();
3857     } else if (ticking == 1) {
3858       ics_clock_paused = FALSE;
3859     }
3860     if (gameMode == IcsIdle ||
3861         relation == RELATION_OBSERVING_STATIC ||
3862         relation == RELATION_EXAMINING ||
3863         ics_clock_paused)
3864       DisplayBothClocks();
3865     else
3866       StartClocks();
3867
3868     /* Display opponents and material strengths */
3869     if (gameInfo.variant != VariantBughouse &&
3870         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3871         if (tinyLayout || smallLayout) {
3872             if(gameInfo.variant == VariantNormal)
3873                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3874                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3875                     basetime, increment);
3876             else
3877                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3878                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3879                     basetime, increment, (int) gameInfo.variant);
3880         } else {
3881             if(gameInfo.variant == VariantNormal)
3882                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3883                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3884                     basetime, increment);
3885             else
3886                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3887                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3888                     basetime, increment, VariantName(gameInfo.variant));
3889         }
3890         DisplayTitle(str);
3891   if (appData.debugMode) {
3892     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3893   }
3894     }
3895
3896
3897     /* Display the board */
3898     if (!pausing && !appData.noGUI) {
3899       if (appData.premove)
3900           if (!gotPremove ||
3901              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3902              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3903               ClearPremoveHighlights();
3904
3905       DrawPosition(FALSE, boards[currentMove]);
3906       DisplayMove(moveNum - 1);
3907       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3908             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3909               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3910         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3911       }
3912     }
3913
3914     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3915 #if ZIPPY
3916     if(bookHit) { // [HGM] book: simulate book reply
3917         static char bookMove[MSG_SIZ]; // a bit generous?
3918
3919         programStats.nodes = programStats.depth = programStats.time =
3920         programStats.score = programStats.got_only_move = 0;
3921         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3922
3923         strcpy(bookMove, "move ");
3924         strcat(bookMove, bookHit);
3925         HandleMachineMove(bookMove, &first);
3926     }
3927 #endif
3928 }
3929
3930 void
3931 GetMoveListEvent()
3932 {
3933     char buf[MSG_SIZ];
3934     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3935         ics_getting_history = H_REQUESTED;
3936         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3937         SendToICS(buf);
3938     }
3939 }
3940
3941 void
3942 AnalysisPeriodicEvent(force)
3943      int force;
3944 {
3945     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3946          && !force) || !appData.periodicUpdates)
3947       return;
3948
3949     /* Send . command to Crafty to collect stats */
3950     SendToProgram(".\n", &first);
3951
3952     /* Don't send another until we get a response (this makes
3953        us stop sending to old Crafty's which don't understand
3954        the "." command (sending illegal cmds resets node count & time,
3955        which looks bad)) */
3956     programStats.ok_to_send = 0;
3957 }
3958
3959 void ics_update_width(new_width)
3960         int new_width;
3961 {
3962         ics_printf("set width %d\n", new_width);
3963 }
3964
3965 void
3966 SendMoveToProgram(moveNum, cps)
3967      int moveNum;
3968      ChessProgramState *cps;
3969 {
3970     char buf[MSG_SIZ];
3971
3972     if (cps->useUsermove) {
3973       SendToProgram("usermove ", cps);
3974     }
3975     if (cps->useSAN) {
3976       char *space;
3977       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3978         int len = space - parseList[moveNum];
3979         memcpy(buf, parseList[moveNum], len);
3980         buf[len++] = '\n';
3981         buf[len] = NULLCHAR;
3982       } else {
3983         sprintf(buf, "%s\n", parseList[moveNum]);
3984       }
3985       SendToProgram(buf, cps);
3986     } else {
3987       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3988         AlphaRank(moveList[moveNum], 4);
3989         SendToProgram(moveList[moveNum], cps);
3990         AlphaRank(moveList[moveNum], 4); // and back
3991       } else
3992       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3993        * the engine. It would be nice to have a better way to identify castle
3994        * moves here. */
3995       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3996                                                                          && cps->useOOCastle) {
3997         int fromX = moveList[moveNum][0] - AAA;
3998         int fromY = moveList[moveNum][1] - ONE;
3999         int toX = moveList[moveNum][2] - AAA;
4000         int toY = moveList[moveNum][3] - ONE;
4001         if((boards[moveNum][fromY][fromX] == WhiteKing
4002             && boards[moveNum][toY][toX] == WhiteRook)
4003            || (boards[moveNum][fromY][fromX] == BlackKing
4004                && boards[moveNum][toY][toX] == BlackRook)) {
4005           if(toX > fromX) SendToProgram("O-O\n", cps);
4006           else SendToProgram("O-O-O\n", cps);
4007         }
4008         else SendToProgram(moveList[moveNum], cps);
4009       }
4010       else SendToProgram(moveList[moveNum], cps);
4011       /* End of additions by Tord */
4012     }
4013
4014     /* [HGM] setting up the opening has brought engine in force mode! */
4015     /*       Send 'go' if we are in a mode where machine should play. */
4016     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4017         (gameMode == TwoMachinesPlay   ||
4018 #ifdef ZIPPY
4019          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4020 #endif
4021          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4022         SendToProgram("go\n", cps);
4023   if (appData.debugMode) {
4024     fprintf(debugFP, "(extra)\n");
4025   }
4026     }
4027     setboardSpoiledMachineBlack = 0;
4028 }
4029
4030 void
4031 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4032      ChessMove moveType;
4033      int fromX, fromY, toX, toY;
4034 {
4035     char user_move[MSG_SIZ];
4036
4037     switch (moveType) {
4038       default:
4039         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4040                 (int)moveType, fromX, fromY, toX, toY);
4041         DisplayError(user_move + strlen("say "), 0);
4042         break;
4043       case WhiteKingSideCastle:
4044       case BlackKingSideCastle:
4045       case WhiteQueenSideCastleWild:
4046       case BlackQueenSideCastleWild:
4047       /* PUSH Fabien */
4048       case WhiteHSideCastleFR:
4049       case BlackHSideCastleFR:
4050       /* POP Fabien */
4051         sprintf(user_move, "o-o\n");
4052         break;
4053       case WhiteQueenSideCastle:
4054       case BlackQueenSideCastle:
4055       case WhiteKingSideCastleWild:
4056       case BlackKingSideCastleWild:
4057       /* PUSH Fabien */
4058       case WhiteASideCastleFR:
4059       case BlackASideCastleFR:
4060       /* POP Fabien */
4061         sprintf(user_move, "o-o-o\n");
4062         break;
4063       case WhitePromotionQueen:
4064       case BlackPromotionQueen:
4065       case WhitePromotionRook:
4066       case BlackPromotionRook:
4067       case WhitePromotionBishop:
4068       case BlackPromotionBishop:
4069       case WhitePromotionKnight:
4070       case BlackPromotionKnight:
4071       case WhitePromotionKing:
4072       case BlackPromotionKing:
4073       case WhitePromotionChancellor:
4074       case BlackPromotionChancellor:
4075       case WhitePromotionArchbishop:
4076       case BlackPromotionArchbishop:
4077         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4078             sprintf(user_move, "%c%c%c%c=%c\n",
4079                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4080                 PieceToChar(WhiteFerz));
4081         else if(gameInfo.variant == VariantGreat)
4082             sprintf(user_move, "%c%c%c%c=%c\n",
4083                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4084                 PieceToChar(WhiteMan));
4085         else
4086             sprintf(user_move, "%c%c%c%c=%c\n",
4087                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4088                 PieceToChar(PromoPiece(moveType)));
4089         break;
4090       case WhiteDrop:
4091       case BlackDrop:
4092         sprintf(user_move, "%c@%c%c\n",
4093                 ToUpper(PieceToChar((ChessSquare) fromX)),
4094                 AAA + toX, ONE + toY);
4095         break;
4096       case NormalMove:
4097       case WhiteCapturesEnPassant:
4098       case BlackCapturesEnPassant:
4099       case IllegalMove:  /* could be a variant we don't quite understand */
4100         sprintf(user_move, "%c%c%c%c\n",
4101                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4102         break;
4103     }
4104     SendToICS(user_move);
4105     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4106         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4107 }
4108
4109 void
4110 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4111      int rf, ff, rt, ft;
4112      char promoChar;
4113      char move[7];
4114 {
4115     if (rf == DROP_RANK) {
4116         sprintf(move, "%c@%c%c\n",
4117                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4118     } else {
4119         if (promoChar == 'x' || promoChar == NULLCHAR) {
4120             sprintf(move, "%c%c%c%c\n",
4121                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4122         } else {
4123             sprintf(move, "%c%c%c%c%c\n",
4124                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4125         }
4126     }
4127 }
4128
4129 void
4130 ProcessICSInitScript(f)
4131      FILE *f;
4132 {
4133     char buf[MSG_SIZ];
4134
4135     while (fgets(buf, MSG_SIZ, f)) {
4136         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4137     }
4138
4139     fclose(f);
4140 }
4141
4142
4143 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4144 void
4145 AlphaRank(char *move, int n)
4146 {
4147 //    char *p = move, c; int x, y;
4148
4149     if (appData.debugMode) {
4150         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4151     }
4152
4153     if(move[1]=='*' &&
4154        move[2]>='0' && move[2]<='9' &&
4155        move[3]>='a' && move[3]<='x'    ) {
4156         move[1] = '@';
4157         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4158         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4159     } else
4160     if(move[0]>='0' && move[0]<='9' &&
4161        move[1]>='a' && move[1]<='x' &&
4162        move[2]>='0' && move[2]<='9' &&
4163        move[3]>='a' && move[3]<='x'    ) {
4164         /* input move, Shogi -> normal */
4165         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4166         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4167         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4168         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4169     } else
4170     if(move[1]=='@' &&
4171        move[3]>='0' && move[3]<='9' &&
4172        move[2]>='a' && move[2]<='x'    ) {
4173         move[1] = '*';
4174         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4175         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4176     } else
4177     if(
4178        move[0]>='a' && move[0]<='x' &&
4179        move[3]>='0' && move[3]<='9' &&
4180        move[2]>='a' && move[2]<='x'    ) {
4181          /* output move, normal -> Shogi */
4182         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4183         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4184         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4185         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4186         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4187     }
4188     if (appData.debugMode) {
4189         fprintf(debugFP, "   out = '%s'\n", move);
4190     }
4191 }
4192
4193 /* Parser for moves from gnuchess, ICS, or user typein box */
4194 Boolean
4195 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4196      char *move;
4197      int moveNum;
4198      ChessMove *moveType;
4199      int *fromX, *fromY, *toX, *toY;
4200      char *promoChar;
4201 {
4202     if (appData.debugMode) {
4203         fprintf(debugFP, "move to parse: %s\n", move);
4204     }
4205     *moveType = yylexstr(moveNum, move);
4206
4207     switch (*moveType) {
4208       case WhitePromotionChancellor:
4209       case BlackPromotionChancellor:
4210       case WhitePromotionArchbishop:
4211       case BlackPromotionArchbishop:
4212       case WhitePromotionQueen:
4213       case BlackPromotionQueen:
4214       case WhitePromotionRook:
4215       case BlackPromotionRook:
4216       case WhitePromotionBishop:
4217       case BlackPromotionBishop:
4218       case WhitePromotionKnight:
4219       case BlackPromotionKnight:
4220       case WhitePromotionKing:
4221       case BlackPromotionKing:
4222       case NormalMove:
4223       case WhiteCapturesEnPassant:
4224       case BlackCapturesEnPassant:
4225       case WhiteKingSideCastle:
4226       case WhiteQueenSideCastle:
4227       case BlackKingSideCastle:
4228       case BlackQueenSideCastle:
4229       case WhiteKingSideCastleWild:
4230       case WhiteQueenSideCastleWild:
4231       case BlackKingSideCastleWild:
4232       case BlackQueenSideCastleWild:
4233       /* Code added by Tord: */
4234       case WhiteHSideCastleFR:
4235       case WhiteASideCastleFR:
4236       case BlackHSideCastleFR:
4237       case BlackASideCastleFR:
4238       /* End of code added by Tord */
4239       case IllegalMove:         /* bug or odd chess variant */
4240         *fromX = currentMoveString[0] - AAA;
4241         *fromY = currentMoveString[1] - ONE;
4242         *toX = currentMoveString[2] - AAA;
4243         *toY = currentMoveString[3] - ONE;
4244         *promoChar = currentMoveString[4];
4245         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4246             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4247     if (appData.debugMode) {
4248         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4249     }
4250             *fromX = *fromY = *toX = *toY = 0;
4251             return FALSE;
4252         }
4253         if (appData.testLegality) {
4254           return (*moveType != IllegalMove);
4255         } else {
4256           return !(fromX == fromY && toX == toY);
4257         }
4258
4259       case WhiteDrop:
4260       case BlackDrop:
4261         *fromX = *moveType == WhiteDrop ?
4262           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4263           (int) CharToPiece(ToLower(currentMoveString[0]));
4264         *fromY = DROP_RANK;
4265         *toX = currentMoveString[2] - AAA;
4266         *toY = currentMoveString[3] - ONE;
4267         *promoChar = NULLCHAR;
4268         return TRUE;
4269
4270       case AmbiguousMove:
4271       case ImpossibleMove:
4272       case (ChessMove) 0:       /* end of file */
4273       case ElapsedTime:
4274       case Comment:
4275       case PGNTag:
4276       case NAG:
4277       case WhiteWins:
4278       case BlackWins:
4279       case GameIsDrawn:
4280       default:
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4283     }
4284         /* bug? */
4285         *fromX = *fromY = *toX = *toY = 0;
4286         *promoChar = NULLCHAR;
4287         return FALSE;
4288     }
4289 }
4290
4291 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4292 // All positions will have equal probability, but the current method will not provide a unique
4293 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4294 #define DARK 1
4295 #define LITE 2
4296 #define ANY 3
4297
4298 int squaresLeft[4];
4299 int piecesLeft[(int)BlackPawn];
4300 int seed, nrOfShuffles;
4301
4302 void GetPositionNumber()
4303 {       // sets global variable seed
4304         int i;
4305
4306         seed = appData.defaultFrcPosition;
4307         if(seed < 0) { // randomize based on time for negative FRC position numbers
4308                 for(i=0; i<50; i++) seed += random();
4309                 seed = random() ^ random() >> 8 ^ random() << 8;
4310                 if(seed<0) seed = -seed;
4311         }
4312 }
4313
4314 int put(Board board, int pieceType, int rank, int n, int shade)
4315 // put the piece on the (n-1)-th empty squares of the given shade
4316 {
4317         int i;
4318
4319         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4320                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4321                         board[rank][i] = (ChessSquare) pieceType;
4322                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4323                         squaresLeft[ANY]--;
4324                         piecesLeft[pieceType]--;
4325                         return i;
4326                 }
4327         }
4328         return -1;
4329 }
4330
4331
4332 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4333 // calculate where the next piece goes, (any empty square), and put it there
4334 {
4335         int i;
4336
4337         i = seed % squaresLeft[shade];
4338         nrOfShuffles *= squaresLeft[shade];
4339         seed /= squaresLeft[shade];
4340         put(board, pieceType, rank, i, shade);
4341 }
4342
4343 void AddTwoPieces(Board board, int pieceType, int rank)
4344 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4345 {
4346         int i, n=squaresLeft[ANY], j=n-1, k;
4347
4348         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4349         i = seed % k;  // pick one
4350         nrOfShuffles *= k;
4351         seed /= k;
4352         while(i >= j) i -= j--;
4353         j = n - 1 - j; i += j;
4354         put(board, pieceType, rank, j, ANY);
4355         put(board, pieceType, rank, i, ANY);
4356 }
4357
4358 void SetUpShuffle(Board board, int number)
4359 {
4360         int i, p, first=1;
4361
4362         GetPositionNumber(); nrOfShuffles = 1;
4363
4364         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4365         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4366         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4367
4368         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4369
4370         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4371             p = (int) board[0][i];
4372             if(p < (int) BlackPawn) piecesLeft[p] ++;
4373             board[0][i] = EmptySquare;
4374         }
4375
4376         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4377             // shuffles restricted to allow normal castling put KRR first
4378             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4379                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4380             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4381                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4382             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4383                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4384             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4385                 put(board, WhiteRook, 0, 0, ANY);
4386             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4387         }
4388
4389         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4390             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4391             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4392                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4393                 while(piecesLeft[p] >= 2) {
4394                     AddOnePiece(board, p, 0, LITE);
4395                     AddOnePiece(board, p, 0, DARK);
4396                 }
4397                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4398             }
4399
4400         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4401             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4402             // but we leave King and Rooks for last, to possibly obey FRC restriction
4403             if(p == (int)WhiteRook) continue;
4404             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4405             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4406         }
4407
4408         // now everything is placed, except perhaps King (Unicorn) and Rooks
4409
4410         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4411             // Last King gets castling rights
4412             while(piecesLeft[(int)WhiteUnicorn]) {
4413                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4414                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4415             }
4416
4417             while(piecesLeft[(int)WhiteKing]) {
4418                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4419                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4420             }
4421
4422
4423         } else {
4424             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4425             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4426         }
4427
4428         // Only Rooks can be left; simply place them all
4429         while(piecesLeft[(int)WhiteRook]) {
4430                 i = put(board, WhiteRook, 0, 0, ANY);
4431                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4432                         if(first) {
4433                                 first=0;
4434                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4435                         }
4436                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4437                 }
4438         }
4439         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4440             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4441         }
4442
4443         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4444 }
4445
4446 int SetCharTable( char *table, const char * map )
4447 /* [HGM] moved here from winboard.c because of its general usefulness */
4448 /*       Basically a safe strcpy that uses the last character as King */
4449 {
4450     int result = FALSE; int NrPieces;
4451
4452     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4453                     && NrPieces >= 12 && !(NrPieces&1)) {
4454         int i; /* [HGM] Accept even length from 12 to 34 */
4455
4456         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4457         for( i=0; i<NrPieces/2-1; i++ ) {
4458             table[i] = map[i];
4459             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4460         }
4461         table[(int) WhiteKing]  = map[NrPieces/2-1];
4462         table[(int) BlackKing]  = map[NrPieces-1];
4463
4464         result = TRUE;
4465     }
4466
4467     return result;
4468 }
4469
4470 void Prelude(Board board)
4471 {       // [HGM] superchess: random selection of exo-pieces
4472         int i, j, k; ChessSquare p;
4473         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4474
4475         GetPositionNumber(); // use FRC position number
4476
4477         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4478             SetCharTable(pieceToChar, appData.pieceToCharTable);
4479             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4480                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4481         }
4482
4483         j = seed%4;                 seed /= 4;
4484         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4485         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4486         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4487         j = seed%3 + (seed%3 >= j); seed /= 3;
4488         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4489         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4490         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4491         j = seed%3;                 seed /= 3;
4492         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4493         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4494         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4495         j = seed%2 + (seed%2 >= j); seed /= 2;
4496         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4497         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4498         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4499         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4500         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4501         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4502         put(board, exoPieces[0],    0, 0, ANY);
4503         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4504 }
4505
4506 void
4507 InitPosition(redraw)
4508      int redraw;
4509 {
4510     ChessSquare (* pieces)[BOARD_SIZE];
4511     int i, j, pawnRow, overrule,
4512     oldx = gameInfo.boardWidth,
4513     oldy = gameInfo.boardHeight,
4514     oldh = gameInfo.holdingsWidth,
4515     oldv = gameInfo.variant;
4516
4517     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4518
4519     /* [AS] Initialize pv info list [HGM] and game status */
4520     {
4521         for( i=0; i<MAX_MOVES; i++ ) {
4522             pvInfoList[i].depth = 0;
4523             epStatus[i]=EP_NONE;
4524             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4525         }
4526
4527         initialRulePlies = 0; /* 50-move counter start */
4528
4529         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4530         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4531     }
4532
4533
4534     /* [HGM] logic here is completely changed. In stead of full positions */
4535     /* the initialized data only consist of the two backranks. The switch */
4536     /* selects which one we will use, which is than copied to the Board   */
4537     /* initialPosition, which for the rest is initialized by Pawns and    */
4538     /* empty squares. This initial position is then copied to boards[0],  */
4539     /* possibly after shuffling, so that it remains available.            */
4540
4541     gameInfo.holdingsWidth = 0; /* default board sizes */
4542     gameInfo.boardWidth    = 8;
4543     gameInfo.boardHeight   = 8;
4544     gameInfo.holdingsSize  = 0;
4545     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4546     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4547     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4548
4549     switch (gameInfo.variant) {
4550     case VariantFischeRandom:
4551       shuffleOpenings = TRUE;
4552     default:
4553       pieces = FIDEArray;
4554       break;
4555     case VariantShatranj:
4556       pieces = ShatranjArray;
4557       nrCastlingRights = 0;
4558       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4559       break;
4560     case VariantTwoKings:
4561       pieces = twoKingsArray;
4562       break;
4563     case VariantCapaRandom:
4564       shuffleOpenings = TRUE;
4565     case VariantCapablanca:
4566       pieces = CapablancaArray;
4567       gameInfo.boardWidth = 10;
4568       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4569       break;
4570     case VariantGothic:
4571       pieces = GothicArray;
4572       gameInfo.boardWidth = 10;
4573       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4574       break;
4575     case VariantJanus:
4576       pieces = JanusArray;
4577       gameInfo.boardWidth = 10;
4578       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4579       nrCastlingRights = 6;
4580         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4581         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4582         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4583         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4584         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4585         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4586       break;
4587     case VariantFalcon:
4588       pieces = FalconArray;
4589       gameInfo.boardWidth = 10;
4590       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4591       break;
4592     case VariantXiangqi:
4593       pieces = XiangqiArray;
4594       gameInfo.boardWidth  = 9;
4595       gameInfo.boardHeight = 10;
4596       nrCastlingRights = 0;
4597       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4598       break;
4599     case VariantShogi:
4600       pieces = ShogiArray;
4601       gameInfo.boardWidth  = 9;
4602       gameInfo.boardHeight = 9;
4603       gameInfo.holdingsSize = 7;
4604       nrCastlingRights = 0;
4605       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4606       break;
4607     case VariantCourier:
4608       pieces = CourierArray;
4609       gameInfo.boardWidth  = 12;
4610       nrCastlingRights = 0;
4611       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4612       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4613       break;
4614     case VariantKnightmate:
4615       pieces = KnightmateArray;
4616       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4617       break;
4618     case VariantFairy:
4619       pieces = fairyArray;
4620       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4621       break;
4622     case VariantGreat:
4623       pieces = GreatArray;
4624       gameInfo.boardWidth = 10;
4625       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4626       gameInfo.holdingsSize = 8;
4627       break;
4628     case VariantSuper:
4629       pieces = FIDEArray;
4630       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4631       gameInfo.holdingsSize = 8;
4632       startedFromSetupPosition = TRUE;
4633       break;
4634     case VariantCrazyhouse:
4635     case VariantBughouse:
4636       pieces = FIDEArray;
4637       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4638       gameInfo.holdingsSize = 5;
4639       break;
4640     case VariantWildCastle:
4641       pieces = FIDEArray;
4642       /* !!?shuffle with kings guaranteed to be on d or e file */
4643       shuffleOpenings = 1;
4644       break;
4645     case VariantNoCastle:
4646       pieces = FIDEArray;
4647       nrCastlingRights = 0;
4648       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4649       /* !!?unconstrained back-rank shuffle */
4650       shuffleOpenings = 1;
4651       break;
4652     }
4653
4654     overrule = 0;
4655     if(appData.NrFiles >= 0) {
4656         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4657         gameInfo.boardWidth = appData.NrFiles;
4658     }
4659     if(appData.NrRanks >= 0) {
4660         gameInfo.boardHeight = appData.NrRanks;
4661     }
4662     if(appData.holdingsSize >= 0) {
4663         i = appData.holdingsSize;
4664         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4665         gameInfo.holdingsSize = i;
4666     }
4667     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4668     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4669         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4670
4671     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4672     if(pawnRow < 1) pawnRow = 1;
4673
4674     /* User pieceToChar list overrules defaults */
4675     if(appData.pieceToCharTable != NULL)
4676         SetCharTable(pieceToChar, appData.pieceToCharTable);
4677
4678     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4679
4680         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4681             s = (ChessSquare) 0; /* account holding counts in guard band */
4682         for( i=0; i<BOARD_HEIGHT; i++ )
4683             initialPosition[i][j] = s;
4684
4685         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4686         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4687         initialPosition[pawnRow][j] = WhitePawn;
4688         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4689         if(gameInfo.variant == VariantXiangqi) {
4690             if(j&1) {
4691                 initialPosition[pawnRow][j] =
4692                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4693                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4694                    initialPosition[2][j] = WhiteCannon;
4695                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4696                 }
4697             }
4698         }
4699         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4700     }
4701     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4702
4703             j=BOARD_LEFT+1;
4704             initialPosition[1][j] = WhiteBishop;
4705             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4706             j=BOARD_RGHT-2;
4707             initialPosition[1][j] = WhiteRook;
4708             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4709     }
4710
4711     if( nrCastlingRights == -1) {
4712         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4713         /*       This sets default castling rights from none to normal corners   */
4714         /* Variants with other castling rights must set them themselves above    */
4715         nrCastlingRights = 6;
4716
4717         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4718         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4719         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4720         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4721         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4722         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4723      }
4724
4725      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4726      if(gameInfo.variant == VariantGreat) { // promotion commoners
4727         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4728         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4729         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4730         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4731      }
4732   if (appData.debugMode) {
4733     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4734   }
4735     if(shuffleOpenings) {
4736         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4737         startedFromSetupPosition = TRUE;
4738     }
4739     if(startedFromPositionFile) {
4740       /* [HGM] loadPos: use PositionFile for every new game */
4741       CopyBoard(initialPosition, filePosition);
4742       for(i=0; i<nrCastlingRights; i++)
4743           castlingRights[0][i] = initialRights[i] = fileRights[i];
4744       startedFromSetupPosition = TRUE;
4745     }
4746
4747     CopyBoard(boards[0], initialPosition);
4748     if(oldx != gameInfo.boardWidth ||
4749        oldy != gameInfo.boardHeight ||
4750        oldh != gameInfo.holdingsWidth
4751 #ifdef GOTHIC
4752        || oldv == VariantGothic ||        // For licensing popups
4753        gameInfo.variant == VariantGothic
4754 #endif
4755 #ifdef FALCON
4756        || oldv == VariantFalcon ||
4757        gameInfo.variant == VariantFalcon
4758 #endif
4759                                          )
4760       {
4761             InitDrawingSizes(-2 ,0);
4762       }
4763
4764     if (redraw)
4765       DrawPosition(TRUE, boards[currentMove]);
4766
4767 }
4768
4769 void
4770 SendBoard(cps, moveNum)
4771      ChessProgramState *cps;
4772      int moveNum;
4773 {
4774     char message[MSG_SIZ];
4775
4776     if (cps->useSetboard) {
4777       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4778       sprintf(message, "setboard %s\n", fen);
4779       SendToProgram(message, cps);
4780       free(fen);
4781
4782     } else {
4783       ChessSquare *bp;
4784       int i, j;
4785       /* Kludge to set black to move, avoiding the troublesome and now
4786        * deprecated "black" command.
4787        */
4788       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4789
4790       SendToProgram("edit\n", cps);
4791       SendToProgram("#\n", cps);
4792       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4793         bp = &boards[moveNum][i][BOARD_LEFT];
4794         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4795           if ((int) *bp < (int) BlackPawn) {
4796             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4797                     AAA + j, ONE + i);
4798             if(message[0] == '+' || message[0] == '~') {
4799                 sprintf(message, "%c%c%c+\n",
4800                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4801                         AAA + j, ONE + i);
4802             }
4803             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4804                 message[1] = BOARD_RGHT   - 1 - j + '1';
4805                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4806             }
4807             SendToProgram(message, cps);
4808           }
4809         }
4810       }
4811
4812       SendToProgram("c\n", cps);
4813       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4814         bp = &boards[moveNum][i][BOARD_LEFT];
4815         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4816           if (((int) *bp != (int) EmptySquare)
4817               && ((int) *bp >= (int) BlackPawn)) {
4818             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4819                     AAA + j, ONE + i);
4820             if(message[0] == '+' || message[0] == '~') {
4821                 sprintf(message, "%c%c%c+\n",
4822                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4823                         AAA + j, ONE + i);
4824             }
4825             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4826                 message[1] = BOARD_RGHT   - 1 - j + '1';
4827                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4828             }
4829             SendToProgram(message, cps);
4830           }
4831         }
4832       }
4833
4834       SendToProgram(".\n", cps);
4835     }
4836     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4837 }
4838
4839 int
4840 IsPromotion(fromX, fromY, toX, toY)
4841      int fromX, fromY, toX, toY;
4842 {
4843     /* [HGM] add Shogi promotions */
4844     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4845     ChessSquare piece;
4846
4847     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4848       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4849    /* [HGM] Note to self: line above also weeds out drops */
4850     piece = boards[currentMove][fromY][fromX];
4851     if(gameInfo.variant == VariantShogi) {
4852         promotionZoneSize = 3;
4853         highestPromotingPiece = (int)WhiteKing;
4854         /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4855            and if in normal chess we then allow promotion to King, why not
4856            allow promotion of other piece in Shogi?                         */
4857     }
4858     if((int)piece >= BlackPawn) {
4859         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4860              return FALSE;
4861         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4862     } else {
4863         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4864            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4865     }
4866     return ( (int)piece <= highestPromotingPiece );
4867 }
4868
4869 int
4870 InPalace(row, column)
4871      int row, column;
4872 {   /* [HGM] for Xiangqi */
4873     if( (row < 3 || row > BOARD_HEIGHT-4) &&
4874          column < (BOARD_WIDTH + 4)/2 &&
4875          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4876     return FALSE;
4877 }
4878
4879 int
4880 PieceForSquare (x, y)
4881      int x;
4882      int y;
4883 {
4884   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4885      return -1;
4886   else
4887      return boards[currentMove][y][x];
4888 }
4889
4890 int
4891 OKToStartUserMove(x, y)
4892      int x, y;
4893 {
4894     ChessSquare from_piece;
4895     int white_piece;
4896
4897     if (matchMode) return FALSE;
4898     if (gameMode == EditPosition) return TRUE;
4899
4900     if (x >= 0 && y >= 0)
4901       from_piece = boards[currentMove][y][x];
4902     else
4903       from_piece = EmptySquare;
4904
4905     if (from_piece == EmptySquare) return FALSE;
4906
4907     white_piece = (int)from_piece >= (int)WhitePawn &&
4908       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4909
4910     switch (gameMode) {
4911       case PlayFromGameFile:
4912       case AnalyzeFile:
4913       case TwoMachinesPlay:
4914       case EndOfGame:
4915         return FALSE;
4916
4917       case IcsObserving:
4918       case IcsIdle:
4919         return FALSE;
4920
4921       case MachinePlaysWhite:
4922       case IcsPlayingBlack:
4923         if (appData.zippyPlay) return FALSE;
4924         if (white_piece) {
4925             DisplayMoveError(_("You are playing Black"));
4926             return FALSE;
4927         }
4928         break;
4929
4930       case MachinePlaysBlack:
4931       case IcsPlayingWhite:
4932         if (appData.zippyPlay) return FALSE;
4933         if (!white_piece) {
4934             DisplayMoveError(_("You are playing White"));
4935             return FALSE;
4936         }
4937         break;
4938
4939       case EditGame:
4940         if (!white_piece && WhiteOnMove(currentMove)) {
4941             DisplayMoveError(_("It is White's turn"));
4942             return FALSE;
4943         }
4944         if (white_piece && !WhiteOnMove(currentMove)) {
4945             DisplayMoveError(_("It is Black's turn"));
4946             return FALSE;
4947         }
4948         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4949             /* Editing correspondence game history */
4950             /* Could disallow this or prompt for confirmation */
4951             cmailOldMove = -1;
4952         }
4953         if (currentMove < forwardMostMove) {
4954             /* Discarding moves */
4955             /* Could prompt for confirmation here,
4956                but I don't think that's such a good idea */
4957             forwardMostMove = currentMove;
4958         }
4959         break;
4960
4961       case BeginningOfGame:
4962         if (appData.icsActive) return FALSE;
4963         if (!appData.noChessProgram) {
4964             if (!white_piece) {
4965                 DisplayMoveError(_("You are playing White"));
4966                 return FALSE;
4967             }
4968         }
4969         break;
4970
4971       case Training:
4972         if (!white_piece && WhiteOnMove(currentMove)) {
4973             DisplayMoveError(_("It is White's turn"));
4974             return FALSE;
4975         }
4976         if (white_piece && !WhiteOnMove(currentMove)) {
4977             DisplayMoveError(_("It is Black's turn"));
4978             return FALSE;
4979         }
4980         break;
4981
4982       default:
4983       case IcsExamining:
4984         break;
4985     }
4986     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4987         && gameMode != AnalyzeFile && gameMode != Training) {
4988         DisplayMoveError(_("Displayed position is not current"));
4989         return FALSE;
4990     }
4991     return TRUE;
4992 }
4993
4994 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4995 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4996 int lastLoadGameUseList = FALSE;
4997 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4998 ChessMove lastLoadGameStart = (ChessMove) 0;
4999
5000 ChessMove
5001 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5002      int fromX, fromY, toX, toY;
5003      int promoChar;
5004      Boolean captureOwn;
5005 {
5006     ChessMove moveType;
5007     ChessSquare pdown, pup;
5008
5009     if (fromX < 0 || fromY < 0) return ImpossibleMove;
5010
5011     /* [HGM] suppress all moves into holdings area and guard band */
5012     if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5013             return ImpossibleMove;
5014
5015     /* [HGM] <sameColor> moved to here from winboard.c */
5016     /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5017     pdown = boards[currentMove][fromY][fromX];
5018     pup = boards[currentMove][toY][toX];
5019     if (    gameMode != EditPosition && !captureOwn &&
5020             (WhitePawn <= pdown && pdown < BlackPawn &&
5021              WhitePawn <= pup && pup < BlackPawn  ||
5022              BlackPawn <= pdown && pdown < EmptySquare &&
5023              BlackPawn <= pup && pup < EmptySquare
5024             ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5025                     (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5026                      pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5027                      pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5028                      pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1  ) 
5029         )           )
5030          return Comment;
5031
5032     /* Check if the user is playing in turn.  This is complicated because we
5033        let the user "pick up" a piece before it is his turn.  So the piece he
5034        tried to pick up may have been captured by the time he puts it down!
5035        Therefore we use the color the user is supposed to be playing in this
5036        test, not the color of the piece that is currently on the starting
5037        square---except in EditGame mode, where the user is playing both
5038        sides; fortunately there the capture race can't happen.  (It can
5039        now happen in IcsExamining mode, but that's just too bad.  The user
5040        will get a somewhat confusing message in that case.)
5041        */
5042
5043     switch (gameMode) {
5044       case PlayFromGameFile:
5045       case AnalyzeFile:
5046       case TwoMachinesPlay:
5047       case EndOfGame:
5048       case IcsObserving:
5049       case IcsIdle:
5050         /* We switched into a game mode where moves are not accepted,
5051            perhaps while the mouse button was down. */
5052         return ImpossibleMove;
5053
5054       case MachinePlaysWhite:
5055         /* User is moving for Black */
5056         if (WhiteOnMove(currentMove)) {
5057             DisplayMoveError(_("It is White's turn"));
5058             return ImpossibleMove;
5059         }
5060         break;
5061
5062       case MachinePlaysBlack:
5063         /* User is moving for White */
5064         if (!WhiteOnMove(currentMove)) {
5065             DisplayMoveError(_("It is Black's turn"));
5066             return ImpossibleMove;
5067         }
5068         break;
5069
5070       case EditGame:
5071       case IcsExamining:
5072       case BeginningOfGame:
5073       case AnalyzeMode:
5074       case Training:
5075         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5076             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5077             /* User is moving for Black */
5078             if (WhiteOnMove(currentMove)) {
5079                 DisplayMoveError(_("It is White's turn"));
5080                 return ImpossibleMove;
5081             }
5082         } else {
5083             /* User is moving for White */
5084             if (!WhiteOnMove(currentMove)) {
5085                 DisplayMoveError(_("It is Black's turn"));
5086                 return ImpossibleMove;
5087             }
5088         }
5089         break;
5090
5091       case IcsPlayingBlack:
5092         /* User is moving for Black */
5093         if (WhiteOnMove(currentMove)) {
5094             if (!appData.premove) {
5095                 DisplayMoveError(_("It is White's turn"));
5096             } else if (toX >= 0 && toY >= 0) {
5097                 premoveToX = toX;
5098                 premoveToY = toY;
5099                 premoveFromX = fromX;
5100                 premoveFromY = fromY;
5101                 premovePromoChar = promoChar;
5102                 gotPremove = 1;
5103                 if (appData.debugMode)
5104                     fprintf(debugFP, "Got premove: fromX %d,"
5105                             "fromY %d, toX %d, toY %d\n",
5106                             fromX, fromY, toX, toY);
5107             }
5108             return ImpossibleMove;
5109         }
5110         break;
5111
5112       case IcsPlayingWhite:
5113         /* User is moving for White */
5114         if (!WhiteOnMove(currentMove)) {
5115             if (!appData.premove) {
5116                 DisplayMoveError(_("It is Black's turn"));
5117             } else if (toX >= 0 && toY >= 0) {
5118                 premoveToX = toX;
5119                 premoveToY = toY;
5120                 premoveFromX = fromX;
5121                 premoveFromY = fromY;
5122                 premovePromoChar = promoChar;
5123                 gotPremove = 1;
5124                 if (appData.debugMode)
5125                     fprintf(debugFP, "Got premove: fromX %d,"
5126                             "fromY %d, toX %d, toY %d\n",
5127                             fromX, fromY, toX, toY);
5128             }
5129             return ImpossibleMove;
5130         }
5131         break;
5132
5133       default:
5134         break;
5135
5136       case EditPosition:
5137         /* EditPosition, empty square, or different color piece;
5138            click-click move is possible */
5139         if (toX == -2 || toY == -2) {
5140             boards[0][fromY][fromX] = EmptySquare;
5141             return AmbiguousMove;
5142         } else if (toX >= 0 && toY >= 0) {
5143             boards[0][toY][toX] = boards[0][fromY][fromX];
5144             boards[0][fromY][fromX] = EmptySquare;
5145             return AmbiguousMove;
5146         }
5147         return ImpossibleMove;
5148     }
5149
5150     /* [HGM] If move started in holdings, it means a drop */
5151     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5152          if( pup != EmptySquare ) return ImpossibleMove;
5153          if(appData.testLegality) {
5154              /* it would be more logical if LegalityTest() also figured out
5155               * which drops are legal. For now we forbid pawns on back rank.
5156               * Shogi is on its own here...
5157               */
5158              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5159                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5160                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5161          }
5162          return WhiteDrop; /* Not needed to specify white or black yet */
5163     }
5164
5165     userOfferedDraw = FALSE;
5166
5167     /* [HGM] always test for legality, to get promotion info */
5168     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5169                           epStatus[currentMove], castlingRights[currentMove],
5170                                          fromY, fromX, toY, toX, promoChar);
5171     /* [HGM] but possibly ignore an IllegalMove result */
5172     if (appData.testLegality) {
5173         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5174             DisplayMoveError(_("Illegal move"));
5175             return ImpossibleMove;
5176         }
5177     }
5178     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5179     return moveType;
5180     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5181        function is made into one that returns an OK move type if FinishMove
5182        should be called. This to give the calling driver routine the
5183        opportunity to finish the userMove input with a promotion popup,
5184        without bothering the user with this for invalid or illegal moves */
5185
5186 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5187 }
5188
5189 /* Common tail of UserMoveEvent and DropMenuEvent */
5190 int
5191 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5192      ChessMove moveType;
5193      int fromX, fromY, toX, toY;
5194      /*char*/int promoChar;
5195 {
5196   char *bookHit = 0;
5197
5198   if(appData.debugMode)
5199     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5200
5201   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5202     {
5203       // [HGM] superchess: suppress promotions to non-available piece
5204       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5205       if(WhiteOnMove(currentMove))
5206         {
5207           if(!boards[currentMove][k][BOARD_WIDTH-2])
5208             return 0;
5209         }
5210       else
5211         {
5212           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5213             return 0;
5214         }
5215     }
5216   
5217   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5218      move type in caller when we know the move is a legal promotion */
5219   if(moveType == NormalMove && promoChar)
5220     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5221
5222   if(appData.debugMode) 
5223     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5224
5225   /* [HGM] convert drag-and-drop piece drops to standard form */
5226   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5227     {
5228       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5229       if(appData.debugMode) 
5230         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5231                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5232       //         fromX = boards[currentMove][fromY][fromX];
5233       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5234       if(fromX == 0) 
5235         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5236
5237       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5238
5239       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5240         fromX++; 
5241
5242       fromY = DROP_RANK;
5243     }
5244
5245   /* [HGM] <popupFix> The following if has been moved here from
5246      UserMoveEvent(). Because it seemed to belon here (why not allow
5247      piece drops in training games?), and because it can only be
5248      performed after it is known to what we promote. */
5249   if (gameMode == Training)
5250     {
5251       /* compare the move played on the board to the next move in the
5252        * game. If they match, display the move and the opponent's response.
5253        * If they don't match, display an error message.
5254        */
5255       int saveAnimate;
5256       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5257       CopyBoard(testBoard, boards[currentMove]);
5258       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5259
5260       if (CompareBoards(testBoard, boards[currentMove+1]))
5261         {
5262           ForwardInner(currentMove+1);
5263
5264           /* Autoplay the opponent's response.
5265            * if appData.animate was TRUE when Training mode was entered,
5266            * the response will be animated.
5267            */
5268           saveAnimate = appData.animate;
5269           appData.animate = animateTraining;
5270           ForwardInner(currentMove+1);
5271           appData.animate = saveAnimate;
5272
5273           /* check for the end of the game */
5274           if (currentMove >= forwardMostMove)
5275             {
5276               gameMode = PlayFromGameFile;
5277               ModeHighlight();
5278               SetTrainingModeOff();
5279               DisplayInformation(_("End of game"));
5280             }
5281         }
5282       else
5283         {
5284           DisplayError(_("Incorrect move"), 0);
5285         }
5286       return 1;
5287     }
5288
5289   /* Ok, now we know that the move is good, so we can kill
5290      the previous line in Analysis Mode */
5291   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5292     {
5293       forwardMostMove = currentMove;
5294     }
5295
5296   /* If we need the chess program but it's dead, restart it */
5297   ResurrectChessProgram();
5298
5299   /* A user move restarts a paused game*/
5300   if (pausing)
5301     PauseEvent();
5302
5303   thinkOutput[0] = NULLCHAR;
5304
5305   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5306
5307   if (gameMode == BeginningOfGame)
5308     {
5309       if (appData.noChessProgram)
5310         {
5311           gameMode = EditGame;
5312           SetGameInfo();
5313         }
5314       else
5315         {
5316           char buf[MSG_SIZ];
5317           gameMode = MachinePlaysBlack;
5318           StartClocks();
5319           SetGameInfo();
5320           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5321           DisplayTitle(buf);
5322           if (first.sendName)
5323             {
5324               sprintf(buf, "name %s\n", gameInfo.white);
5325               SendToProgram(buf, &first);
5326             }
5327           StartClocks();
5328         }
5329       ModeHighlight();
5330     }
5331   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5332
5333   /* Relay move to ICS or chess engine */
5334   if (appData.icsActive)
5335     {
5336       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5337           gameMode == IcsExamining)
5338         {
5339           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5340           ics_user_moved = 1;
5341         }
5342     }
5343   else
5344     {
5345       if (first.sendTime && (gameMode == BeginningOfGame ||
5346                              gameMode == MachinePlaysWhite ||
5347                              gameMode == MachinePlaysBlack))
5348         {
5349           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5350         }
5351       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5352         {
5353           // [HGM] book: if program might be playing, let it use book
5354           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5355           first.maybeThinking = TRUE;
5356         }
5357       else
5358         SendMoveToProgram(forwardMostMove-1, &first);
5359       if (currentMove == cmailOldMove + 1)
5360         {
5361           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5362         }
5363     }
5364
5365   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5366
5367   switch (gameMode)
5368     {
5369     case EditGame:
5370       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5371                        EP_UNKNOWN, castlingRights[currentMove]) )
5372         {
5373         case MT_NONE:
5374         case MT_CHECK:
5375           break;
5376         case MT_CHECKMATE:
5377         case MT_STAINMATE:
5378           if (WhiteOnMove(currentMove))
5379             {
5380               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5381             }
5382           else
5383             {
5384               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5385             }
5386           break;
5387         case MT_STALEMATE:
5388           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5389           break;
5390     }
5391       break;
5392
5393     case MachinePlaysBlack:
5394     case MachinePlaysWhite:
5395       /* disable certain menu options while machine is thinking */
5396       SetMachineThinkingEnables();
5397       break;
5398
5399     default:
5400       break;
5401     }
5402
5403   if(bookHit)
5404     { // [HGM] book: simulate book reply
5405       static char bookMove[MSG_SIZ]; // a bit generous?
5406
5407       programStats.nodes = programStats.depth = programStats.time =
5408         programStats.score = programStats.got_only_move = 0;
5409       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5410
5411       strcpy(bookMove, "move ");
5412       strcat(bookMove, bookHit);
5413       HandleMachineMove(bookMove, &first);
5414     }
5415
5416   return 1;
5417 }
5418
5419 void
5420 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5421      int fromX, fromY, toX, toY;
5422      int promoChar;
5423 {
5424     /* [HGM] This routine was added to allow calling of its two logical
5425        parts from other modules in the old way. Before, UserMoveEvent()
5426        automatically called FinishMove() if the move was OK, and returned
5427        otherwise. I separated the two, in order to make it possible to
5428        slip a promotion popup in between. But that it always needs two
5429        calls, to the first part, (now called UserMoveTest() ), and to
5430        FinishMove if the first part succeeded. Calls that do not need
5431        to do anything in between, can call this routine the old way.
5432     */
5433     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5434 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5435     if(moveType == AmbiguousMove)
5436         DrawPosition(FALSE, boards[currentMove]);
5437     else if(moveType != ImpossibleMove && moveType != Comment)
5438         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5439 }
5440
5441 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5442 {
5443 //    char * hint = lastHint;
5444     FrontEndProgramStats stats;
5445
5446     stats.which = cps == &first ? 0 : 1;
5447     stats.depth = cpstats->depth;
5448     stats.nodes = cpstats->nodes;
5449     stats.score = cpstats->score;
5450     stats.time = cpstats->time;
5451     stats.pv = cpstats->movelist;
5452     stats.hint = lastHint;
5453     stats.an_move_index = 0;
5454     stats.an_move_count = 0;
5455
5456     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5457         stats.hint = cpstats->move_name;
5458         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5459         stats.an_move_count = cpstats->nr_moves;
5460     }
5461
5462     SetProgramStats( &stats );
5463 }
5464
5465 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5466 {   // [HGM] book: this routine intercepts moves to simulate book replies
5467     char *bookHit = NULL;
5468
5469     //first determine if the incoming move brings opponent into his book
5470     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5471         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5472     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5473     if(bookHit != NULL && !cps->bookSuspend) {
5474         // make sure opponent is not going to reply after receiving move to book position
5475         SendToProgram("force\n", cps);
5476         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5477     }
5478     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5479     // now arrange restart after book miss
5480     if(bookHit) {
5481         // after a book hit we never send 'go', and the code after the call to this routine
5482         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5483         char buf[MSG_SIZ];
5484         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5485         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5486         SendToProgram(buf, cps);
5487         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5488     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5489         SendToProgram("go\n", cps);
5490         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5491     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5492         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5493             SendToProgram("go\n", cps);
5494         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5495     }
5496     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5497 }
5498
5499 char *savedMessage;
5500 ChessProgramState *savedState;
5501 void DeferredBookMove(void)
5502 {
5503         if(savedState->lastPing != savedState->lastPong)
5504                     ScheduleDelayedEvent(DeferredBookMove, 10);
5505         else
5506         HandleMachineMove(savedMessage, savedState);
5507 }
5508
5509 void
5510 HandleMachineMove(message, cps)
5511      char *message;
5512      ChessProgramState *cps;
5513 {
5514     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5515     char realname[MSG_SIZ];
5516     int fromX, fromY, toX, toY;
5517     ChessMove moveType;
5518     char promoChar;
5519     char *p;
5520     int machineWhite;
5521     char *bookHit;
5522
5523 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5524     /*
5525      * Kludge to ignore BEL characters
5526      */
5527     while (*message == '\007') message++;
5528
5529     /*
5530      * [HGM] engine debug message: ignore lines starting with '#' character
5531      */
5532     if(cps->debug && *message == '#') return;
5533
5534     /*
5535      * Look for book output
5536      */
5537     if (cps == &first && bookRequested) {
5538         if (message[0] == '\t' || message[0] == ' ') {
5539             /* Part of the book output is here; append it */
5540             strcat(bookOutput, message);
5541             strcat(bookOutput, "  \n");
5542             return;
5543         } else if (bookOutput[0] != NULLCHAR) {
5544             /* All of book output has arrived; display it */
5545             char *p = bookOutput;
5546             while (*p != NULLCHAR) {
5547                 if (*p == '\t') *p = ' ';
5548                 p++;
5549             }
5550             DisplayInformation(bookOutput);
5551             bookRequested = FALSE;
5552             /* Fall through to parse the current output */
5553         }
5554     }
5555
5556     /*
5557      * Look for machine move.
5558      */
5559     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5560         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5561     {
5562         /* This method is only useful on engines that support ping */
5563         if (cps->lastPing != cps->lastPong) {
5564           if (gameMode == BeginningOfGame) {
5565             /* Extra move from before last new; ignore */
5566             if (appData.debugMode) {
5567                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5568             }
5569           } else {
5570             if (appData.debugMode) {
5571                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5572                         cps->which, gameMode);
5573             }
5574
5575             SendToProgram("undo\n", cps);
5576           }
5577           return;
5578         }
5579
5580         switch (gameMode) {
5581           case BeginningOfGame:
5582             /* Extra move from before last reset; ignore */
5583             if (appData.debugMode) {
5584                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5585             }
5586             return;
5587
5588           case EndOfGame:
5589           case IcsIdle:
5590           default:
5591             /* Extra move after we tried to stop.  The mode test is
5592                not a reliable way of detecting this problem, but it's
5593                the best we can do on engines that don't support ping.
5594             */
5595             if (appData.debugMode) {
5596                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5597                         cps->which, gameMode);
5598             }
5599             SendToProgram("undo\n", cps);
5600             return;
5601
5602           case MachinePlaysWhite:
5603           case IcsPlayingWhite:
5604             machineWhite = TRUE;
5605             break;
5606
5607           case MachinePlaysBlack:
5608           case IcsPlayingBlack:
5609             machineWhite = FALSE;
5610             break;
5611
5612           case TwoMachinesPlay:
5613             machineWhite = (cps->twoMachinesColor[0] == 'w');
5614             break;
5615         }
5616         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5617             if (appData.debugMode) {
5618                 fprintf(debugFP,
5619                         "Ignoring move out of turn by %s, gameMode %d"
5620                         ", forwardMost %d\n",
5621                         cps->which, gameMode, forwardMostMove);
5622             }
5623             return;
5624         }
5625
5626     if (appData.debugMode) { int f = forwardMostMove;
5627         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5628                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5629     }
5630         if(cps->alphaRank) AlphaRank(machineMove, 4);
5631         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5632                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5633             /* Machine move could not be parsed; ignore it. */
5634             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5635                     machineMove, cps->which);
5636             DisplayError(buf1, 0);
5637             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5638                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5639             if (gameMode == TwoMachinesPlay) {
5640               GameEnds(machineWhite ? BlackWins : WhiteWins,
5641                        buf1, GE_XBOARD);
5642             }
5643             return;
5644         }
5645
5646         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5647         /* So we have to redo legality test with true e.p. status here,  */
5648         /* to make sure an illegal e.p. capture does not slip through,   */
5649         /* to cause a forfeit on a justified illegal-move complaint      */
5650         /* of the opponent.                                              */
5651         if( gameMode==TwoMachinesPlay && appData.testLegality
5652             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5653                                                               ) {
5654            ChessMove moveType;
5655            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5656                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5657                              fromY, fromX, toY, toX, promoChar);
5658             if (appData.debugMode) {
5659                 int i;
5660                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5661                     castlingRights[forwardMostMove][i], castlingRank[i]);
5662                 fprintf(debugFP, "castling rights\n");
5663             }
5664             if(moveType == IllegalMove) {
5665                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5666                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5667                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5668                            buf1, GE_XBOARD);
5669                 return;
5670            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5671            /* [HGM] Kludge to handle engines that send FRC-style castling
5672               when they shouldn't (like TSCP-Gothic) */
5673            switch(moveType) {
5674              case WhiteASideCastleFR:
5675              case BlackASideCastleFR:
5676                toX+=2;
5677                currentMoveString[2]++;
5678                break;
5679              case WhiteHSideCastleFR:
5680              case BlackHSideCastleFR:
5681                toX--;
5682                currentMoveString[2]--;
5683                break;
5684              default: ; // nothing to do, but suppresses warning of pedantic compilers
5685            }
5686         }
5687         hintRequested = FALSE;
5688         lastHint[0] = NULLCHAR;
5689         bookRequested = FALSE;
5690         /* Program may be pondering now */
5691         cps->maybeThinking = TRUE;
5692         if (cps->sendTime == 2) cps->sendTime = 1;
5693         if (cps->offeredDraw) cps->offeredDraw--;
5694
5695 #if ZIPPY
5696         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5697             first.initDone) {
5698           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5699           ics_user_moved = 1;
5700           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5701                 char buf[3*MSG_SIZ];
5702
5703                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5704                         programStats.score / 100.,
5705                         programStats.depth,
5706                         programStats.time / 100.,
5707                         (unsigned int)programStats.nodes,
5708                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5709                         programStats.movelist);
5710                 SendToICS(buf);
5711 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5712           }
5713         }
5714 #endif
5715         /* currentMoveString is set as a side-effect of ParseOneMove */
5716         strcpy(machineMove, currentMoveString);
5717         strcat(machineMove, "\n");
5718         strcpy(moveList[forwardMostMove], machineMove);
5719
5720         /* [AS] Save move info and clear stats for next move */
5721         pvInfoList[ forwardMostMove ].score = programStats.score;
5722         pvInfoList[ forwardMostMove ].depth = programStats.depth;
5723         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
5724         ClearProgramStats();
5725         thinkOutput[0] = NULLCHAR;
5726         hiddenThinkOutputState = 0;
5727
5728         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5729
5730         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5731         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5732             int count = 0;
5733
5734             while( count < adjudicateLossPlies ) {
5735                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5736
5737                 if( count & 1 ) {
5738                     score = -score; /* Flip score for winning side */
5739                 }
5740
5741                 if( score > adjudicateLossThreshold ) {
5742                     break;
5743                 }
5744
5745                 count++;
5746             }
5747
5748             if( count >= adjudicateLossPlies ) {
5749                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5750
5751                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5752                     "Xboard adjudication",
5753                     GE_XBOARD );
5754
5755                 return;
5756             }
5757         }
5758
5759         if( gameMode == TwoMachinesPlay ) {
5760           // [HGM] some adjudications useful with buggy engines
5761             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5762           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5763
5764
5765             if( appData.testLegality )
5766             {   /* [HGM] Some more adjudications for obstinate engines */
5767                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5768                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5769                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5770                 static int moveCount = 6;
5771                 ChessMove result;
5772                 char *reason = NULL;
5773
5774                 /* Count what is on board. */
5775                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5776                 {   ChessSquare p = boards[forwardMostMove][i][j];
5777                     int m=i;
5778
5779                     switch((int) p)
5780                     {   /* count B,N,R and other of each side */
5781                         case WhiteKing:
5782                         case BlackKing:
5783                              NrK++; break; // [HGM] atomic: count Kings
5784                         case WhiteKnight:
5785                              NrWN++; break;
5786                         case WhiteBishop:
5787                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5788                              bishopsColor |= 1 << ((i^j)&1);
5789                              NrWB++; break;
5790                         case BlackKnight:
5791                              NrBN++; break;
5792                         case BlackBishop:
5793                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
5794                              bishopsColor |= 1 << ((i^j)&1);
5795                              NrBB++; break;
5796                         case WhiteRook:
5797                              NrWR++; break;
5798                         case BlackRook:
5799                              NrBR++; break;
5800                         case WhiteQueen:
5801                              NrWQ++; break;
5802                         case BlackQueen:
5803                              NrBQ++; break;
5804                         case EmptySquare:
5805                              break;
5806                         case BlackPawn:
5807                              m = 7-i;
5808                         case WhitePawn:
5809                              PawnAdvance += m; NrPawns++;
5810                     }
5811                     NrPieces += (p != EmptySquare);
5812                     NrW += ((int)p < (int)BlackPawn);
5813                     if(gameInfo.variant == VariantXiangqi &&
5814                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5815                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5816                         NrW -= ((int)p < (int)BlackPawn);
5817                     }
5818                 }
5819
5820                 /* Some material-based adjudications that have to be made before stalemate test */
5821                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5822                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5823                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5824                      if(appData.checkMates) {
5825                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5826                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5827                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5828                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
5829                          return;
5830                      }
5831                 }
5832
5833                 /* Bare King in Shatranj (loses) or Losers (wins) */
5834                 if( NrW == 1 || NrPieces - NrW == 1) {
5835                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5836                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
5837                      if(appData.checkMates) {
5838                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5839                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5840                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5841                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5842                          return;
5843                      }
5844                   } else
5845                   if( gameInfo.variant == VariantShatranj && --bare < 0)
5846                   {    /* bare King */
5847                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5848                         if(appData.checkMates) {
5849                             /* but only adjudicate if adjudication enabled */
5850                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5851                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5852                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5853                                                         "Xboard adjudication: Bare king", GE_XBOARD );
5854                             return;
5855                         }
5856                   }
5857                 } else bare = 1;
5858
5859
5860             // don't wait for engine to announce game end if we can judge ourselves
5861             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5862                                        castlingRights[forwardMostMove]) ) {
5863               case MT_CHECK:
5864                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5865                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
5866                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5867                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5868                             checkCnt++;
5869                         if(checkCnt >= 2) {
5870                             reason = "Xboard adjudication: 3rd check";
5871                             epStatus[forwardMostMove] = EP_CHECKMATE;
5872                             break;
5873                         }
5874                     }
5875                 }
5876               case MT_NONE:
5877               default:
5878                 break;
5879               case MT_STALEMATE:
5880               case MT_STAINMATE:
5881                 reason = "Xboard adjudication: Stalemate";
5882                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5883                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
5884                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5885                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
5886                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5887                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5888                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5889                                                                         EP_CHECKMATE : EP_WINS);
5890                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5891                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5892                 }
5893                 break;
5894               case MT_CHECKMATE:
5895                 reason = "Xboard adjudication: Checkmate";
5896                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5897                 break;
5898             }
5899
5900                 switch(i = epStatus[forwardMostMove]) {
5901                     case EP_STALEMATE:
5902                         result = GameIsDrawn; break;
5903                     case EP_CHECKMATE:
5904                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5905                     case EP_WINS:
5906                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5907                     default:
5908                         result = (ChessMove) 0;
5909                 }
5910                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5911                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5912                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5913                     GameEnds( result, reason, GE_XBOARD );
5914                     return;
5915                 }
5916
5917                 /* Next absolutely insufficient mating material. */
5918                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5919                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5920                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5921                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5922                 {    /* KBK, KNK, KK of KBKB with like Bishops */
5923
5924                      /* always flag draws, for judging claims */
5925                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
5926
5927                      if(appData.materialDraws) {
5928                          /* but only adjudicate them if adjudication enabled */
5929                          SendToProgram("force\n", cps->other); // suppress reply
5930                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5931                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5932                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5933                          return;
5934                      }
5935                 }
5936
5937                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5938                 if(NrPieces == 4 &&
5939                    (   NrWR == 1 && NrBR == 1 /* KRKR */
5940                    || NrWQ==1 && NrBQ==1     /* KQKQ */
5941                    || NrWN==2 || NrBN==2     /* KNNK */
5942                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5943                   ) ) {
5944                      if(--moveCount < 0 && appData.trivialDraws)
5945                      {    /* if the first 3 moves do not show a tactical win, declare draw */
5946                           SendToProgram("force\n", cps->other); // suppress reply
5947                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5948                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5949                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5950                           return;
5951                      }
5952                 } else moveCount = 6;
5953             }
5954           }
5955           
5956           if (appData.debugMode) { int i;
5957             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5958                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5959                     appData.drawRepeats);
5960             for( i=forwardMostMove; i>=backwardMostMove; i-- )
5961               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5962             
5963           }
5964
5965                 /* Check for rep-draws */
5966                 count = 0;
5967                 for(k = forwardMostMove-2;
5968                     k>=backwardMostMove && k>=forwardMostMove-100 &&
5969                         epStatus[k] < EP_UNKNOWN &&
5970                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5971                     k-=2)
5972                 {   int rights=0;
5973                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
5974                         /* compare castling rights */
5975                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5976                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5977                                 rights++; /* King lost rights, while rook still had them */
5978                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5979                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5980                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5981                                    rights++; /* but at least one rook lost them */
5982                         }
5983                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5984                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5985                                 rights++;
5986                         if( castlingRights[forwardMostMove][5] >= 0 ) {
5987                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5988                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5989                                    rights++;
5990                         }
5991                         if( rights == 0 && ++count > appData.drawRepeats-2
5992                             && appData.drawRepeats > 1) {
5993                              /* adjudicate after user-specified nr of repeats */
5994                              SendToProgram("force\n", cps->other); // suppress reply
5995                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5996                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5997                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5998                                 // [HGM] xiangqi: check for forbidden perpetuals
5999                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6000                                 for(m=forwardMostMove; m>k; m-=2) {
6001                                     if(MateTest(boards[m], PosFlags(m),
6002                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6003                                         ourPerpetual = 0; // the current mover did not always check
6004                                     if(MateTest(boards[m-1], PosFlags(m-1),
6005                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6006                                         hisPerpetual = 0; // the opponent did not always check
6007                                 }
6008                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6009                                                                         ourPerpetual, hisPerpetual);
6010                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6011                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6012                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6013                                     return;
6014                                 }
6015                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6016                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6017                                 // Now check for perpetual chases
6018                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6019                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6020                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6021                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6022                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6023                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6024                                         return;
6025                                     }
6026                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6027                                         break; // Abort repetition-checking loop.
6028                                 }
6029                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6030                              }
6031                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6032                              return;
6033                         }
6034                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6035                              epStatus[forwardMostMove] = EP_REP_DRAW;
6036                     }
6037                 }
6038
6039                 /* Now we test for 50-move draws. Determine ply count */
6040                 count = forwardMostMove;
6041                 /* look for last irreversble move */
6042                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6043                     count--;
6044                 /* if we hit starting position, add initial plies */
6045                 if( count == backwardMostMove )
6046                     count -= initialRulePlies;
6047                 count = forwardMostMove - count;
6048                 if( count >= 100)
6049                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6050                          /* this is used to judge if draw claims are legal */
6051                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6052                          SendToProgram("force\n", cps->other); // suppress reply
6053                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6054                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6056                          return;
6057                 }
6058
6059                 /* if draw offer is pending, treat it as a draw claim
6060                  * when draw condition present, to allow engines a way to
6061                  * claim draws before making their move to avoid a race
6062                  * condition occurring after their move
6063                  */
6064                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6065                          char *p = NULL;
6066                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6067                              p = "Draw claim: 50-move rule";
6068                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6069                              p = "Draw claim: 3-fold repetition";
6070                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6071                              p = "Draw claim: insufficient mating material";
6072                          if( p != NULL ) {
6073                              SendToProgram("force\n", cps->other); // suppress reply
6074                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6075                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6076                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6077                              return;
6078                          }
6079                 }
6080
6081
6082                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6083                     SendToProgram("force\n", cps->other); // suppress reply
6084                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6085                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6086
6087                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6088
6089                     return;
6090                 }
6091         }
6092
6093         bookHit = NULL;
6094         if (gameMode == TwoMachinesPlay) {
6095             /* [HGM] relaying draw offers moved to after reception of move */
6096             /* and interpreting offer as claim if it brings draw condition */
6097             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6098                 SendToProgram("draw\n", cps->other);
6099             }
6100             if (cps->other->sendTime) {
6101                 SendTimeRemaining(cps->other,
6102                                   cps->other->twoMachinesColor[0] == 'w');
6103             }
6104             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6105             if (firstMove && !bookHit) {
6106                 firstMove = FALSE;
6107                 if (cps->other->useColors) {
6108                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6109                 }
6110                 SendToProgram("go\n", cps->other);
6111             }
6112             cps->other->maybeThinking = TRUE;
6113         }
6114
6115         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116
6117         if (!pausing && appData.ringBellAfterMoves) {
6118             RingBell();
6119         }
6120
6121         /*
6122          * Reenable menu items that were disabled while
6123          * machine was thinking
6124          */
6125         if (gameMode != TwoMachinesPlay)
6126             SetUserThinkingEnables();
6127
6128         // [HGM] book: after book hit opponent has received move and is now in force mode
6129         // force the book reply into it, and then fake that it outputted this move by jumping
6130         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6131         if(bookHit) {
6132                 static char bookMove[MSG_SIZ]; // a bit generous?
6133
6134                 strcpy(bookMove, "move ");
6135                 strcat(bookMove, bookHit);
6136                 message = bookMove;
6137                 cps = cps->other;
6138                 programStats.nodes = programStats.depth = programStats.time =
6139                 programStats.score = programStats.got_only_move = 0;
6140                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6141
6142                 if(cps->lastPing != cps->lastPong) {
6143                     savedMessage = message; // args for deferred call
6144                     savedState = cps;
6145                     ScheduleDelayedEvent(DeferredBookMove, 10);
6146                     return;
6147                 }
6148                 goto FakeBookMove;
6149         }
6150
6151         return;
6152     }
6153
6154     /* Set special modes for chess engines.  Later something general
6155      *  could be added here; for now there is just one kludge feature,
6156      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6157      *  when "xboard" is given as an interactive command.
6158      */
6159     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6160         cps->useSigint = FALSE;
6161         cps->useSigterm = FALSE;
6162     }
6163     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6164       ParseFeatures(message+8, cps);
6165       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6166     }
6167
6168     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6169      * want this, I was asked to put it in, and obliged.
6170      */
6171     if (!strncmp(message, "setboard ", 9)) {
6172         Board initial_position; int i;
6173
6174         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6175
6176         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6177             DisplayError(_("Bad FEN received from engine"), 0);
6178             return ;
6179         } else {
6180            Reset(FALSE, FALSE);
6181            CopyBoard(boards[0], initial_position);
6182            initialRulePlies = FENrulePlies;
6183            epStatus[0] = FENepStatus;
6184            for( i=0; i<nrCastlingRights; i++ )
6185                 castlingRights[0][i] = FENcastlingRights[i];
6186            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6187            else gameMode = MachinePlaysBlack;
6188            DrawPosition(FALSE, boards[currentMove]);
6189         }
6190         return;
6191     }
6192
6193     /*
6194      * Look for communication commands
6195      */
6196     if (!strncmp(message, "telluser ", 9)) {
6197         DisplayNote(message + 9);
6198         return;
6199     }
6200     if (!strncmp(message, "tellusererror ", 14)) {
6201         DisplayError(message + 14, 0);
6202         return;
6203     }
6204     if (!strncmp(message, "tellopponent ", 13)) {
6205       if (appData.icsActive) {
6206         if (loggedOn) {
6207           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6208           SendToICS(buf1);
6209         }
6210       } else {
6211         DisplayNote(message + 13);
6212       }
6213       return;
6214     }
6215     if (!strncmp(message, "tellothers ", 11)) {
6216       if (appData.icsActive) {
6217         if (loggedOn) {
6218           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6219           SendToICS(buf1);
6220         }
6221       }
6222       return;
6223     }
6224     if (!strncmp(message, "tellall ", 8)) {
6225       if (appData.icsActive) {
6226         if (loggedOn) {
6227           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6228           SendToICS(buf1);
6229         }
6230       } else {
6231         DisplayNote(message + 8);
6232       }
6233       return;
6234     }
6235     if (strncmp(message, "warning", 7) == 0) {
6236         /* Undocumented feature, use tellusererror in new code */
6237         DisplayError(message, 0);
6238         return;
6239     }
6240     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6241         strcpy(realname, cps->tidy);
6242         strcat(realname, " query");
6243         AskQuestion(realname, buf2, buf1, cps->pr);
6244         return;
6245     }
6246     /* Commands from the engine directly to ICS.  We don't allow these to be
6247      *  sent until we are logged on. Crafty kibitzes have been known to
6248      *  interfere with the login process.
6249      */
6250     if (loggedOn) {
6251         if (!strncmp(message, "tellics ", 8)) {
6252             SendToICS(message + 8);
6253             SendToICS("\n");
6254             return;
6255         }
6256         if (!strncmp(message, "tellicsnoalias ", 15)) {
6257             SendToICS(ics_prefix);
6258             SendToICS(message + 15);
6259             SendToICS("\n");
6260             return;
6261         }
6262         /* The following are for backward compatibility only */
6263         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6264             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6265             SendToICS(ics_prefix);
6266             SendToICS(message);
6267             SendToICS("\n");
6268             return;
6269         }
6270     }
6271     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6272         return;
6273     }
6274     /*
6275      * If the move is illegal, cancel it and redraw the board.
6276      * Also deal with other error cases.  Matching is rather loose
6277      * here to accommodate engines written before the spec.
6278      */
6279     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6280         strncmp(message, "Error", 5) == 0) {
6281         if (StrStr(message, "name") ||
6282             StrStr(message, "rating") || StrStr(message, "?") ||
6283             StrStr(message, "result") || StrStr(message, "board") ||
6284             StrStr(message, "bk") || StrStr(message, "computer") ||
6285             StrStr(message, "variant") || StrStr(message, "hint") ||
6286             StrStr(message, "random") || StrStr(message, "depth") ||
6287             StrStr(message, "accepted")) {
6288             return;
6289         }
6290         if (StrStr(message, "protover")) {
6291           /* Program is responding to input, so it's apparently done
6292              initializing, and this error message indicates it is
6293              protocol version 1.  So we don't need to wait any longer
6294              for it to initialize and send feature commands. */
6295           FeatureDone(cps, 1);
6296           cps->protocolVersion = 1;
6297           return;
6298         }
6299         cps->maybeThinking = FALSE;
6300
6301         if (StrStr(message, "draw")) {
6302             /* Program doesn't have "draw" command */
6303             cps->sendDrawOffers = 0;
6304             return;
6305         }
6306         if (cps->sendTime != 1 &&
6307             (StrStr(message, "time") || StrStr(message, "otim"))) {
6308           /* Program apparently doesn't have "time" or "otim" command */
6309           cps->sendTime = 0;
6310           return;
6311         }
6312         if (StrStr(message, "analyze")) {
6313             cps->analysisSupport = FALSE;
6314             cps->analyzing = FALSE;
6315             Reset(FALSE, TRUE);
6316             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6317             DisplayError(buf2, 0);
6318             return;
6319         }
6320         if (StrStr(message, "(no matching move)st")) {
6321           /* Special kludge for GNU Chess 4 only */
6322           cps->stKludge = TRUE;
6323           SendTimeControl(cps, movesPerSession, timeControl,
6324                           timeIncrement, appData.searchDepth,
6325                           searchTime);
6326           return;
6327         }
6328         if (StrStr(message, "(no matching move)sd")) {
6329           /* Special kludge for GNU Chess 4 only */
6330           cps->sdKludge = TRUE;
6331           SendTimeControl(cps, movesPerSession, timeControl,
6332                           timeIncrement, appData.searchDepth,
6333                           searchTime);
6334           return;
6335         }
6336         if (!StrStr(message, "llegal")) {
6337             return;
6338         }
6339         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6340             gameMode == IcsIdle) return;
6341         if (forwardMostMove <= backwardMostMove) return;
6342         if (pausing) PauseEvent();
6343       if(appData.forceIllegal) {
6344             // [HGM] illegal: machine refused move; force position after move into it
6345           SendToProgram("force\n", cps);
6346           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6347                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6348                 // when black is to move, while there might be nothing on a2 or black
6349                 // might already have the move. So send the board as if white has the move.
6350                 // But first we must change the stm of the engine, as it refused the last move
6351                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6352                 if(WhiteOnMove(forwardMostMove)) {
6353                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6354                     SendBoard(cps, forwardMostMove); // kludgeless board
6355                 } else {
6356                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6357                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6358                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6359                 }
6360           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6361             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6362                  gameMode == TwoMachinesPlay)
6363               SendToProgram("go\n", cps);
6364             return;
6365       } else
6366         if (gameMode == PlayFromGameFile) {
6367             /* Stop reading this game file */
6368             gameMode = EditGame;
6369             ModeHighlight();
6370         }
6371         currentMove = --forwardMostMove;
6372         DisplayMove(currentMove-1); /* before DisplayMoveError */
6373         SwitchClocks();
6374         DisplayBothClocks();
6375         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6376                 parseList[currentMove], cps->which);
6377         DisplayMoveError(buf1);
6378         DrawPosition(FALSE, boards[currentMove]);
6379
6380         /* [HGM] illegal-move claim should forfeit game when Xboard */
6381         /* only passes fully legal moves                            */
6382         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6383             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6384                                 "False illegal-move claim", GE_XBOARD );
6385         }
6386         return;
6387     }
6388     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6389         /* Program has a broken "time" command that
6390            outputs a string not ending in newline.
6391            Don't use it. */
6392         cps->sendTime = 0;
6393     }
6394
6395     /*
6396      * If chess program startup fails, exit with an error message.
6397      * Attempts to recover here are futile.
6398      */
6399     if ((StrStr(message, "unknown host") != NULL)
6400         || (StrStr(message, "No remote directory") != NULL)
6401         || (StrStr(message, "not found") != NULL)
6402         || (StrStr(message, "No such file") != NULL)
6403         || (StrStr(message, "can't alloc") != NULL)
6404         || (StrStr(message, "Permission denied") != NULL)) {
6405
6406         cps->maybeThinking = FALSE;
6407         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6408                 cps->which, cps->program, cps->host, message);
6409         RemoveInputSource(cps->isr);
6410         DisplayFatalError(buf1, 0, 1);
6411         return;
6412     }
6413
6414     /*
6415      * Look for hint output
6416      */
6417     if (sscanf(message, "Hint: %s", buf1) == 1) {
6418         if (cps == &first && hintRequested) {
6419             hintRequested = FALSE;
6420             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6421                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6422                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6423                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6424                                     fromY, fromX, toY, toX, promoChar, buf1);
6425                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6426                 DisplayInformation(buf2);
6427             } else {
6428                 /* Hint move could not be parsed!? */
6429               snprintf(buf2, sizeof(buf2),
6430                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6431                         buf1, cps->which);
6432                 DisplayError(buf2, 0);
6433             }
6434         } else {
6435             strcpy(lastHint, buf1);
6436         }
6437         return;
6438     }
6439
6440     /*
6441      * Ignore other messages if game is not in progress
6442      */
6443     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6444         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6445
6446     /*
6447      * look for win, lose, draw, or draw offer
6448      */
6449     if (strncmp(message, "1-0", 3) == 0) {
6450         char *p, *q, *r = "";
6451         p = strchr(message, '{');
6452         if (p) {
6453             q = strchr(p, '}');
6454             if (q) {
6455                 *q = NULLCHAR;
6456                 r = p + 1;
6457             }
6458         }
6459         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6460         return;
6461     } else if (strncmp(message, "0-1", 3) == 0) {
6462         char *p, *q, *r = "";
6463         p = strchr(message, '{');
6464         if (p) {
6465             q = strchr(p, '}');
6466             if (q) {
6467                 *q = NULLCHAR;
6468                 r = p + 1;
6469             }
6470         }
6471         /* Kludge for Arasan 4.1 bug */
6472         if (strcmp(r, "Black resigns") == 0) {
6473             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6474             return;
6475         }
6476         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6477         return;
6478     } else if (strncmp(message, "1/2", 3) == 0) {
6479         char *p, *q, *r = "";
6480         p = strchr(message, '{');
6481         if (p) {
6482             q = strchr(p, '}');
6483             if (q) {
6484                 *q = NULLCHAR;
6485                 r = p + 1;
6486             }
6487         }
6488
6489         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6490         return;
6491
6492     } else if (strncmp(message, "White resign", 12) == 0) {
6493         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6494         return;
6495     } else if (strncmp(message, "Black resign", 12) == 0) {
6496         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6497         return;
6498     } else if (strncmp(message, "White matches", 13) == 0 ||
6499                strncmp(message, "Black matches", 13) == 0   ) {
6500         /* [HGM] ignore GNUShogi noises */
6501         return;
6502     } else if (strncmp(message, "White", 5) == 0 &&
6503                message[5] != '(' &&
6504                StrStr(message, "Black") == NULL) {
6505         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506         return;
6507     } else if (strncmp(message, "Black", 5) == 0 &&
6508                message[5] != '(') {
6509         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6510         return;
6511     } else if (strcmp(message, "resign") == 0 ||
6512                strcmp(message, "computer resigns") == 0) {
6513         switch (gameMode) {
6514           case MachinePlaysBlack:
6515           case IcsPlayingBlack:
6516             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6517             break;
6518           case MachinePlaysWhite:
6519           case IcsPlayingWhite:
6520             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6521             break;
6522           case TwoMachinesPlay:
6523             if (cps->twoMachinesColor[0] == 'w')
6524               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6525             else
6526               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6527             break;
6528           default:
6529             /* can't happen */
6530             break;
6531         }
6532         return;
6533     } else if (strncmp(message, "opponent mates", 14) == 0) {
6534         switch (gameMode) {
6535           case MachinePlaysBlack:
6536           case IcsPlayingBlack:
6537             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6538             break;
6539           case MachinePlaysWhite:
6540           case IcsPlayingWhite:
6541             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6542             break;
6543           case TwoMachinesPlay:
6544             if (cps->twoMachinesColor[0] == 'w')
6545               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6546             else
6547               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6548             break;
6549           default:
6550             /* can't happen */
6551             break;
6552         }
6553         return;
6554     } else if (strncmp(message, "computer mates", 14) == 0) {
6555         switch (gameMode) {
6556           case MachinePlaysBlack:
6557           case IcsPlayingBlack:
6558             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6559             break;
6560           case MachinePlaysWhite:
6561           case IcsPlayingWhite:
6562             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6563             break;
6564           case TwoMachinesPlay:
6565             if (cps->twoMachinesColor[0] == 'w')
6566               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6567             else
6568               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6569             break;
6570           default:
6571             /* can't happen */
6572             break;
6573         }
6574         return;
6575     } else if (strncmp(message, "checkmate", 9) == 0) {
6576         if (WhiteOnMove(forwardMostMove)) {
6577             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6578         } else {
6579             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6580         }
6581         return;
6582     } else if (strstr(message, "Draw") != NULL ||
6583                strstr(message, "game is a draw") != NULL) {
6584         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6585         return;
6586     } else if (strstr(message, "offer") != NULL &&
6587                strstr(message, "draw") != NULL) {
6588 #if ZIPPY
6589         if (appData.zippyPlay && first.initDone) {
6590             /* Relay offer to ICS */
6591             SendToICS(ics_prefix);
6592             SendToICS("draw\n");
6593         }
6594 #endif
6595         cps->offeredDraw = 2; /* valid until this engine moves twice */
6596         if (gameMode == TwoMachinesPlay) {
6597             if (cps->other->offeredDraw) {
6598                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6599             /* [HGM] in two-machine mode we delay relaying draw offer      */
6600             /* until after we also have move, to see if it is really claim */
6601             }
6602         } else if (gameMode == MachinePlaysWhite ||
6603                    gameMode == MachinePlaysBlack) {
6604           if (userOfferedDraw) {
6605             DisplayInformation(_("Machine accepts your draw offer"));
6606             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6607           } else {
6608             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6609           }
6610         }
6611     }
6612
6613
6614     /*
6615      * Look for thinking output
6616      */
6617     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6618           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6619                                 ) {
6620         int plylev, mvleft, mvtot, curscore, time;
6621         char mvname[MOVE_LEN];
6622         u64 nodes; // [DM]
6623         char plyext;
6624         int ignore = FALSE;
6625         int prefixHint = FALSE;
6626         mvname[0] = NULLCHAR;
6627
6628         switch (gameMode) {
6629           case MachinePlaysBlack:
6630           case IcsPlayingBlack:
6631             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6632             break;
6633           case MachinePlaysWhite:
6634           case IcsPlayingWhite:
6635             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6636             break;
6637           case AnalyzeMode:
6638           case AnalyzeFile:
6639             break;
6640           case IcsObserving: /* [DM] icsEngineAnalyze */
6641             if (!appData.icsEngineAnalyze) ignore = TRUE;
6642             break;
6643           case TwoMachinesPlay:
6644             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6645                 ignore = TRUE;
6646             }
6647             break;
6648           default:
6649             ignore = TRUE;
6650             break;
6651         }
6652
6653         if (!ignore) {
6654             buf1[0] = NULLCHAR;
6655             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6656                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6657
6658                 if (plyext != ' ' && plyext != '\t') {
6659                     time *= 100;
6660                 }
6661
6662                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6663                 if( cps->scoreIsAbsolute &&
6664                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6665                 {
6666                     curscore = -curscore;
6667                 }
6668
6669
6670                 programStats.depth = plylev;
6671                 programStats.nodes = nodes;
6672                 programStats.time = time;
6673                 programStats.score = curscore;
6674                 programStats.got_only_move = 0;
6675
6676                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6677                         int ticklen;
6678
6679                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6680                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6681                         if(WhiteOnMove(forwardMostMove))
6682                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6683                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6684                 }
6685
6686                 /* Buffer overflow protection */
6687                 if (buf1[0] != NULLCHAR) {
6688                     if (strlen(buf1) >= sizeof(programStats.movelist)
6689                         && appData.debugMode) {
6690                         fprintf(debugFP,
6691                                 "PV is too long; using the first %d bytes.\n",
6692                                 sizeof(programStats.movelist) - 1);
6693                     }
6694
6695                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6696                 } else {
6697                     sprintf(programStats.movelist, " no PV\n");
6698                 }
6699
6700                 if (programStats.seen_stat) {
6701                     programStats.ok_to_send = 1;
6702                 }
6703
6704                 if (strchr(programStats.movelist, '(') != NULL) {
6705                     programStats.line_is_book = 1;
6706                     programStats.nr_moves = 0;
6707                     programStats.moves_left = 0;
6708                 } else {
6709                     programStats.line_is_book = 0;
6710                 }
6711
6712                 SendProgramStatsToFrontend( cps, &programStats );
6713
6714                 /*
6715                     [AS] Protect the thinkOutput buffer from overflow... this
6716                     is only useful if buf1 hasn't overflowed first!
6717                 */
6718                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6719                         plylev,
6720                         (gameMode == TwoMachinesPlay ?
6721                          ToUpper(cps->twoMachinesColor[0]) : ' '),
6722                         ((double) curscore) / 100.0,
6723                         prefixHint ? lastHint : "",
6724                         prefixHint ? " " : "" );
6725
6726                 if( buf1[0] != NULLCHAR ) {
6727                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6728
6729                     if( strlen(buf1) > max_len ) {
6730                         if( appData.debugMode) {
6731                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6732                         }
6733                         buf1[max_len+1] = '\0';
6734                     }
6735
6736                     strcat( thinkOutput, buf1 );
6737                 }
6738
6739                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6740                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6741                     DisplayMove(currentMove - 1);
6742                     DisplayAnalysis();
6743                 }
6744                 return;
6745
6746             } else if ((p=StrStr(message, "(only move)")) != NULL) {
6747                 /* crafty (9.25+) says "(only move) <move>"
6748                  * if there is only 1 legal move
6749                  */
6750                 sscanf(p, "(only move) %s", buf1);
6751                 sprintf(thinkOutput, "%s (only move)", buf1);
6752                 sprintf(programStats.movelist, "%s (only move)", buf1);
6753                 programStats.depth = 1;
6754                 programStats.nr_moves = 1;
6755                 programStats.moves_left = 1;
6756                 programStats.nodes = 1;
6757                 programStats.time = 1;
6758                 programStats.got_only_move = 1;
6759
6760                 /* Not really, but we also use this member to
6761                    mean "line isn't going to change" (Crafty
6762                    isn't searching, so stats won't change) */
6763                 programStats.line_is_book = 1;
6764
6765                 SendProgramStatsToFrontend( cps, &programStats );
6766
6767                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6768                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6769                     DisplayMove(currentMove - 1);
6770                     DisplayAnalysis();
6771                 }
6772                 return;
6773             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6774                               &time, &nodes, &plylev, &mvleft,
6775                               &mvtot, mvname) >= 5) {
6776                 /* The stat01: line is from Crafty (9.29+) in response
6777                    to the "." command */
6778                 programStats.seen_stat = 1;
6779                 cps->maybeThinking = TRUE;
6780
6781                 if (programStats.got_only_move || !appData.periodicUpdates)
6782                   return;
6783
6784                 programStats.depth = plylev;
6785                 programStats.time = time;
6786                 programStats.nodes = nodes;
6787                 programStats.moves_left = mvleft;
6788                 programStats.nr_moves = mvtot;
6789                 strcpy(programStats.move_name, mvname);
6790                 programStats.ok_to_send = 1;
6791                 programStats.movelist[0] = '\0';
6792
6793                 SendProgramStatsToFrontend( cps, &programStats );
6794
6795                 DisplayAnalysis();
6796                 return;
6797
6798             } else if (strncmp(message,"++",2) == 0) {
6799                 /* Crafty 9.29+ outputs this */
6800                 programStats.got_fail = 2;
6801                 return;
6802
6803             } else if (strncmp(message,"--",2) == 0) {
6804                 /* Crafty 9.29+ outputs this */
6805                 programStats.got_fail = 1;
6806                 return;
6807
6808             } else if (thinkOutput[0] != NULLCHAR &&
6809                        strncmp(message, "    ", 4) == 0) {
6810                 unsigned message_len;
6811
6812                 p = message;
6813                 while (*p && *p == ' ') p++;
6814
6815                 message_len = strlen( p );
6816
6817                 /* [AS] Avoid buffer overflow */
6818                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6819                     strcat(thinkOutput, " ");
6820                     strcat(thinkOutput, p);
6821                 }
6822
6823                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6824                     strcat(programStats.movelist, " ");
6825                     strcat(programStats.movelist, p);
6826                 }
6827
6828                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6829                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6830                     DisplayMove(currentMove - 1);
6831                     DisplayAnalysis();
6832                 }
6833                 return;
6834             }
6835         }
6836         else {
6837             buf1[0] = NULLCHAR;
6838
6839             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6840                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6841             {
6842                 ChessProgramStats cpstats;
6843
6844                 if (plyext != ' ' && plyext != '\t') {
6845                     time *= 100;
6846                 }
6847
6848                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6849                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6850                     curscore = -curscore;
6851                 }
6852
6853                 cpstats.depth = plylev;
6854                 cpstats.nodes = nodes;
6855                 cpstats.time = time;
6856                 cpstats.score = curscore;
6857                 cpstats.got_only_move = 0;
6858                 cpstats.movelist[0] = '\0';
6859
6860                 if (buf1[0] != NULLCHAR) {
6861                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6862                 }
6863
6864                 cpstats.ok_to_send = 0;
6865                 cpstats.line_is_book = 0;
6866                 cpstats.nr_moves = 0;
6867                 cpstats.moves_left = 0;
6868
6869                 SendProgramStatsToFrontend( cps, &cpstats );
6870             }
6871         }
6872     }
6873 }
6874
6875
6876 /* Parse a game score from the character string "game", and
6877    record it as the history of the current game.  The game
6878    score is NOT assumed to start from the standard position.
6879    The display is not updated in any way.
6880    */
6881 void
6882 ParseGameHistory(game)
6883      char *game;
6884 {
6885     ChessMove moveType;
6886     int fromX, fromY, toX, toY, boardIndex;
6887     char promoChar;
6888     char *p, *q;
6889     char buf[MSG_SIZ];
6890
6891     if (appData.debugMode)
6892       fprintf(debugFP, "Parsing game history: %s\n", game);
6893
6894     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6895     gameInfo.site = StrSave(appData.icsHost);
6896     gameInfo.date = PGNDate();
6897     gameInfo.round = StrSave("-");
6898
6899     /* Parse out names of players */
6900     while (*game == ' ') game++;
6901     p = buf;
6902     while (*game != ' ') *p++ = *game++;
6903     *p = NULLCHAR;
6904     gameInfo.white = StrSave(buf);
6905     while (*game == ' ') game++;
6906     p = buf;
6907     while (*game != ' ' && *game != '\n') *p++ = *game++;
6908     *p = NULLCHAR;
6909     gameInfo.black = StrSave(buf);
6910
6911     /* Parse moves */
6912     boardIndex = blackPlaysFirst ? 1 : 0;
6913     yynewstr(game);
6914     for (;;) {
6915         yyboardindex = boardIndex;
6916         moveType = (ChessMove) yylex();
6917         switch (moveType) {
6918           case IllegalMove:             /* maybe suicide chess, etc. */
6919   if (appData.debugMode) {
6920     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6921     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6922     setbuf(debugFP, NULL);
6923   }
6924           case WhitePromotionChancellor:
6925           case BlackPromotionChancellor:
6926           case WhitePromotionArchbishop:
6927           case BlackPromotionArchbishop:
6928           case WhitePromotionQueen:
6929           case BlackPromotionQueen:
6930           case WhitePromotionRook:
6931           case BlackPromotionRook:
6932           case WhitePromotionBishop:
6933           case BlackPromotionBishop:
6934           case WhitePromotionKnight:
6935           case BlackPromotionKnight:
6936           case WhitePromotionKing:
6937           case BlackPromotionKing:
6938           case NormalMove:
6939           case WhiteCapturesEnPassant:
6940           case BlackCapturesEnPassant:
6941           case WhiteKingSideCastle:
6942           case WhiteQueenSideCastle:
6943           case BlackKingSideCastle:
6944           case BlackQueenSideCastle:
6945           case WhiteKingSideCastleWild:
6946           case WhiteQueenSideCastleWild:
6947           case BlackKingSideCastleWild:
6948           case BlackQueenSideCastleWild:
6949           /* PUSH Fabien */
6950           case WhiteHSideCastleFR:
6951           case WhiteASideCastleFR:
6952           case BlackHSideCastleFR:
6953           case BlackASideCastleFR:
6954           /* POP Fabien */
6955             fromX = currentMoveString[0] - AAA;
6956             fromY = currentMoveString[1] - ONE;
6957             toX = currentMoveString[2] - AAA;
6958             toY = currentMoveString[3] - ONE;
6959             promoChar = currentMoveString[4];
6960             break;
6961           case WhiteDrop:
6962           case BlackDrop:
6963             fromX = moveType == WhiteDrop ?
6964               (int) CharToPiece(ToUpper(currentMoveString[0])) :
6965             (int) CharToPiece(ToLower(currentMoveString[0]));
6966             fromY = DROP_RANK;
6967             toX = currentMoveString[2] - AAA;
6968             toY = currentMoveString[3] - ONE;
6969             promoChar = NULLCHAR;
6970             break;
6971           case AmbiguousMove:
6972             /* bug? */
6973             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6974   if (appData.debugMode) {
6975     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6976     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6977     setbuf(debugFP, NULL);
6978   }
6979             DisplayError(buf, 0);
6980             return;
6981           case ImpossibleMove:
6982             /* bug? */
6983             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6984   if (appData.debugMode) {
6985     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6986     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6987     setbuf(debugFP, NULL);
6988   }
6989             DisplayError(buf, 0);
6990             return;
6991           case (ChessMove) 0:   /* end of file */
6992             if (boardIndex < backwardMostMove) {
6993                 /* Oops, gap.  How did that happen? */
6994                 DisplayError(_("Gap in move list"), 0);
6995                 return;
6996             }
6997             backwardMostMove =  blackPlaysFirst ? 1 : 0;
6998             if (boardIndex > forwardMostMove) {
6999                 forwardMostMove = boardIndex;
7000             }
7001             return;
7002           case ElapsedTime:
7003             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7004                 strcat(parseList[boardIndex-1], " ");
7005                 strcat(parseList[boardIndex-1], yy_text);
7006             }
7007             continue;
7008           case Comment:
7009           case PGNTag:
7010           case NAG:
7011           default:
7012             /* ignore */
7013             continue;
7014           case WhiteWins:
7015           case BlackWins:
7016           case GameIsDrawn:
7017           case GameUnfinished:
7018             if (gameMode == IcsExamining) {
7019                 if (boardIndex < backwardMostMove) {
7020                     /* Oops, gap.  How did that happen? */
7021                     return;
7022                 }
7023                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7024                 return;
7025             }
7026             gameInfo.result = moveType;
7027             p = strchr(yy_text, '{');
7028             if (p == NULL) p = strchr(yy_text, '(');
7029             if (p == NULL) {
7030                 p = yy_text;
7031                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7032             } else {
7033                 q = strchr(p, *p == '{' ? '}' : ')');
7034                 if (q != NULL) *q = NULLCHAR;
7035                 p++;
7036             }
7037             gameInfo.resultDetails = StrSave(p);
7038             continue;
7039         }
7040         if (boardIndex >= forwardMostMove &&
7041             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7042             backwardMostMove = blackPlaysFirst ? 1 : 0;
7043             return;
7044         }
7045         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7046                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7047                                  parseList[boardIndex]);
7048         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7049         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7050         /* currentMoveString is set as a side-effect of yylex */
7051         strcpy(moveList[boardIndex], currentMoveString);
7052         strcat(moveList[boardIndex], "\n");
7053         boardIndex++;
7054         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7055                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7056         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7057                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7058           case MT_NONE:
7059           case MT_STALEMATE:
7060           default:
7061             break;
7062           case MT_CHECK:
7063             if(gameInfo.variant != VariantShogi)
7064                 strcat(parseList[boardIndex - 1], "+");
7065             break;
7066           case MT_CHECKMATE:
7067           case MT_STAINMATE:
7068             strcat(parseList[boardIndex - 1], "#");
7069             break;
7070         }
7071     }
7072 }
7073
7074
7075 /* Apply a move to the given board  */
7076 void
7077 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7078      int fromX, fromY, toX, toY;
7079      int promoChar;
7080      Board board;
7081      char *castling;
7082      char *ep;
7083 {
7084   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7085
7086     /* [HGM] compute & store e.p. status and castling rights for new position */
7087     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7088     { int i;
7089
7090       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7091       oldEP = *ep;
7092       *ep = EP_NONE;
7093
7094       if( board[toY][toX] != EmptySquare )
7095            *ep = EP_CAPTURE;
7096
7097       if( board[fromY][fromX] == WhitePawn ) {
7098            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7099                *ep = EP_PAWN_MOVE;
7100            if( toY-fromY==2) {
7101                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7102                         gameInfo.variant != VariantBerolina || toX < fromX)
7103                       *ep = toX | berolina;
7104                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7105                         gameInfo.variant != VariantBerolina || toX > fromX)
7106                       *ep = toX;
7107            }
7108       } else
7109       if( board[fromY][fromX] == BlackPawn ) {
7110            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7111                *ep = EP_PAWN_MOVE;
7112            if( toY-fromY== -2) {
7113                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7114                         gameInfo.variant != VariantBerolina || toX < fromX)
7115                       *ep = toX | berolina;
7116                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7117                         gameInfo.variant != VariantBerolina || toX > fromX)
7118                       *ep = toX;
7119            }
7120        }
7121
7122        for(i=0; i<nrCastlingRights; i++) {
7123            if(castling[i] == fromX && castlingRank[i] == fromY ||
7124               castling[i] == toX   && castlingRank[i] == toY
7125              ) castling[i] = -1; // revoke for moved or captured piece
7126        }
7127
7128     }
7129
7130   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7131   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7132        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7133
7134   if (fromX == toX && fromY == toY) return;
7135
7136   if (fromY == DROP_RANK) {
7137         /* must be first */
7138         piece = board[toY][toX] = (ChessSquare) fromX;
7139   } else {
7140      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7141      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7142      if(gameInfo.variant == VariantKnightmate)
7143          king += (int) WhiteUnicorn - (int) WhiteKing;
7144
7145     /* Code added by Tord: */
7146     /* FRC castling assumed when king captures friendly rook. */
7147     if (board[fromY][fromX] == WhiteKing &&
7148              board[toY][toX] == WhiteRook) {
7149       board[fromY][fromX] = EmptySquare;
7150       board[toY][toX] = EmptySquare;
7151       if(toX > fromX) {
7152         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7153       } else {
7154         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7155       }
7156     } else if (board[fromY][fromX] == BlackKing &&
7157                board[toY][toX] == BlackRook) {
7158       board[fromY][fromX] = EmptySquare;
7159       board[toY][toX] = EmptySquare;
7160       if(toX > fromX) {
7161         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7162       } else {
7163         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7164       }
7165     /* End of code added by Tord */
7166
7167     } else if (board[fromY][fromX] == king
7168         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7169         && toY == fromY && toX > fromX+1) {
7170         board[fromY][fromX] = EmptySquare;
7171         board[toY][toX] = king;
7172         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7173         board[fromY][BOARD_RGHT-1] = EmptySquare;
7174     } else if (board[fromY][fromX] == king
7175         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7176                && toY == fromY && toX < fromX-1) {
7177         board[fromY][fromX] = EmptySquare;
7178         board[toY][toX] = king;
7179         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7180         board[fromY][BOARD_LEFT] = EmptySquare;
7181     } else if (board[fromY][fromX] == WhitePawn
7182                && toY == BOARD_HEIGHT-1
7183                && gameInfo.variant != VariantXiangqi
7184                ) {
7185         /* white pawn promotion */
7186         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7187         if (board[toY][toX] == EmptySquare) {
7188             board[toY][toX] = WhiteQueen;
7189         }
7190         if(gameInfo.variant==VariantBughouse ||
7191            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7192             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7193         board[fromY][fromX] = EmptySquare;
7194     } else if ((fromY == BOARD_HEIGHT-4)
7195                && (toX != fromX)
7196                && gameInfo.variant != VariantXiangqi
7197                && gameInfo.variant != VariantBerolina
7198                && (board[fromY][fromX] == WhitePawn)
7199                && (board[toY][toX] == EmptySquare)) {
7200         board[fromY][fromX] = EmptySquare;
7201         board[toY][toX] = WhitePawn;
7202         captured = board[toY - 1][toX];
7203         board[toY - 1][toX] = EmptySquare;
7204     } else if ((fromY == BOARD_HEIGHT-4)
7205                && (toX == fromX)
7206                && gameInfo.variant == VariantBerolina
7207                && (board[fromY][fromX] == WhitePawn)
7208                && (board[toY][toX] == EmptySquare)) {
7209         board[fromY][fromX] = EmptySquare;
7210         board[toY][toX] = WhitePawn;
7211         if(oldEP & EP_BEROLIN_A) {
7212                 captured = board[fromY][fromX-1];
7213                 board[fromY][fromX-1] = EmptySquare;
7214         }else{  captured = board[fromY][fromX+1];
7215                 board[fromY][fromX+1] = EmptySquare;
7216         }
7217     } else if (board[fromY][fromX] == king
7218         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219                && toY == fromY && toX > fromX+1) {
7220         board[fromY][fromX] = EmptySquare;
7221         board[toY][toX] = king;
7222         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7223         board[fromY][BOARD_RGHT-1] = EmptySquare;
7224     } else if (board[fromY][fromX] == king
7225         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7226                && toY == fromY && toX < fromX-1) {
7227         board[fromY][fromX] = EmptySquare;
7228         board[toY][toX] = king;
7229         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7230         board[fromY][BOARD_LEFT] = EmptySquare;
7231     } else if (fromY == 7 && fromX == 3
7232                && board[fromY][fromX] == BlackKing
7233                && toY == 7 && toX == 5) {
7234         board[fromY][fromX] = EmptySquare;
7235         board[toY][toX] = BlackKing;
7236         board[fromY][7] = EmptySquare;
7237         board[toY][4] = BlackRook;
7238     } else if (fromY == 7 && fromX == 3
7239                && board[fromY][fromX] == BlackKing
7240                && toY == 7 && toX == 1) {
7241         board[fromY][fromX] = EmptySquare;
7242         board[toY][toX] = BlackKing;
7243         board[fromY][0] = EmptySquare;
7244         board[toY][2] = BlackRook;
7245     } else if (board[fromY][fromX] == BlackPawn
7246                && toY == 0
7247                && gameInfo.variant != VariantXiangqi
7248                ) {
7249         /* black pawn promotion */
7250         board[0][toX] = CharToPiece(ToLower(promoChar));
7251         if (board[0][toX] == EmptySquare) {
7252             board[0][toX] = BlackQueen;
7253         }
7254         if(gameInfo.variant==VariantBughouse ||
7255            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7256             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7257         board[fromY][fromX] = EmptySquare;
7258     } else if ((fromY == 3)
7259                && (toX != fromX)
7260                && gameInfo.variant != VariantXiangqi
7261                && gameInfo.variant != VariantBerolina
7262                && (board[fromY][fromX] == BlackPawn)
7263                && (board[toY][toX] == EmptySquare)) {
7264         board[fromY][fromX] = EmptySquare;
7265         board[toY][toX] = BlackPawn;
7266         captured = board[toY + 1][toX];
7267         board[toY + 1][toX] = EmptySquare;
7268     } else if ((fromY == 3)
7269                && (toX == fromX)
7270                && gameInfo.variant == VariantBerolina
7271                && (board[fromY][fromX] == BlackPawn)
7272                && (board[toY][toX] == EmptySquare)) {
7273         board[fromY][fromX] = EmptySquare;
7274         board[toY][toX] = BlackPawn;
7275         if(oldEP & EP_BEROLIN_A) {
7276                 captured = board[fromY][fromX-1];
7277                 board[fromY][fromX-1] = EmptySquare;
7278         }else{  captured = board[fromY][fromX+1];
7279                 board[fromY][fromX+1] = EmptySquare;
7280         }
7281     } else {
7282         board[toY][toX] = board[fromY][fromX];
7283         board[fromY][fromX] = EmptySquare;
7284     }
7285
7286     /* [HGM] now we promote for Shogi, if needed */
7287     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7288         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7289   }
7290
7291     if (gameInfo.holdingsWidth != 0) {
7292
7293       /* !!A lot more code needs to be written to support holdings  */
7294       /* [HGM] OK, so I have written it. Holdings are stored in the */
7295       /* penultimate board files, so they are automaticlly stored   */
7296       /* in the game history.                                       */
7297       if (fromY == DROP_RANK) {
7298         /* Delete from holdings, by decreasing count */
7299         /* and erasing image if necessary            */
7300         p = (int) fromX;
7301         if(p < (int) BlackPawn) { /* white drop */
7302              p -= (int)WhitePawn;
7303              if(p >= gameInfo.holdingsSize) p = 0;
7304              if(--board[p][BOARD_WIDTH-2] == 0)
7305                   board[p][BOARD_WIDTH-1] = EmptySquare;
7306         } else {                  /* black drop */
7307              p -= (int)BlackPawn;
7308              if(p >= gameInfo.holdingsSize) p = 0;
7309              if(--board[BOARD_HEIGHT-1-p][1] == 0)
7310                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7311         }
7312       }
7313       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7314           && gameInfo.variant != VariantBughouse        ) {
7315         /* [HGM] holdings: Add to holdings, if holdings exist */
7316         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7317                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7318                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7319         }
7320         p = (int) captured;
7321         if (p >= (int) BlackPawn) {
7322           p -= (int)BlackPawn;
7323           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7324                   /* in Shogi restore piece to its original  first */
7325                   captured = (ChessSquare) (DEMOTED captured);
7326                   p = DEMOTED p;
7327           }
7328           p = PieceToNumber((ChessSquare)p);
7329           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7330           board[p][BOARD_WIDTH-2]++;
7331           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7332         } else {
7333           p -= (int)WhitePawn;
7334           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7335                   captured = (ChessSquare) (DEMOTED captured);
7336                   p = DEMOTED p;
7337           }
7338           p = PieceToNumber((ChessSquare)p);
7339           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7340           board[BOARD_HEIGHT-1-p][1]++;
7341           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7342         }
7343       }
7344
7345     } else if (gameInfo.variant == VariantAtomic) {
7346       if (captured != EmptySquare) {
7347         int y, x;
7348         for (y = toY-1; y <= toY+1; y++) {
7349           for (x = toX-1; x <= toX+1; x++) {
7350             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7351                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7352               board[y][x] = EmptySquare;
7353             }
7354           }
7355         }
7356         board[toY][toX] = EmptySquare;
7357       }
7358     }
7359     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7360         /* [HGM] Shogi promotions */
7361         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7362     }
7363
7364     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7365                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7366         // [HGM] superchess: take promotion piece out of holdings
7367         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7368         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7369             if(!--board[k][BOARD_WIDTH-2])
7370                 board[k][BOARD_WIDTH-1] = EmptySquare;
7371         } else {
7372             if(!--board[BOARD_HEIGHT-1-k][1])
7373                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7374         }
7375     }
7376
7377 }
7378
7379 /* Updates forwardMostMove */
7380 void
7381 MakeMove(fromX, fromY, toX, toY, promoChar)
7382      int fromX, fromY, toX, toY;
7383      int promoChar;
7384 {
7385 //    forwardMostMove++; // [HGM] bare: moved downstream
7386
7387     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7388         int timeLeft; static int lastLoadFlag=0; int king, piece;
7389         piece = boards[forwardMostMove][fromY][fromX];
7390         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7391         if(gameInfo.variant == VariantKnightmate)
7392             king += (int) WhiteUnicorn - (int) WhiteKing;
7393         if(forwardMostMove == 0) {
7394             if(blackPlaysFirst)
7395                 fprintf(serverMoves, "%s;", second.tidy);
7396             fprintf(serverMoves, "%s;", first.tidy);
7397             if(!blackPlaysFirst)
7398                 fprintf(serverMoves, "%s;", second.tidy);
7399         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7400         lastLoadFlag = loadFlag;
7401         // print base move
7402         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7403         // print castling suffix
7404         if( toY == fromY && piece == king ) {
7405             if(toX-fromX > 1)
7406                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7407             if(fromX-toX >1)
7408                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7409         }
7410         // e.p. suffix
7411         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7412              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7413              boards[forwardMostMove][toY][toX] == EmptySquare
7414              && fromX != toX )
7415                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7416         // promotion suffix
7417         if(promoChar != NULLCHAR)
7418                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7419         if(!loadFlag) {
7420             fprintf(serverMoves, "/%d/%d",
7421                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7422             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7423             else                      timeLeft = blackTimeRemaining/1000;
7424             fprintf(serverMoves, "/%d", timeLeft);
7425         }
7426         fflush(serverMoves);
7427     }
7428
7429     if (forwardMostMove+1 >= MAX_MOVES) {
7430       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7431                         0, 1);
7432       return;
7433     }
7434     if (commentList[forwardMostMove+1] != NULL) {
7435         free(commentList[forwardMostMove+1]);
7436         commentList[forwardMostMove+1] = NULL;
7437     }
7438     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7439     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7440     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7441                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7442     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7443     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7444     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7445     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7446     gameInfo.result = GameUnfinished;
7447     if (gameInfo.resultDetails != NULL) {
7448         free(gameInfo.resultDetails);
7449         gameInfo.resultDetails = NULL;
7450     }
7451     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7452                               moveList[forwardMostMove - 1]);
7453     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7454                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7455                              fromY, fromX, toY, toX, promoChar,
7456                              parseList[forwardMostMove - 1]);
7457     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7458                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7459                             castlingRights[forwardMostMove]) ) {
7460       case MT_NONE:
7461       case MT_STALEMATE:
7462       default:
7463         break;
7464       case MT_CHECK:
7465         if(gameInfo.variant != VariantShogi)
7466             strcat(parseList[forwardMostMove - 1], "+");
7467         break;
7468       case MT_CHECKMATE:
7469       case MT_STAINMATE:
7470         strcat(parseList[forwardMostMove - 1], "#");
7471         break;
7472     }
7473     if (appData.debugMode) {
7474         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7475     }
7476
7477 }
7478
7479 /* Updates currentMove if not pausing */
7480 void
7481 ShowMove(fromX, fromY, toX, toY)
7482 {
7483     int instant = (gameMode == PlayFromGameFile) ?
7484         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7485
7486     if(appData.noGUI) return;
7487
7488     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7489       {
7490         if (!instant)
7491           {
7492             if (forwardMostMove == currentMove + 1)
7493               {
7494 //TODO
7495 //              AnimateMove(boards[forwardMostMove - 1],
7496 //                          fromX, fromY, toX, toY);
7497               }
7498             if (appData.highlightLastMove)
7499               {
7500                 SetHighlights(fromX, fromY, toX, toY);
7501               }
7502           }
7503         currentMove = forwardMostMove;
7504     }
7505
7506     if (instant) return;
7507
7508     DisplayMove(currentMove - 1);
7509     DrawPosition(FALSE, boards[currentMove]);
7510     DisplayBothClocks();
7511     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7512
7513     return;
7514 }
7515
7516 void SendEgtPath(ChessProgramState *cps)
7517 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7518         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7519
7520         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7521
7522         while(*p) {
7523             char c, *q = name+1, *r, *s;
7524
7525             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7526             while(*p && *p != ',') *q++ = *p++;
7527             *q++ = ':'; *q = 0;
7528             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7529                 strcmp(name, ",nalimov:") == 0 ) {
7530                 // take nalimov path from the menu-changeable option first, if it is defined
7531                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7532                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7533             } else
7534             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7535                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7536                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7537                 s = r = StrStr(s, ":") + 1; // beginning of path info
7538                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7539                 c = *r; *r = 0;             // temporarily null-terminate path info
7540                     *--q = 0;               // strip of trailig ':' from name
7541                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7542                 *r = c;
7543                 SendToProgram(buf,cps);     // send egtbpath command for this format
7544             }
7545             if(*p == ',') p++; // read away comma to position for next format name
7546         }
7547 }
7548
7549 void
7550 InitChessProgram(cps, setup)
7551      ChessProgramState *cps;
7552      int setup; /* [HGM] needed to setup FRC opening position */
7553 {
7554     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7555     if (appData.noChessProgram) return;
7556     hintRequested = FALSE;
7557     bookRequested = FALSE;
7558
7559     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7560     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7561     if(cps->memSize) { /* [HGM] memory */
7562         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7563         SendToProgram(buf, cps);
7564     }
7565     SendEgtPath(cps); /* [HGM] EGT */
7566     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7567         sprintf(buf, "cores %d\n", appData.smpCores);
7568         SendToProgram(buf, cps);
7569     }
7570
7571     SendToProgram(cps->initString, cps);
7572     if (gameInfo.variant != VariantNormal &&
7573         gameInfo.variant != VariantLoadable
7574         /* [HGM] also send variant if board size non-standard */
7575         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7576                                             ) {
7577       char *v = VariantName(gameInfo.variant);
7578       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7579         /* [HGM] in protocol 1 we have to assume all variants valid */
7580         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7581         DisplayFatalError(buf, 0, 1);
7582         return;
7583       }
7584
7585       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7586       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7587       if( gameInfo.variant == VariantXiangqi )
7588            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7589       if( gameInfo.variant == VariantShogi )
7590            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7591       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7592            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7593       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7594                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7595            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7596       if( gameInfo.variant == VariantCourier )
7597            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7598       if( gameInfo.variant == VariantSuper )
7599            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7600       if( gameInfo.variant == VariantGreat )
7601            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7602
7603       if(overruled) {
7604            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7605                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7606            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7607            if(StrStr(cps->variants, b) == NULL) {
7608                // specific sized variant not known, check if general sizing allowed
7609                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7610                    if(StrStr(cps->variants, "boardsize") == NULL) {
7611                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7612                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7613                        DisplayFatalError(buf, 0, 1);
7614                        return;
7615                    }
7616                    /* [HGM] here we really should compare with the maximum supported board size */
7617                }
7618            }
7619       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7620       sprintf(buf, "variant %s\n", b);
7621       SendToProgram(buf, cps);
7622     }
7623     currentlyInitializedVariant = gameInfo.variant;
7624
7625     /* [HGM] send opening position in FRC to first engine */
7626     if(setup) {
7627           SendToProgram("force\n", cps);
7628           SendBoard(cps, 0);
7629           /* engine is now in force mode! Set flag to wake it up after first move. */
7630           setboardSpoiledMachineBlack = 1;
7631     }
7632
7633     if (cps->sendICS) {
7634       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7635       SendToProgram(buf, cps);
7636     }
7637     cps->maybeThinking = FALSE;
7638     cps->offeredDraw = 0;
7639     if (!appData.icsActive) {
7640         SendTimeControl(cps, movesPerSession, timeControl,
7641                         timeIncrement, appData.searchDepth,
7642                         searchTime);
7643     }
7644     if (appData.showThinking
7645         // [HGM] thinking: four options require thinking output to be sent
7646         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7647                                 ) {
7648         SendToProgram("post\n", cps);
7649     }
7650     SendToProgram("hard\n", cps);
7651     if (!appData.ponderNextMove) {
7652         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7653            it without being sure what state we are in first.  "hard"
7654            is not a toggle, so that one is OK.
7655          */
7656         SendToProgram("easy\n", cps);
7657     }
7658     if (cps->usePing) {
7659       sprintf(buf, "ping %d\n", ++cps->lastPing);
7660       SendToProgram(buf, cps);
7661     }
7662     cps->initDone = TRUE;
7663 }
7664
7665
7666 void
7667 StartChessProgram(cps)
7668      ChessProgramState *cps;
7669 {
7670     char buf[MSG_SIZ];
7671     int err;
7672
7673     if (appData.noChessProgram) return;
7674     cps->initDone = FALSE;
7675
7676     if (strcmp(cps->host, "localhost") == 0) {
7677         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7678     } else if (*appData.remoteShell == NULLCHAR) {
7679         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7680     } else {
7681         if (*appData.remoteUser == NULLCHAR) {
7682           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7683                     cps->program);
7684         } else {
7685           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7686                     cps->host, appData.remoteUser, cps->program);
7687         }
7688         err = StartChildProcess(buf, "", &cps->pr);
7689     }
7690
7691     if (err != 0) {
7692         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7693         DisplayFatalError(buf, err, 1);
7694         cps->pr = NoProc;
7695         cps->isr = NULL;
7696         return;
7697     }
7698
7699     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7700     if (cps->protocolVersion > 1) {
7701       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7702       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7703       cps->comboCnt = 0;  //                and values of combo boxes
7704       SendToProgram(buf, cps);
7705     } else {
7706       SendToProgram("xboard\n", cps);
7707     }
7708 }
7709
7710
7711 void
7712 TwoMachinesEventIfReady P((void))
7713 {
7714   if (first.lastPing != first.lastPong) {
7715     DisplayMessage("", _("Waiting for first chess program"));
7716     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7717     return;
7718   }
7719   if (second.lastPing != second.lastPong) {
7720     DisplayMessage("", _("Waiting for second chess program"));
7721     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7722     return;
7723   }
7724   ThawUI();
7725   TwoMachinesEvent();
7726 }
7727
7728 void
7729 NextMatchGame P((void))
7730 {
7731     int index; /* [HGM] autoinc: step lod index during match */
7732     Reset(FALSE, TRUE);
7733     if (*appData.loadGameFile != NULLCHAR) {
7734         index = appData.loadGameIndex;
7735         if(index < 0) { // [HGM] autoinc
7736             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7737             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7738         }
7739         LoadGameFromFile(appData.loadGameFile,
7740                          index,
7741                          appData.loadGameFile, FALSE);
7742     } else if (*appData.loadPositionFile != NULLCHAR) {
7743         index = appData.loadPositionIndex;
7744         if(index < 0) { // [HGM] autoinc
7745             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7746             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7747         }
7748         LoadPositionFromFile(appData.loadPositionFile,
7749                              index,
7750                              appData.loadPositionFile);
7751     }
7752     TwoMachinesEventIfReady();
7753 }
7754
7755 void UserAdjudicationEvent( int result )
7756 {
7757     ChessMove gameResult = GameIsDrawn;
7758
7759     if( result > 0 ) {
7760         gameResult = WhiteWins;
7761     }
7762     else if( result < 0 ) {
7763         gameResult = BlackWins;
7764     }
7765
7766     if( gameMode == TwoMachinesPlay ) {
7767         GameEnds( gameResult, "User adjudication", GE_XBOARD );
7768     }
7769 }
7770
7771
7772 // [HGM] save: calculate checksum of game to make games easily identifiable
7773 int StringCheckSum(char *s)
7774 {
7775         int i = 0;
7776         if(s==NULL) return 0;
7777         while(*s) i = i*259 + *s++;
7778         return i;
7779 }
7780
7781 int GameCheckSum()
7782 {
7783         int i, sum=0;
7784         for(i=backwardMostMove; i<forwardMostMove; i++) {
7785                 sum += pvInfoList[i].depth;
7786                 sum += StringCheckSum(parseList[i]);
7787                 sum += StringCheckSum(commentList[i]);
7788                 sum *= 261;
7789         }
7790         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7791         return sum + StringCheckSum(commentList[i]);
7792 } // end of save patch
7793
7794 void
7795 GameEnds(result, resultDetails, whosays)
7796      ChessMove result;
7797      char *resultDetails;
7798      int whosays;
7799 {
7800     GameMode nextGameMode;
7801     int isIcsGame;
7802     char buf[MSG_SIZ];
7803
7804     if(endingGame) return; /* [HGM] crash: forbid recursion */
7805     endingGame = 1;
7806
7807     if (appData.debugMode) {
7808       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7809               result, resultDetails ? resultDetails : "(null)", whosays);
7810     }
7811
7812     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7813         /* If we are playing on ICS, the server decides when the
7814            game is over, but the engine can offer to draw, claim
7815            a draw, or resign.
7816          */
7817 #if ZIPPY
7818         if (appData.zippyPlay && first.initDone) {
7819             if (result == GameIsDrawn) {
7820                 /* In case draw still needs to be claimed */
7821                 SendToICS(ics_prefix);
7822                 SendToICS("draw\n");
7823             } else if (StrCaseStr(resultDetails, "resign")) {
7824                 SendToICS(ics_prefix);
7825                 SendToICS("resign\n");
7826             }
7827         }
7828 #endif
7829         endingGame = 0; /* [HGM] crash */
7830         return;
7831     }
7832
7833     /* If we're loading the game from a file, stop */
7834     if (whosays == GE_FILE) {
7835       (void) StopLoadGameTimer();
7836       gameFileFP = NULL;
7837     }
7838
7839     /* Cancel draw offers */
7840     first.offeredDraw = second.offeredDraw = 0;
7841
7842     /* If this is an ICS game, only ICS can really say it's done;
7843        if not, anyone can. */
7844     isIcsGame = (gameMode == IcsPlayingWhite ||
7845                  gameMode == IcsPlayingBlack ||
7846                  gameMode == IcsObserving    ||
7847                  gameMode == IcsExamining);
7848
7849     if (!isIcsGame || whosays == GE_ICS) {
7850         /* OK -- not an ICS game, or ICS said it was done */
7851         StopClocks();
7852         if (!isIcsGame && !appData.noChessProgram)
7853           SetUserThinkingEnables();
7854
7855         /* [HGM] if a machine claims the game end we verify this claim */
7856         if(gameMode == TwoMachinesPlay && appData.testClaims) {
7857             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7858                 char claimer;
7859                 ChessMove trueResult = (ChessMove) -1;
7860
7861                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
7862                                             first.twoMachinesColor[0] :
7863                                             second.twoMachinesColor[0] ;
7864
7865                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7866                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7867                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7868                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7869                 } else
7870                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7871                     /* [HGM] verify: engine mate claims accepted if they were flagged */
7872                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7873                 } else
7874                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7875                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7876                 }
7877
7878                 // now verify win claims, but not in drop games, as we don't understand those yet
7879                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7880                                                  || gameInfo.variant == VariantGreat) &&
7881                     (result == WhiteWins && claimer == 'w' ||
7882                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
7883                       if (appData.debugMode) {
7884                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
7885                                 result, epStatus[forwardMostMove], forwardMostMove);
7886                       }
7887                       if(result != trueResult) {
7888                               sprintf(buf, "False win claim: '%s'", resultDetails);
7889                               result = claimer == 'w' ? BlackWins : WhiteWins;
7890                               resultDetails = buf;
7891                       }
7892                 } else
7893                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7894                     && (forwardMostMove <= backwardMostMove ||
7895                         epStatus[forwardMostMove-1] > EP_DRAWS ||
7896                         (claimer=='b')==(forwardMostMove&1))
7897                                                                                   ) {
7898                       /* [HGM] verify: draws that were not flagged are false claims */
7899                       sprintf(buf, "False draw claim: '%s'", resultDetails);
7900                       result = claimer == 'w' ? BlackWins : WhiteWins;
7901                       resultDetails = buf;
7902                 }
7903                 /* (Claiming a loss is accepted no questions asked!) */
7904             }
7905
7906             /* [HGM] bare: don't allow bare King to win */
7907             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7908                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7909                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7910                && result != GameIsDrawn)
7911             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7912                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7913                         int p = (int)boards[forwardMostMove][i][j] - color;
7914                         if(p >= 0 && p <= (int)WhiteKing) k++;
7915                 }
7916                 if (appData.debugMode) {
7917                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7918                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7919                 }
7920                 if(k <= 1) {
7921                         result = GameIsDrawn;
7922                         sprintf(buf, "%s but bare king", resultDetails);
7923                         resultDetails = buf;
7924                 }
7925             }
7926         }
7927
7928         if(serverMoves != NULL && !loadFlag) { char c = '=';
7929             if(result==WhiteWins) c = '+';
7930             if(result==BlackWins) c = '-';
7931             if(resultDetails != NULL)
7932                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7933         }
7934         if (resultDetails != NULL) {
7935             gameInfo.result = result;
7936             gameInfo.resultDetails = StrSave(resultDetails);
7937
7938             /* display last move only if game was not loaded from file */
7939             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7940                 DisplayMove(currentMove - 1);
7941
7942             if (forwardMostMove != 0) {
7943                 if (gameMode != PlayFromGameFile && gameMode != EditGame
7944                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7945                                                                 ) {
7946                     if (*appData.saveGameFile != NULLCHAR) {
7947                         SaveGameToFile(appData.saveGameFile, TRUE);
7948                     } else if (appData.autoSaveGames) {
7949                         AutoSaveGame();
7950                     }
7951                     if (*appData.savePositionFile != NULLCHAR) {
7952                         SavePositionToFile(appData.savePositionFile);
7953                     }
7954                 }
7955             }
7956
7957             /* Tell program how game ended in case it is learning */
7958             /* [HGM] Moved this to after saving the PGN, just in case */
7959             /* engine died and we got here through time loss. In that */
7960             /* case we will get a fatal error writing the pipe, which */
7961             /* would otherwise lose us the PGN.                       */
7962             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
7963             /* output during GameEnds should never be fatal anymore   */
7964             if (gameMode == MachinePlaysWhite ||
7965                 gameMode == MachinePlaysBlack ||
7966                 gameMode == TwoMachinesPlay ||
7967                 gameMode == IcsPlayingWhite ||
7968                 gameMode == IcsPlayingBlack ||
7969                 gameMode == BeginningOfGame) {
7970                 char buf[MSG_SIZ];
7971                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7972                         resultDetails);
7973                 if (first.pr != NoProc) {
7974                     SendToProgram(buf, &first);
7975                 }
7976                 if (second.pr != NoProc &&
7977                     gameMode == TwoMachinesPlay) {
7978                     SendToProgram(buf, &second);
7979                 }
7980             }
7981         }
7982
7983         if (appData.icsActive) {
7984             if (appData.quietPlay &&
7985                 (gameMode == IcsPlayingWhite ||
7986                  gameMode == IcsPlayingBlack)) {
7987                 SendToICS(ics_prefix);
7988                 SendToICS("set shout 1\n");
7989             }
7990             nextGameMode = IcsIdle;
7991             ics_user_moved = FALSE;
7992             /* clean up premove.  It's ugly when the game has ended and the
7993              * premove highlights are still on the board.
7994              */
7995             if (gotPremove) {
7996               gotPremove = FALSE;
7997               ClearPremoveHighlights();
7998               DrawPosition(FALSE, boards[currentMove]);
7999             }
8000             if (whosays == GE_ICS) {
8001                 switch (result) {
8002                 case WhiteWins:
8003                     if (gameMode == IcsPlayingWhite)
8004                         PlayIcsWinSound();
8005                     else if(gameMode == IcsPlayingBlack)
8006                         PlayIcsLossSound();
8007                     break;
8008                 case BlackWins:
8009                     if (gameMode == IcsPlayingBlack)
8010                         PlayIcsWinSound();
8011                     else if(gameMode == IcsPlayingWhite)
8012                         PlayIcsLossSound();
8013                     break;
8014                 case GameIsDrawn:
8015                     PlayIcsDrawSound();
8016                     break;
8017                 default:
8018                     PlayIcsUnfinishedSound();
8019                 }
8020             }
8021         } else if (gameMode == EditGame ||
8022                    gameMode == PlayFromGameFile ||
8023                    gameMode == AnalyzeMode ||
8024                    gameMode == AnalyzeFile) {
8025             nextGameMode = gameMode;
8026         } else {
8027             nextGameMode = EndOfGame;
8028         }
8029         pausing = FALSE;
8030         ModeHighlight();
8031     } else {
8032         nextGameMode = gameMode;
8033     }
8034
8035     if (appData.noChessProgram) {
8036         gameMode = nextGameMode;
8037         ModeHighlight();
8038         endingGame = 0; /* [HGM] crash */
8039         return;
8040     }
8041
8042     if (first.reuse) {
8043         /* Put first chess program into idle state */
8044         if (first.pr != NoProc &&
8045             (gameMode == MachinePlaysWhite ||
8046              gameMode == MachinePlaysBlack ||
8047              gameMode == TwoMachinesPlay ||
8048              gameMode == IcsPlayingWhite ||
8049              gameMode == IcsPlayingBlack ||
8050              gameMode == BeginningOfGame)) {
8051             SendToProgram("force\n", &first);
8052             if (first.usePing) {
8053               char buf[MSG_SIZ];
8054               sprintf(buf, "ping %d\n", ++first.lastPing);
8055               SendToProgram(buf, &first);
8056             }
8057         }
8058     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8059         /* Kill off first chess program */
8060         if (first.isr != NULL)
8061           RemoveInputSource(first.isr);
8062         first.isr = NULL;
8063
8064         if (first.pr != NoProc) {
8065             ExitAnalyzeMode();
8066             DoSleep( appData.delayBeforeQuit );
8067             SendToProgram("quit\n", &first);
8068             DoSleep( appData.delayAfterQuit );
8069             DestroyChildProcess(first.pr, first.useSigterm);
8070         }
8071         first.pr = NoProc;
8072     }
8073     if (second.reuse) {
8074         /* Put second chess program into idle state */
8075         if (second.pr != NoProc &&
8076             gameMode == TwoMachinesPlay) {
8077             SendToProgram("force\n", &second);
8078             if (second.usePing) {
8079               char buf[MSG_SIZ];
8080               sprintf(buf, "ping %d\n", ++second.lastPing);
8081               SendToProgram(buf, &second);
8082             }
8083         }
8084     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8085         /* Kill off second chess program */
8086         if (second.isr != NULL)
8087           RemoveInputSource(second.isr);
8088         second.isr = NULL;
8089
8090         if (second.pr != NoProc) {
8091             DoSleep( appData.delayBeforeQuit );
8092             SendToProgram("quit\n", &second);
8093             DoSleep( appData.delayAfterQuit );
8094             DestroyChildProcess(second.pr, second.useSigterm);
8095         }
8096         second.pr = NoProc;
8097     }
8098
8099     if (matchMode && gameMode == TwoMachinesPlay) {
8100         switch (result) {
8101         case WhiteWins:
8102           if (first.twoMachinesColor[0] == 'w') {
8103             first.matchWins++;
8104           } else {
8105             second.matchWins++;
8106           }
8107           break;
8108         case BlackWins:
8109           if (first.twoMachinesColor[0] == 'b') {
8110             first.matchWins++;
8111           } else {
8112             second.matchWins++;
8113           }
8114           break;
8115         default:
8116           break;
8117         }
8118         if (matchGame < appData.matchGames) {
8119             char *tmp;
8120             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8121                 tmp = first.twoMachinesColor;
8122                 first.twoMachinesColor = second.twoMachinesColor;
8123                 second.twoMachinesColor = tmp;
8124             }
8125             gameMode = nextGameMode;
8126             matchGame++;
8127             if(appData.matchPause>10000 || appData.matchPause<10)
8128                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8129             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8130             endingGame = 0; /* [HGM] crash */
8131             return;
8132         } else {
8133             char buf[MSG_SIZ];
8134             gameMode = nextGameMode;
8135             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8136                     first.tidy, second.tidy,
8137                     first.matchWins, second.matchWins,
8138                     appData.matchGames - (first.matchWins + second.matchWins));
8139             DisplayFatalError(buf, 0, 0);
8140         }
8141     }
8142     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8143         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8144       ExitAnalyzeMode();
8145     gameMode = nextGameMode;
8146     ModeHighlight();
8147     endingGame = 0;  /* [HGM] crash */
8148 }
8149
8150 /* Assumes program was just initialized (initString sent).
8151    Leaves program in force mode. */
8152 void
8153 FeedMovesToProgram(cps, upto)
8154      ChessProgramState *cps;
8155      int upto;
8156 {
8157     int i;
8158
8159     if (appData.debugMode)
8160       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8161               startedFromSetupPosition ? "position and " : "",
8162               backwardMostMove, upto, cps->which);
8163     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8164         // [HGM] variantswitch: make engine aware of new variant
8165         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8166                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8167         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8168         SendToProgram(buf, cps);
8169         currentlyInitializedVariant = gameInfo.variant;
8170     }
8171     SendToProgram("force\n", cps);
8172     if (startedFromSetupPosition) {
8173         SendBoard(cps, backwardMostMove);
8174     if (appData.debugMode) {
8175         fprintf(debugFP, "feedMoves\n");
8176     }
8177     }
8178     for (i = backwardMostMove; i < upto; i++) {
8179         SendMoveToProgram(i, cps);
8180     }
8181 }
8182
8183
8184 void
8185 ResurrectChessProgram()
8186 {
8187      /* The chess program may have exited.
8188         If so, restart it and feed it all the moves made so far. */
8189
8190     if (appData.noChessProgram || first.pr != NoProc) return;
8191
8192     StartChessProgram(&first);
8193     InitChessProgram(&first, FALSE);
8194     FeedMovesToProgram(&first, currentMove);
8195
8196     if (!first.sendTime) {
8197         /* can't tell gnuchess what its clock should read,
8198            so we bow to its notion. */
8199         ResetClocks();
8200         timeRemaining[0][currentMove] = whiteTimeRemaining;
8201         timeRemaining[1][currentMove] = blackTimeRemaining;
8202     }
8203
8204     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8205                 appData.icsEngineAnalyze) && first.analysisSupport) {
8206       SendToProgram("analyze\n", &first);
8207       first.analyzing = TRUE;
8208     }
8209 }
8210
8211 /*
8212  * Button procedures
8213  */
8214 void
8215 Reset(redraw, init)
8216      int redraw, init;
8217 {
8218     int i;
8219
8220     if (appData.debugMode) {
8221         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8222                 redraw, init, gameMode);
8223     }
8224     pausing = pauseExamInvalid = FALSE;
8225     startedFromSetupPosition = blackPlaysFirst = FALSE;
8226     firstMove = TRUE;
8227     whiteFlag = blackFlag = FALSE;
8228     userOfferedDraw = FALSE;
8229     hintRequested = bookRequested = FALSE;
8230     first.maybeThinking = FALSE;
8231     second.maybeThinking = FALSE;
8232     first.bookSuspend = FALSE; // [HGM] book
8233     second.bookSuspend = FALSE;
8234     thinkOutput[0] = NULLCHAR;
8235     lastHint[0] = NULLCHAR;
8236     ClearGameInfo(&gameInfo);
8237     gameInfo.variant = StringToVariant(appData.variant);
8238     ics_user_moved = ics_clock_paused = FALSE;
8239     ics_getting_history = H_FALSE;
8240     ics_gamenum = -1;
8241     white_holding[0] = black_holding[0] = NULLCHAR;
8242     ClearProgramStats();
8243     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8244
8245     ResetFrontEnd();
8246     ClearHighlights();
8247     flipView = appData.flipView;
8248     ClearPremoveHighlights();
8249     gotPremove = FALSE;
8250     alarmSounded = FALSE;
8251
8252     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8253     if(appData.serverMovesName != NULL) {
8254         /* [HGM] prepare to make moves file for broadcasting */
8255         clock_t t = clock();
8256         if(serverMoves != NULL) fclose(serverMoves);
8257         serverMoves = fopen(appData.serverMovesName, "r");
8258         if(serverMoves != NULL) {
8259             fclose(serverMoves);
8260             /* delay 15 sec before overwriting, so all clients can see end */
8261             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8262         }
8263         serverMoves = fopen(appData.serverMovesName, "w");
8264     }
8265
8266     ExitAnalyzeMode();
8267     gameMode = BeginningOfGame;
8268     ModeHighlight();
8269
8270     if(appData.icsActive) gameInfo.variant = VariantNormal;
8271     currentMove = forwardMostMove = backwardMostMove = 0;
8272     InitPosition(redraw);
8273     for (i = 0; i < MAX_MOVES; i++) {
8274         if (commentList[i] != NULL) {
8275             free(commentList[i]);
8276             commentList[i] = NULL;
8277         }
8278     }
8279
8280     ResetClocks();
8281     timeRemaining[0][0] = whiteTimeRemaining;
8282     timeRemaining[1][0] = blackTimeRemaining;
8283     if (first.pr == NULL) {
8284         StartChessProgram(&first);
8285     }
8286     if (init) {
8287             InitChessProgram(&first, startedFromSetupPosition);
8288     }
8289
8290     DisplayTitle("");
8291     DisplayMessage("", "");
8292     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8293     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8294     return;
8295 }
8296
8297 void
8298 AutoPlayGameLoop()
8299 {
8300     for (;;) {
8301         if (!AutoPlayOneMove())
8302           return;
8303         if (matchMode || appData.timeDelay == 0)
8304           continue;
8305         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8306           return;
8307         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8308         break;
8309     }
8310 }
8311
8312
8313 int
8314 AutoPlayOneMove()
8315 {
8316     int fromX, fromY, toX, toY;
8317
8318     if (appData.debugMode) {
8319       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8320     }
8321
8322     if (gameMode != PlayFromGameFile)
8323       return FALSE;
8324
8325     if (currentMove >= forwardMostMove) {
8326       gameMode = EditGame;
8327       ModeHighlight();
8328
8329       /* [AS] Clear current move marker at the end of a game */
8330       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8331
8332       return FALSE;
8333     }
8334
8335     toX = moveList[currentMove][2] - AAA;
8336     toY = moveList[currentMove][3] - ONE;
8337
8338     if (moveList[currentMove][1] == '@') {
8339         if (appData.highlightLastMove) {
8340             SetHighlights(-1, -1, toX, toY);
8341         }
8342     } else {
8343         fromX = moveList[currentMove][0] - AAA;
8344         fromY = moveList[currentMove][1] - ONE;
8345
8346         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8347
8348         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8349
8350         if (appData.highlightLastMove) {
8351             SetHighlights(fromX, fromY, toX, toY);
8352         }
8353     }
8354     DisplayMove(currentMove);
8355     SendMoveToProgram(currentMove++, &first);
8356     DisplayBothClocks();
8357     DrawPosition(FALSE, boards[currentMove]);
8358     // [HGM] PV info: always display, routine tests if empty
8359     DisplayComment(currentMove - 1, commentList[currentMove]);
8360     return TRUE;
8361 }
8362
8363
8364 int
8365 LoadGameOneMove(readAhead)
8366      ChessMove readAhead;
8367 {
8368     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8369     char promoChar = NULLCHAR;
8370     ChessMove moveType;
8371     char move[MSG_SIZ];
8372     char *p, *q;
8373
8374     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8375         gameMode != AnalyzeMode && gameMode != Training) {
8376         gameFileFP = NULL;
8377         return FALSE;
8378     }
8379
8380     yyboardindex = forwardMostMove;
8381     if (readAhead != (ChessMove)0) {
8382       moveType = readAhead;
8383     } else {
8384       if (gameFileFP == NULL)
8385           return FALSE;
8386       moveType = (ChessMove) yylex();
8387     }
8388
8389     done = FALSE;
8390     switch (moveType) {
8391       case Comment:
8392         if (appData.debugMode)
8393           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8394         p = yy_text;
8395         if (*p == '{' || *p == '[' || *p == '(') {
8396             p[strlen(p) - 1] = NULLCHAR;
8397             p++;
8398         }
8399
8400         /* append the comment but don't display it */
8401         while (*p == '\n') p++;
8402         AppendComment(currentMove, p);
8403         return TRUE;
8404
8405       case WhiteCapturesEnPassant:
8406       case BlackCapturesEnPassant:
8407       case WhitePromotionChancellor:
8408       case BlackPromotionChancellor:
8409       case WhitePromotionArchbishop:
8410       case BlackPromotionArchbishop:
8411       case WhitePromotionCentaur:
8412       case BlackPromotionCentaur:
8413       case WhitePromotionQueen:
8414       case BlackPromotionQueen:
8415       case WhitePromotionRook:
8416       case BlackPromotionRook:
8417       case WhitePromotionBishop:
8418       case BlackPromotionBishop:
8419       case WhitePromotionKnight:
8420       case BlackPromotionKnight:
8421       case WhitePromotionKing:
8422       case BlackPromotionKing:
8423       case NormalMove:
8424       case WhiteKingSideCastle:
8425       case WhiteQueenSideCastle:
8426       case BlackKingSideCastle:
8427       case BlackQueenSideCastle:
8428       case WhiteKingSideCastleWild:
8429       case WhiteQueenSideCastleWild:
8430       case BlackKingSideCastleWild:
8431       case BlackQueenSideCastleWild:
8432       /* PUSH Fabien */
8433       case WhiteHSideCastleFR:
8434       case WhiteASideCastleFR:
8435       case BlackHSideCastleFR:
8436       case BlackASideCastleFR:
8437       /* POP Fabien */
8438         if (appData.debugMode)
8439           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8440         fromX = currentMoveString[0] - AAA;
8441         fromY = currentMoveString[1] - ONE;
8442         toX = currentMoveString[2] - AAA;
8443         toY = currentMoveString[3] - ONE;
8444         promoChar = currentMoveString[4];
8445         break;
8446
8447       case WhiteDrop:
8448       case BlackDrop:
8449         if (appData.debugMode)
8450           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8451         fromX = moveType == WhiteDrop ?
8452           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8453         (int) CharToPiece(ToLower(currentMoveString[0]));
8454         fromY = DROP_RANK;
8455         toX = currentMoveString[2] - AAA;
8456         toY = currentMoveString[3] - ONE;
8457         break;
8458
8459       case WhiteWins:
8460       case BlackWins:
8461       case GameIsDrawn:
8462       case GameUnfinished:
8463         if (appData.debugMode)
8464           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8465         p = strchr(yy_text, '{');
8466         if (p == NULL) p = strchr(yy_text, '(');
8467         if (p == NULL) {
8468             p = yy_text;
8469             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8470         } else {
8471             q = strchr(p, *p == '{' ? '}' : ')');
8472             if (q != NULL) *q = NULLCHAR;
8473             p++;
8474         }
8475         GameEnds(moveType, p, GE_FILE);
8476         done = TRUE;
8477         if (cmailMsgLoaded) {
8478             ClearHighlights();
8479             flipView = WhiteOnMove(currentMove);
8480             if (moveType == GameUnfinished) flipView = !flipView;
8481             if (appData.debugMode)
8482               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8483         }
8484         break;
8485
8486       case (ChessMove) 0:       /* end of file */
8487         if (appData.debugMode)
8488           fprintf(debugFP, "Parser hit end of file\n");
8489         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8490                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8491           case MT_NONE:
8492           case MT_CHECK:
8493             break;
8494           case MT_CHECKMATE:
8495           case MT_STAINMATE:
8496             if (WhiteOnMove(currentMove)) {
8497                 GameEnds(BlackWins, "Black mates", GE_FILE);
8498             } else {
8499                 GameEnds(WhiteWins, "White mates", GE_FILE);
8500             }
8501             break;
8502           case MT_STALEMATE:
8503             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8504             break;
8505         }
8506         done = TRUE;
8507         break;
8508
8509       case MoveNumberOne:
8510         if (lastLoadGameStart == GNUChessGame) {
8511             /* GNUChessGames have numbers, but they aren't move numbers */
8512             if (appData.debugMode)
8513               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8514                       yy_text, (int) moveType);
8515             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8516         }
8517         /* else fall thru */
8518
8519       case XBoardGame:
8520       case GNUChessGame:
8521       case PGNTag:
8522         /* Reached start of next game in file */
8523         if (appData.debugMode)
8524           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8525         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8526                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8527           case MT_NONE:
8528           case MT_CHECK:
8529             break;
8530           case MT_CHECKMATE:
8531           case MT_STAINMATE:
8532             if (WhiteOnMove(currentMove)) {
8533                 GameEnds(BlackWins, "Black mates", GE_FILE);
8534             } else {
8535                 GameEnds(WhiteWins, "White mates", GE_FILE);
8536             }
8537             break;
8538           case MT_STALEMATE:
8539             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8540             break;
8541         }
8542         done = TRUE;
8543         break;
8544
8545       case PositionDiagram:     /* should not happen; ignore */
8546       case ElapsedTime:         /* ignore */
8547       case NAG:                 /* ignore */
8548         if (appData.debugMode)
8549           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8550                   yy_text, (int) moveType);
8551         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8552
8553       case IllegalMove:
8554         if (appData.testLegality) {
8555             if (appData.debugMode)
8556               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8557             sprintf(move, _("Illegal move: %d.%s%s"),
8558                     (forwardMostMove / 2) + 1,
8559                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8560             DisplayError(move, 0);
8561             done = TRUE;
8562         } else {
8563             if (appData.debugMode)
8564               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8565                       yy_text, currentMoveString);
8566             fromX = currentMoveString[0] - AAA;
8567             fromY = currentMoveString[1] - ONE;
8568             toX = currentMoveString[2] - AAA;
8569             toY = currentMoveString[3] - ONE;
8570             promoChar = currentMoveString[4];
8571         }
8572         break;
8573
8574       case AmbiguousMove:
8575         if (appData.debugMode)
8576           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8577         sprintf(move, _("Ambiguous move: %d.%s%s"),
8578                 (forwardMostMove / 2) + 1,
8579                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8580         DisplayError(move, 0);
8581         done = TRUE;
8582         break;
8583
8584       default:
8585       case ImpossibleMove:
8586         if (appData.debugMode)
8587           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8588         sprintf(move, _("Illegal move: %d.%s%s"),
8589                 (forwardMostMove / 2) + 1,
8590                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8591         DisplayError(move, 0);
8592         done = TRUE;
8593         break;
8594     }
8595
8596     if (done) {
8597         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8598             DrawPosition(FALSE, boards[currentMove]);
8599             DisplayBothClocks();
8600             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8601               DisplayComment(currentMove - 1, commentList[currentMove]);
8602         }
8603         (void) StopLoadGameTimer();
8604         gameFileFP = NULL;
8605         cmailOldMove = forwardMostMove;
8606         return FALSE;
8607     } else {
8608         /* currentMoveString is set as a side-effect of yylex */
8609         strcat(currentMoveString, "\n");
8610         strcpy(moveList[forwardMostMove], currentMoveString);
8611
8612         thinkOutput[0] = NULLCHAR;
8613         MakeMove(fromX, fromY, toX, toY, promoChar);
8614         currentMove = forwardMostMove;
8615         return TRUE;
8616     }
8617 }
8618
8619 /* Load the nth game from the given file */
8620 int
8621 LoadGameFromFile(filename, n, title, useList)
8622      char *filename;
8623      int n;
8624      char *title;
8625      /*Boolean*/ int useList;
8626 {
8627     FILE *f;
8628     char buf[MSG_SIZ];
8629
8630     if (strcmp(filename, "-") == 0) {
8631         f = stdin;
8632         title = "stdin";
8633     } else {
8634         f = fopen(filename, "rb");
8635         if (f == NULL) {
8636           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8637             DisplayError(buf, errno);
8638             return FALSE;
8639         }
8640     }
8641     if (fseek(f, 0, 0) == -1) {
8642         /* f is not seekable; probably a pipe */
8643         useList = FALSE;
8644     }
8645     if (useList && n == 0) {
8646         int error = GameListBuild(f);
8647         if (error) {
8648             DisplayError(_("Cannot build game list"), error);
8649         } else if (!ListEmpty(&gameList) &&
8650                    ((ListGame *) gameList.tailPred)->number > 1) {
8651           // TODO convert to GTK
8652           //        GameListPopUp(f, title);
8653             return TRUE;
8654         }
8655         GameListDestroy();
8656         n = 1;
8657     }
8658     if (n == 0) n = 1;
8659     return LoadGame(f, n, title, FALSE);
8660 }
8661
8662
8663 void
8664 MakeRegisteredMove()
8665 {
8666     int fromX, fromY, toX, toY;
8667     char promoChar;
8668     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8669         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8670           case CMAIL_MOVE:
8671           case CMAIL_DRAW:
8672             if (appData.debugMode)
8673               fprintf(debugFP, "Restoring %s for game %d\n",
8674                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8675
8676             thinkOutput[0] = NULLCHAR;
8677             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8678             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8679             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8680             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8681             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8682             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8683             MakeMove(fromX, fromY, toX, toY, promoChar);
8684             ShowMove(fromX, fromY, toX, toY);
8685
8686             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8687                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8688               case MT_NONE:
8689               case MT_CHECK:
8690                 break;
8691
8692               case MT_CHECKMATE:
8693               case MT_STAINMATE:
8694                 if (WhiteOnMove(currentMove)) {
8695                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8696                 } else {
8697                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8698                 }
8699                 break;
8700
8701               case MT_STALEMATE:
8702                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8703                 break;
8704             }
8705
8706             break;
8707
8708           case CMAIL_RESIGN:
8709             if (WhiteOnMove(currentMove)) {
8710                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8711             } else {
8712                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8713             }
8714             break;
8715
8716           case CMAIL_ACCEPT:
8717             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8718             break;
8719
8720           default:
8721             break;
8722         }
8723     }
8724
8725     return;
8726 }
8727
8728 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8729 int
8730 CmailLoadGame(f, gameNumber, title, useList)
8731      FILE *f;
8732      int gameNumber;
8733      char *title;
8734      int useList;
8735 {
8736     int retVal;
8737
8738     if (gameNumber > nCmailGames) {
8739         DisplayError(_("No more games in this message"), 0);
8740         return FALSE;
8741     }
8742     if (f == lastLoadGameFP) {
8743         int offset = gameNumber - lastLoadGameNumber;
8744         if (offset == 0) {
8745             cmailMsg[0] = NULLCHAR;
8746             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8747                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8748                 nCmailMovesRegistered--;
8749             }
8750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8751             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8752                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8753             }
8754         } else {
8755             if (! RegisterMove()) return FALSE;
8756         }
8757     }
8758
8759     retVal = LoadGame(f, gameNumber, title, useList);
8760
8761     /* Make move registered during previous look at this game, if any */
8762     MakeRegisteredMove();
8763
8764     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8765         commentList[currentMove]
8766           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8767         DisplayComment(currentMove - 1, commentList[currentMove]);
8768     }
8769
8770     return retVal;
8771 }
8772
8773 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8774 int
8775 ReloadGame(offset)
8776      int offset;
8777 {
8778     int gameNumber = lastLoadGameNumber + offset;
8779     if (lastLoadGameFP == NULL) {
8780         DisplayError(_("No game has been loaded yet"), 0);
8781         return FALSE;
8782     }
8783     if (gameNumber <= 0) {
8784         DisplayError(_("Can't back up any further"), 0);
8785         return FALSE;
8786     }
8787     if (cmailMsgLoaded) {
8788         return CmailLoadGame(lastLoadGameFP, gameNumber,
8789                              lastLoadGameTitle, lastLoadGameUseList);
8790     } else {
8791         return LoadGame(lastLoadGameFP, gameNumber,
8792                         lastLoadGameTitle, lastLoadGameUseList);
8793     }
8794 }
8795
8796
8797
8798 /* Load the nth game from open file f */
8799 int
8800 LoadGame(f, gameNumber, title, useList)
8801      FILE *f;
8802      int gameNumber;
8803      char *title;
8804      int useList;
8805 {
8806     ChessMove cm;
8807     char buf[MSG_SIZ];
8808     int gn = gameNumber;
8809     ListGame *lg = NULL;
8810     int numPGNTags = 0;
8811     int err;
8812     GameMode oldGameMode;
8813     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8814
8815     if (appData.debugMode)
8816         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8817
8818     if (gameMode == Training )
8819         SetTrainingModeOff();
8820
8821     oldGameMode = gameMode;
8822     if (gameMode != BeginningOfGame) 
8823       {
8824         Reset(FALSE, TRUE);
8825       };
8826
8827     gameFileFP = f;
8828     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
8829       {
8830         fclose(lastLoadGameFP);
8831       };
8832
8833     if (useList) 
8834       {
8835         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8836         
8837         if (lg) 
8838           {
8839             fseek(f, lg->offset, 0);
8840             GameListHighlight(gameNumber);
8841             gn = 1;
8842           }
8843         else 
8844           {
8845             DisplayError(_("Game number out of range"), 0);
8846             return FALSE;
8847           };
8848       } 
8849     else 
8850       {
8851         GameListDestroy();
8852         if (fseek(f, 0, 0) == -1) 
8853           {
8854             if (f == lastLoadGameFP ?
8855                 gameNumber == lastLoadGameNumber + 1 :
8856                 gameNumber == 1) 
8857               {
8858                 gn = 1;
8859               } 
8860             else 
8861               {
8862                 DisplayError(_("Can't seek on game file"), 0);
8863                 return FALSE;
8864               };
8865           };
8866       };
8867
8868     lastLoadGameFP      = f;
8869     lastLoadGameNumber  = gameNumber;
8870     strcpy(lastLoadGameTitle, title);
8871     lastLoadGameUseList = useList;
8872
8873     yynewfile(f);
8874
8875     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
8876       {
8877         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8878                  lg->gameInfo.black);
8879         DisplayTitle(buf);
8880       } 
8881     else if (*title != NULLCHAR) 
8882       {
8883         if (gameNumber > 1) 
8884           {
8885             sprintf(buf, "%s %d", title, gameNumber);
8886             DisplayTitle(buf);
8887           } 
8888         else 
8889           {
8890             DisplayTitle(title);
8891           };
8892       };
8893
8894     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
8895       {
8896         gameMode = PlayFromGameFile;
8897         ModeHighlight();
8898       };
8899
8900     currentMove = forwardMostMove = backwardMostMove = 0;
8901     CopyBoard(boards[0], initialPosition);
8902     StopClocks();
8903
8904     /*
8905      * Skip the first gn-1 games in the file.
8906      * Also skip over anything that precedes an identifiable
8907      * start of game marker, to avoid being confused by
8908      * garbage at the start of the file.  Currently
8909      * recognized start of game markers are the move number "1",
8910      * the pattern "gnuchess .* game", the pattern
8911      * "^[#;%] [^ ]* game file", and a PGN tag block.
8912      * A game that starts with one of the latter two patterns
8913      * will also have a move number 1, possibly
8914      * following a position diagram.
8915      * 5-4-02: Let's try being more lenient and allowing a game to
8916      * start with an unnumbered move.  Does that break anything?
8917      */
8918     cm = lastLoadGameStart = (ChessMove) 0;
8919     while (gn > 0) {
8920         yyboardindex = forwardMostMove;
8921         cm = (ChessMove) yylex();
8922         switch (cm) {
8923           case (ChessMove) 0:
8924             if (cmailMsgLoaded) {
8925                 nCmailGames = CMAIL_MAX_GAMES - gn;
8926             } else {
8927                 Reset(TRUE, TRUE);
8928                 DisplayError(_("Game not found in file"), 0);
8929             }
8930             return FALSE;
8931
8932           case GNUChessGame:
8933           case XBoardGame:
8934             gn--;
8935             lastLoadGameStart = cm;
8936             break;
8937
8938           case MoveNumberOne:
8939             switch (lastLoadGameStart) {
8940               case GNUChessGame:
8941               case XBoardGame:
8942               case PGNTag:
8943                 break;
8944               case MoveNumberOne:
8945               case (ChessMove) 0:
8946                 gn--;           /* count this game */
8947                 lastLoadGameStart = cm;
8948                 break;
8949               default:
8950                 /* impossible */
8951                 break;
8952             }
8953             break;
8954
8955           case PGNTag:
8956             switch (lastLoadGameStart) {
8957               case GNUChessGame:
8958               case PGNTag:
8959               case MoveNumberOne:
8960               case (ChessMove) 0:
8961                 gn--;           /* count this game */
8962                 lastLoadGameStart = cm;
8963                 break;
8964               case XBoardGame:
8965                 lastLoadGameStart = cm; /* game counted already */
8966                 break;
8967               default:
8968                 /* impossible */
8969                 break;
8970             }
8971             if (gn > 0) {
8972                 do {
8973                     yyboardindex = forwardMostMove;
8974                     cm = (ChessMove) yylex();
8975                 } while (cm == PGNTag || cm == Comment);
8976             }
8977             break;
8978
8979           case WhiteWins:
8980           case BlackWins:
8981           case GameIsDrawn:
8982             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8983                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
8984                     != CMAIL_OLD_RESULT) {
8985                     nCmailResults ++ ;
8986                     cmailResult[  CMAIL_MAX_GAMES
8987                                 - gn - 1] = CMAIL_OLD_RESULT;
8988                 }
8989             }
8990             break;
8991
8992           case NormalMove:
8993             /* Only a NormalMove can be at the start of a game
8994              * without a position diagram. */
8995             if (lastLoadGameStart == (ChessMove) 0) {
8996               gn--;
8997               lastLoadGameStart = MoveNumberOne;
8998             }
8999             break;
9000
9001           default:
9002             break;
9003         }
9004     }
9005
9006     if (appData.debugMode)
9007       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9008
9009     if (cm == XBoardGame) {
9010         /* Skip any header junk before position diagram and/or move 1 */
9011         for (;;) {
9012             yyboardindex = forwardMostMove;
9013             cm = (ChessMove) yylex();
9014
9015             if (cm == (ChessMove) 0 ||
9016                 cm == GNUChessGame || cm == XBoardGame) {
9017                 /* Empty game; pretend end-of-file and handle later */
9018                 cm = (ChessMove) 0;
9019                 break;
9020             }
9021
9022             if (cm == MoveNumberOne || cm == PositionDiagram ||
9023                 cm == PGNTag || cm == Comment)
9024               break;
9025         }
9026     } else if (cm == GNUChessGame) {
9027         if (gameInfo.event != NULL) {
9028             free(gameInfo.event);
9029         }
9030         gameInfo.event = StrSave(yy_text);
9031     }
9032
9033     startedFromSetupPosition = FALSE;
9034     while (cm == PGNTag) {
9035         if (appData.debugMode)
9036           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9037         err = ParsePGNTag(yy_text, &gameInfo);
9038         if (!err) numPGNTags++;
9039
9040         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9041         if(gameInfo.variant != oldVariant) {
9042             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9043             InitPosition(TRUE);
9044             oldVariant = gameInfo.variant;
9045             if (appData.debugMode)
9046               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9047         }
9048
9049
9050         if (gameInfo.fen != NULL) {
9051           Board initial_position;
9052           startedFromSetupPosition = TRUE;
9053           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9054             Reset(TRUE, TRUE);
9055             DisplayError(_("Bad FEN position in file"), 0);
9056             return FALSE;
9057           }
9058           CopyBoard(boards[0], initial_position);
9059           if (blackPlaysFirst) {
9060             currentMove = forwardMostMove = backwardMostMove = 1;
9061             CopyBoard(boards[1], initial_position);
9062             strcpy(moveList[0], "");
9063             strcpy(parseList[0], "");
9064             timeRemaining[0][1] = whiteTimeRemaining;
9065             timeRemaining[1][1] = blackTimeRemaining;
9066             if (commentList[0] != NULL) {
9067               commentList[1] = commentList[0];
9068               commentList[0] = NULL;
9069             }
9070           } else {
9071             currentMove = forwardMostMove = backwardMostMove = 0;
9072           }
9073           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9074           {   int i;
9075               initialRulePlies = FENrulePlies;
9076               epStatus[forwardMostMove] = FENepStatus;
9077               for( i=0; i< nrCastlingRights; i++ )
9078                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9079           }
9080           yyboardindex = forwardMostMove;
9081           free(gameInfo.fen);
9082           gameInfo.fen = NULL;
9083         }
9084
9085         yyboardindex = forwardMostMove;
9086         cm = (ChessMove) yylex();
9087
9088         /* Handle comments interspersed among the tags */
9089         while (cm == Comment) {
9090             char *p;
9091             if (appData.debugMode)
9092               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9093             p = yy_text;
9094             if (*p == '{' || *p == '[' || *p == '(') {
9095                 p[strlen(p) - 1] = NULLCHAR;
9096                 p++;
9097             }
9098             while (*p == '\n') p++;
9099             AppendComment(currentMove, p);
9100             yyboardindex = forwardMostMove;
9101             cm = (ChessMove) yylex();
9102         }
9103     }
9104
9105     /* don't rely on existence of Event tag since if game was
9106      * pasted from clipboard the Event tag may not exist
9107      */
9108     if (numPGNTags > 0){
9109         char *tags;
9110         if (gameInfo.variant == VariantNormal) {
9111           gameInfo.variant = StringToVariant(gameInfo.event);
9112         }
9113         if (!matchMode) {
9114           if( appData.autoDisplayTags ) {
9115             tags = PGNTags(&gameInfo);
9116             TagsPopUp(tags, CmailMsg());
9117             free(tags);
9118           }
9119         }
9120     } else {
9121         /* Make something up, but don't display it now */
9122         SetGameInfo();
9123         TagsPopDown();
9124     }
9125
9126     if (cm == PositionDiagram) {
9127         int i, j;
9128         char *p;
9129         Board initial_position;
9130
9131         if (appData.debugMode)
9132           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9133
9134         if (!startedFromSetupPosition) {
9135             p = yy_text;
9136             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9137               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9138                 switch (*p) {
9139                   case '[':
9140                   case '-':
9141                   case ' ':
9142                   case '\t':
9143                   case '\n':
9144                   case '\r':
9145                     break;
9146                   default:
9147                     initial_position[i][j++] = CharToPiece(*p);
9148                     break;
9149                 }
9150             while (*p == ' ' || *p == '\t' ||
9151                    *p == '\n' || *p == '\r') p++;
9152
9153             if (strncmp(p, "black", strlen("black"))==0)
9154               blackPlaysFirst = TRUE;
9155             else
9156               blackPlaysFirst = FALSE;
9157             startedFromSetupPosition = TRUE;
9158
9159             CopyBoard(boards[0], initial_position);
9160             if (blackPlaysFirst) {
9161                 currentMove = forwardMostMove = backwardMostMove = 1;
9162                 CopyBoard(boards[1], initial_position);
9163                 strcpy(moveList[0], "");
9164                 strcpy(parseList[0], "");
9165                 timeRemaining[0][1] = whiteTimeRemaining;
9166                 timeRemaining[1][1] = blackTimeRemaining;
9167                 if (commentList[0] != NULL) {
9168                     commentList[1] = commentList[0];
9169                     commentList[0] = NULL;
9170                 }
9171             } else {
9172                 currentMove = forwardMostMove = backwardMostMove = 0;
9173             }
9174         }
9175         yyboardindex = forwardMostMove;
9176         cm = (ChessMove) yylex();
9177     }
9178
9179     if (first.pr == NoProc) {
9180         StartChessProgram(&first);
9181     }
9182     InitChessProgram(&first, FALSE);
9183     SendToProgram("force\n", &first);
9184     if (startedFromSetupPosition) {
9185         SendBoard(&first, forwardMostMove);
9186     if (appData.debugMode) {
9187         fprintf(debugFP, "Load Game\n");
9188     }
9189         DisplayBothClocks();
9190     }
9191
9192     /* [HGM] server: flag to write setup moves in broadcast file as one */
9193     loadFlag = appData.suppressLoadMoves;
9194
9195     while (cm == Comment) {
9196         char *p;
9197         if (appData.debugMode)
9198           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9199         p = yy_text;
9200         if (*p == '{' || *p == '[' || *p == '(') {
9201             p[strlen(p) - 1] = NULLCHAR;
9202             p++;
9203         }
9204         while (*p == '\n') p++;
9205         AppendComment(currentMove, p);
9206         yyboardindex = forwardMostMove;
9207         cm = (ChessMove) yylex();
9208     }
9209
9210     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9211         cm == WhiteWins || cm == BlackWins ||
9212         cm == GameIsDrawn || cm == GameUnfinished) {
9213         DisplayMessage("", _("No moves in game"));
9214         if (cmailMsgLoaded) {
9215             if (appData.debugMode)
9216               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9217             ClearHighlights();
9218             flipView = FALSE;
9219         }
9220         DrawPosition(FALSE, boards[currentMove]);
9221         DisplayBothClocks();
9222         gameMode = EditGame;
9223         ModeHighlight();
9224         gameFileFP = NULL;
9225         cmailOldMove = 0;
9226         return TRUE;
9227     }
9228
9229     // [HGM] PV info: routine tests if comment empty
9230     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9231         DisplayComment(currentMove - 1, commentList[currentMove]);
9232     }
9233     if (!matchMode && appData.timeDelay != 0)
9234       DrawPosition(FALSE, boards[currentMove]);
9235
9236     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9237       programStats.ok_to_send = 1;
9238     }
9239
9240     /* if the first token after the PGN tags is a move
9241      * and not move number 1, retrieve it from the parser
9242      */
9243     if (cm != MoveNumberOne)
9244         LoadGameOneMove(cm);
9245
9246     /* load the remaining moves from the file */
9247     while (LoadGameOneMove((ChessMove)0)) {
9248       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9249       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9250     }
9251
9252     /* rewind to the start of the game */
9253     currentMove = backwardMostMove;
9254
9255     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9256
9257     if (oldGameMode == AnalyzeFile ||
9258         oldGameMode == AnalyzeMode) {
9259       AnalyzeFileEvent();
9260     }
9261
9262     if (matchMode || appData.timeDelay == 0) {
9263       ToEndEvent();
9264       gameMode = EditGame;
9265       ModeHighlight();
9266     } else if (appData.timeDelay > 0) {
9267       AutoPlayGameLoop();
9268     }
9269
9270     if (appData.debugMode)
9271         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9272
9273     loadFlag = 0; /* [HGM] true game starts */
9274     return TRUE;
9275 }
9276
9277 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9278 int
9279 ReloadPosition(offset)
9280      int offset;
9281 {
9282     int positionNumber = lastLoadPositionNumber + offset;
9283     if (lastLoadPositionFP == NULL) {
9284         DisplayError(_("No position has been loaded yet"), 0);
9285         return FALSE;
9286     }
9287     if (positionNumber <= 0) {
9288         DisplayError(_("Can't back up any further"), 0);
9289         return FALSE;
9290     }
9291     return LoadPosition(lastLoadPositionFP, positionNumber,
9292                         lastLoadPositionTitle);
9293 }
9294
9295 /* Load the nth position from the given file */
9296 int
9297 LoadPositionFromFile(filename, n, title)
9298      char *filename;
9299      int n;
9300      char *title;
9301 {
9302     FILE *f;
9303     char buf[MSG_SIZ];
9304
9305     if (strcmp(filename, "-") == 0) {
9306         return LoadPosition(stdin, n, "stdin");
9307     } else {
9308         f = fopen(filename, "rb");
9309         if (f == NULL) {
9310             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9311             DisplayError(buf, errno);
9312             return FALSE;
9313         } else {
9314             return LoadPosition(f, n, title);
9315         }
9316     }
9317 }
9318
9319 /* Load the nth position from the given open file, and close it */
9320 int
9321 LoadPosition(f, positionNumber, title)
9322      FILE *f;
9323      int positionNumber;
9324      char *title;
9325 {
9326     char *p, line[MSG_SIZ];
9327     Board initial_position;
9328     int i, j, fenMode, pn;
9329
9330     if (gameMode == Training )
9331         SetTrainingModeOff();
9332
9333     if (gameMode != BeginningOfGame) {
9334         Reset(FALSE, TRUE);
9335     }
9336     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9337         fclose(lastLoadPositionFP);
9338     }
9339     if (positionNumber == 0) positionNumber = 1;
9340     lastLoadPositionFP = f;
9341     lastLoadPositionNumber = positionNumber;
9342     strcpy(lastLoadPositionTitle, title);
9343     if (first.pr == NoProc) {
9344       StartChessProgram(&first);
9345       InitChessProgram(&first, FALSE);
9346     }
9347     pn = positionNumber;
9348     if (positionNumber < 0) {
9349         /* Negative position number means to seek to that byte offset */
9350         if (fseek(f, -positionNumber, 0) == -1) {
9351             DisplayError(_("Can't seek on position file"), 0);
9352             return FALSE;
9353         };
9354         pn = 1;
9355     } else {
9356         if (fseek(f, 0, 0) == -1) {
9357             if (f == lastLoadPositionFP ?
9358                 positionNumber == lastLoadPositionNumber + 1 :
9359                 positionNumber == 1) {
9360                 pn = 1;
9361             } else {
9362                 DisplayError(_("Can't seek on position file"), 0);
9363                 return FALSE;
9364             }
9365         }
9366     }
9367     /* See if this file is FEN or old-style xboard */
9368     if (fgets(line, MSG_SIZ, f) == NULL) {
9369         DisplayError(_("Position not found in file"), 0);
9370         return FALSE;
9371     }
9372     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9373     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9374
9375     if (pn >= 2) {
9376         if (fenMode || line[0] == '#') pn--;
9377         while (pn > 0) {
9378             /* skip positions before number pn */
9379             if (fgets(line, MSG_SIZ, f) == NULL) {
9380                 Reset(TRUE, TRUE);
9381                 DisplayError(_("Position not found in file"), 0);
9382                 return FALSE;
9383             }
9384             if (fenMode || line[0] == '#') pn--;
9385         }
9386     }
9387
9388     if (fenMode) {
9389         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9390             DisplayError(_("Bad FEN position in file"), 0);
9391             return FALSE;
9392         }
9393     } else {
9394         (void) fgets(line, MSG_SIZ, f);
9395         (void) fgets(line, MSG_SIZ, f);
9396
9397         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9398             (void) fgets(line, MSG_SIZ, f);
9399             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9400                 if (*p == ' ')
9401                   continue;
9402                 initial_position[i][j++] = CharToPiece(*p);
9403             }
9404         }
9405
9406         blackPlaysFirst = FALSE;
9407         if (!feof(f)) {
9408             (void) fgets(line, MSG_SIZ, f);
9409             if (strncmp(line, "black", strlen("black"))==0)
9410               blackPlaysFirst = TRUE;
9411         }
9412     }
9413     startedFromSetupPosition = TRUE;
9414
9415     SendToProgram("force\n", &first);
9416     CopyBoard(boards[0], initial_position);
9417     if (blackPlaysFirst) {
9418         currentMove = forwardMostMove = backwardMostMove = 1;
9419         strcpy(moveList[0], "");
9420         strcpy(parseList[0], "");
9421         CopyBoard(boards[1], initial_position);
9422         DisplayMessage("", _("Black to play"));
9423     } else {
9424         currentMove = forwardMostMove = backwardMostMove = 0;
9425         DisplayMessage("", _("White to play"));
9426     }
9427           /* [HGM] copy FEN attributes as well */
9428           {   int i;
9429               initialRulePlies = FENrulePlies;
9430               epStatus[forwardMostMove] = FENepStatus;
9431               for( i=0; i< nrCastlingRights; i++ )
9432                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9433           }
9434     SendBoard(&first, forwardMostMove);
9435     if (appData.debugMode) {
9436 int i, j;
9437   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9438   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9439         fprintf(debugFP, "Load Position\n");
9440     }
9441
9442     if (positionNumber > 1) {
9443         sprintf(line, "%s %d", title, positionNumber);
9444         DisplayTitle(line);
9445     } else {
9446         DisplayTitle(title);
9447     }
9448     gameMode = EditGame;
9449     ModeHighlight();
9450     ResetClocks();
9451     timeRemaining[0][1] = whiteTimeRemaining;
9452     timeRemaining[1][1] = blackTimeRemaining;
9453     DrawPosition(FALSE, boards[currentMove]);
9454
9455     return TRUE;
9456 }
9457
9458
9459 void
9460 CopyPlayerNameIntoFileName(dest, src)
9461      char **dest, *src;
9462 {
9463     while (*src != NULLCHAR && *src != ',') {
9464         if (*src == ' ') {
9465             *(*dest)++ = '_';
9466             src++;
9467         } else {
9468             *(*dest)++ = *src++;
9469         }
9470     }
9471 }
9472
9473 char *DefaultFileName(ext)
9474      char *ext;
9475 {
9476     static char def[MSG_SIZ];
9477     char *p;
9478
9479     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9480         p = def;
9481         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9482         *p++ = '-';
9483         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9484         *p++ = '.';
9485         strcpy(p, ext);
9486     } else {
9487         def[0] = NULLCHAR;
9488     }
9489     return def;
9490 }
9491
9492 /* Save the current game to the given file */
9493 int
9494 SaveGameToFile(filename, append)
9495      char *filename;
9496      int append;
9497 {
9498     FILE *f;
9499     char buf[MSG_SIZ];
9500
9501     if (strcmp(filename, "-") == 0) {
9502         return SaveGame(stdout, 0, NULL);
9503     } else {
9504         f = fopen(filename, append ? "a" : "w");
9505         if (f == NULL) {
9506             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9507             DisplayError(buf, errno);
9508             return FALSE;
9509         } else {
9510             return SaveGame(f, 0, NULL);
9511         }
9512     }
9513 }
9514
9515 char *
9516 SavePart(str)
9517      char *str;
9518 {
9519     static char buf[MSG_SIZ];
9520     char *p;
9521
9522     p = strchr(str, ' ');
9523     if (p == NULL) return str;
9524     strncpy(buf, str, p - str);
9525     buf[p - str] = NULLCHAR;
9526     return buf;
9527 }
9528
9529 #define PGN_MAX_LINE 75
9530
9531 #define PGN_SIDE_WHITE  0
9532 #define PGN_SIDE_BLACK  1
9533
9534 /* [AS] */
9535 static int FindFirstMoveOutOfBook( int side )
9536 {
9537     int result = -1;
9538
9539     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9540         int index = backwardMostMove;
9541         int has_book_hit = 0;
9542
9543         if( (index % 2) != side ) {
9544             index++;
9545         }
9546
9547         while( index < forwardMostMove ) {
9548             /* Check to see if engine is in book */
9549             int depth = pvInfoList[index].depth;
9550             int score = pvInfoList[index].score;
9551             int in_book = 0;
9552
9553             if( depth <= 2 ) {
9554                 in_book = 1;
9555             }
9556             else if( score == 0 && depth == 63 ) {
9557                 in_book = 1; /* Zappa */
9558             }
9559             else if( score == 2 && depth == 99 ) {
9560                 in_book = 1; /* Abrok */
9561             }
9562
9563             has_book_hit += in_book;
9564
9565             if( ! in_book ) {
9566                 result = index;
9567
9568                 break;
9569             }
9570
9571             index += 2;
9572         }
9573     }
9574
9575     return result;
9576 }
9577
9578 /* [AS] */
9579 void GetOutOfBookInfo( char * buf )
9580 {
9581     int oob[2];
9582     int i;
9583     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9584
9585     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9586     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9587
9588     *buf = '\0';
9589
9590     if( oob[0] >= 0 || oob[1] >= 0 ) {
9591         for( i=0; i<2; i++ ) {
9592             int idx = oob[i];
9593
9594             if( idx >= 0 ) {
9595                 if( i > 0 && oob[0] >= 0 ) {
9596                     strcat( buf, "   " );
9597                 }
9598
9599                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9600                 sprintf( buf+strlen(buf), "%s%.2f",
9601                     pvInfoList[idx].score >= 0 ? "+" : "",
9602                     pvInfoList[idx].score / 100.0 );
9603             }
9604         }
9605     }
9606 }
9607
9608 /* Save game in PGN style and close the file */
9609 int
9610 SaveGamePGN(f)
9611      FILE *f;
9612 {
9613     int i, offset, linelen, newblock;
9614     time_t tm;
9615 //    char *movetext;
9616     char numtext[32];
9617     int movelen, numlen, blank;
9618     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9619
9620     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9621
9622     tm = time((time_t *) NULL);
9623
9624     PrintPGNTags(f, &gameInfo);
9625
9626     if (backwardMostMove > 0 || startedFromSetupPosition) {
9627         char *fen = PositionToFEN(backwardMostMove, NULL);
9628         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9629         fprintf(f, "\n{--------------\n");
9630         PrintPosition(f, backwardMostMove);
9631         fprintf(f, "--------------}\n");
9632         free(fen);
9633     }
9634     else {
9635         /* [AS] Out of book annotation */
9636         if( appData.saveOutOfBookInfo ) {
9637             char buf[64];
9638
9639             GetOutOfBookInfo( buf );
9640
9641             if( buf[0] != '\0' ) {
9642                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9643             }
9644         }
9645
9646         fprintf(f, "\n");
9647     }
9648
9649     i = backwardMostMove;
9650     linelen = 0;
9651     newblock = TRUE;
9652
9653     while (i < forwardMostMove) {
9654         /* Print comments preceding this move */
9655         if (commentList[i] != NULL) {
9656             if (linelen > 0) fprintf(f, "\n");
9657             fprintf(f, "{\n%s}\n", commentList[i]);
9658             linelen = 0;
9659             newblock = TRUE;
9660         }
9661
9662         /* Format move number */
9663         if ((i % 2) == 0) {
9664             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9665         } else {
9666             if (newblock) {
9667                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9668             } else {
9669                 numtext[0] = NULLCHAR;
9670             }
9671         }
9672         numlen = strlen(numtext);
9673         newblock = FALSE;
9674
9675         /* Print move number */
9676         blank = linelen > 0 && numlen > 0;
9677         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9678             fprintf(f, "\n");
9679             linelen = 0;
9680             blank = 0;
9681         }
9682         if (blank) {
9683             fprintf(f, " ");
9684             linelen++;
9685         }
9686         fprintf(f, "%s", numtext);
9687         linelen += numlen;
9688
9689         /* Get move */
9690         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9691         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9692
9693         /* Print move */
9694         blank = linelen > 0 && movelen > 0;
9695         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9696             fprintf(f, "\n");
9697             linelen = 0;
9698             blank = 0;
9699         }
9700         if (blank) {
9701             fprintf(f, " ");
9702             linelen++;
9703         }
9704         fprintf(f, "%s", move_buffer);
9705         linelen += movelen;
9706
9707         /* [AS] Add PV info if present */
9708         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9709             /* [HGM] add time */
9710             char buf[MSG_SIZ]; int seconds = 0;
9711
9712             if(i >= backwardMostMove) {
9713                 if(WhiteOnMove(i))
9714                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9715                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9716                 else
9717                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9718                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9719             }
9720             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9721
9722             if( seconds <= 0) buf[0] = 0; else
9723             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9724                 seconds = (seconds + 4)/10; // round to full seconds
9725                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9726                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9727             }
9728
9729             sprintf( move_buffer, "{%s%.2f/%d%s}",
9730                 pvInfoList[i].score >= 0 ? "+" : "",
9731                 pvInfoList[i].score / 100.0,
9732                 pvInfoList[i].depth,
9733                 buf );
9734
9735             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9736
9737             /* Print score/depth */
9738             blank = linelen > 0 && movelen > 0;
9739             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9740                 fprintf(f, "\n");
9741                 linelen = 0;
9742                 blank = 0;
9743             }
9744             if (blank) {
9745                 fprintf(f, " ");
9746                 linelen++;
9747             }
9748             fprintf(f, "%s", move_buffer);
9749             linelen += movelen;
9750         }
9751
9752         i++;
9753     }
9754
9755     /* Start a new line */
9756     if (linelen > 0) fprintf(f, "\n");
9757
9758     /* Print comments after last move */
9759     if (commentList[i] != NULL) {
9760         fprintf(f, "{\n%s}\n", commentList[i]);
9761     }
9762
9763     /* Print result */
9764     if (gameInfo.resultDetails != NULL &&
9765         gameInfo.resultDetails[0] != NULLCHAR) {
9766         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9767                 PGNResult(gameInfo.result));
9768     } else {
9769         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9770     }
9771
9772     fclose(f);
9773     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9774     return TRUE;
9775 }
9776
9777 /* Save game in old style and close the file */
9778 int
9779 SaveGameOldStyle(f)
9780      FILE *f;
9781 {
9782     int i, offset;
9783     time_t tm;
9784
9785     tm = time((time_t *) NULL);
9786
9787     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9788     PrintOpponents(f);
9789
9790     if (backwardMostMove > 0 || startedFromSetupPosition) {
9791         fprintf(f, "\n[--------------\n");
9792         PrintPosition(f, backwardMostMove);
9793         fprintf(f, "--------------]\n");
9794     } else {
9795         fprintf(f, "\n");
9796     }
9797
9798     i = backwardMostMove;
9799     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9800
9801     while (i < forwardMostMove) {
9802         if (commentList[i] != NULL) {
9803             fprintf(f, "[%s]\n", commentList[i]);
9804         }
9805
9806         if ((i % 2) == 1) {
9807             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
9808             i++;
9809         } else {
9810             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
9811             i++;
9812             if (commentList[i] != NULL) {
9813                 fprintf(f, "\n");
9814                 continue;
9815             }
9816             if (i >= forwardMostMove) {
9817                 fprintf(f, "\n");
9818                 break;
9819             }
9820             fprintf(f, "%s\n", parseList[i]);
9821             i++;
9822         }
9823     }
9824
9825     if (commentList[i] != NULL) {
9826         fprintf(f, "[%s]\n", commentList[i]);
9827     }
9828
9829     /* This isn't really the old style, but it's close enough */
9830     if (gameInfo.resultDetails != NULL &&
9831         gameInfo.resultDetails[0] != NULLCHAR) {
9832         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9833                 gameInfo.resultDetails);
9834     } else {
9835         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9836     }
9837
9838     fclose(f);
9839     return TRUE;
9840 }
9841
9842 /* Save the current game to open file f and close the file */
9843 int
9844 SaveGame(f, dummy, dummy2)
9845      FILE *f;
9846      int dummy;
9847      char *dummy2;
9848 {
9849     if (gameMode == EditPosition) EditPositionDone();
9850     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9851     if (appData.oldSaveStyle)
9852       return SaveGameOldStyle(f);
9853     else
9854       return SaveGamePGN(f);
9855 }
9856
9857 /* Save the current position to the given file */
9858 int
9859 SavePositionToFile(filename)
9860      char *filename;
9861 {
9862     FILE *f;
9863     char buf[MSG_SIZ];
9864
9865     if (strcmp(filename, "-") == 0) {
9866         return SavePosition(stdout, 0, NULL);
9867     } else {
9868         f = fopen(filename, "a");
9869         if (f == NULL) {
9870             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9871             DisplayError(buf, errno);
9872             return FALSE;
9873         } else {
9874             SavePosition(f, 0, NULL);
9875             return TRUE;
9876         }
9877     }
9878 }
9879
9880 /* Save the current position to the given open file and close the file */
9881 int
9882 SavePosition(f, dummy, dummy2)
9883      FILE *f;
9884      int dummy;
9885      char *dummy2;
9886 {
9887     time_t tm;
9888     char *fen;
9889
9890     if (appData.oldSaveStyle) {
9891         tm = time((time_t *) NULL);
9892
9893         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9894         PrintOpponents(f);
9895         fprintf(f, "[--------------\n");
9896         PrintPosition(f, currentMove);
9897         fprintf(f, "--------------]\n");
9898     } else {
9899         fen = PositionToFEN(currentMove, NULL);
9900         fprintf(f, "%s\n", fen);
9901         free(fen);
9902     }
9903     fclose(f);
9904     return TRUE;
9905 }
9906
9907 void
9908 ReloadCmailMsgEvent(unregister)
9909      int unregister;
9910 {
9911 #if !WIN32
9912     static char *inFilename = NULL;
9913     static char *outFilename;
9914     int i;
9915     struct stat inbuf, outbuf;
9916     int status;
9917
9918     /* Any registered moves are unregistered if unregister is set, */
9919     /* i.e. invoked by the signal handler */
9920     if (unregister) {
9921         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9922             cmailMoveRegistered[i] = FALSE;
9923             if (cmailCommentList[i] != NULL) {
9924                 free(cmailCommentList[i]);
9925                 cmailCommentList[i] = NULL;
9926             }
9927         }
9928         nCmailMovesRegistered = 0;
9929     }
9930
9931     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9932         cmailResult[i] = CMAIL_NOT_RESULT;
9933     }
9934     nCmailResults = 0;
9935
9936     if (inFilename == NULL) {
9937         /* Because the filenames are static they only get malloced once  */
9938         /* and they never get freed                                      */
9939         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9940         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9941
9942         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9943         sprintf(outFilename, "%s.out", appData.cmailGameName);
9944     }
9945
9946     status = stat(outFilename, &outbuf);
9947     if (status < 0) {
9948         cmailMailedMove = FALSE;
9949     } else {
9950         status = stat(inFilename, &inbuf);
9951         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9952     }
9953
9954     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9955        counts the games, notes how each one terminated, etc.
9956
9957        It would be nice to remove this kludge and instead gather all
9958        the information while building the game list.  (And to keep it
9959        in the game list nodes instead of having a bunch of fixed-size
9960        parallel arrays.)  Note this will require getting each game's
9961        termination from the PGN tags, as the game list builder does
9962        not process the game moves.  --mann
9963        */
9964     cmailMsgLoaded = TRUE;
9965     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9966
9967     /* Load first game in the file or popup game menu */
9968     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9969
9970 #endif /* !WIN32 */
9971     return;
9972 }
9973
9974 int
9975 RegisterMove()
9976 {
9977     FILE *f;
9978     char string[MSG_SIZ];
9979
9980     if (   cmailMailedMove
9981         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9982         return TRUE;            /* Allow free viewing  */
9983     }
9984
9985     /* Unregister move to ensure that we don't leave RegisterMove        */
9986     /* with the move registered when the conditions for registering no   */
9987     /* longer hold                                                       */
9988     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9989         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9990         nCmailMovesRegistered --;
9991
9992         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9993           {
9994               free(cmailCommentList[lastLoadGameNumber - 1]);
9995               cmailCommentList[lastLoadGameNumber - 1] = NULL;
9996           }
9997     }
9998
9999     if (cmailOldMove == -1) {
10000         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10001         return FALSE;
10002     }
10003
10004     if (currentMove > cmailOldMove + 1) {
10005         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10006         return FALSE;
10007     }
10008
10009     if (currentMove < cmailOldMove) {
10010         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10011         return FALSE;
10012     }
10013
10014     if (forwardMostMove > currentMove) {
10015         /* Silently truncate extra moves */
10016         TruncateGame();
10017     }
10018
10019     if (   (currentMove == cmailOldMove + 1)
10020         || (   (currentMove == cmailOldMove)
10021             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10022                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10023         if (gameInfo.result != GameUnfinished) {
10024             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10025         }
10026
10027         if (commentList[currentMove] != NULL) {
10028             cmailCommentList[lastLoadGameNumber - 1]
10029               = StrSave(commentList[currentMove]);
10030         }
10031         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10032
10033         if (appData.debugMode)
10034           fprintf(debugFP, "Saving %s for game %d\n",
10035                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10036
10037         sprintf(string,
10038                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10039
10040         f = fopen(string, "w");
10041         if (appData.oldSaveStyle) {
10042             SaveGameOldStyle(f); /* also closes the file */
10043
10044             sprintf(string, "%s.pos.out", appData.cmailGameName);
10045             f = fopen(string, "w");
10046             SavePosition(f, 0, NULL); /* also closes the file */
10047         } else {
10048             fprintf(f, "{--------------\n");
10049             PrintPosition(f, currentMove);
10050             fprintf(f, "--------------}\n\n");
10051
10052             SaveGame(f, 0, NULL); /* also closes the file*/
10053         }
10054
10055         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10056         nCmailMovesRegistered ++;
10057     } else if (nCmailGames == 1) {
10058         DisplayError(_("You have not made a move yet"), 0);
10059         return FALSE;
10060     }
10061
10062     return TRUE;
10063 }
10064
10065 void
10066 MailMoveEvent()
10067 {
10068 #if !WIN32
10069     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10070     FILE *commandOutput;
10071     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10072     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10073     int nBuffers;
10074     int i;
10075     int archived;
10076     char *arcDir;
10077
10078     if (! cmailMsgLoaded) {
10079         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10080         return;
10081     }
10082
10083     if (nCmailGames == nCmailResults) {
10084         DisplayError(_("No unfinished games"), 0);
10085         return;
10086     }
10087
10088 #if CMAIL_PROHIBIT_REMAIL
10089     if (cmailMailedMove) {
10090         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);
10091         DisplayError(msg, 0);
10092         return;
10093     }
10094 #endif
10095
10096     if (! (cmailMailedMove || RegisterMove())) return;
10097
10098     if (   cmailMailedMove
10099         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10100         sprintf(string, partCommandString,
10101                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10102         commandOutput = popen(string, "r");
10103
10104         if (commandOutput == NULL) {
10105             DisplayError(_("Failed to invoke cmail"), 0);
10106         } else {
10107             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10108                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10109             }
10110             if (nBuffers > 1) {
10111                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10112                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10113                 nBytes = MSG_SIZ - 1;
10114             } else {
10115                 (void) memcpy(msg, buffer, nBytes);
10116             }
10117             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10118
10119             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10120                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10121
10122                 archived = TRUE;
10123                 for (i = 0; i < nCmailGames; i ++) {
10124                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10125                         archived = FALSE;
10126                     }
10127                 }
10128                 if (   archived
10129                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10130                         != NULL)) {
10131                     sprintf(buffer, "%s/%s.%s.archive",
10132                             arcDir,
10133                             appData.cmailGameName,
10134                             gameInfo.date);
10135                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10136                     cmailMsgLoaded = FALSE;
10137                 }
10138             }
10139
10140             DisplayInformation(msg);
10141             pclose(commandOutput);
10142         }
10143     } else {
10144         if ((*cmailMsg) != '\0') {
10145             DisplayInformation(cmailMsg);
10146         }
10147     }
10148
10149     return;
10150 #endif /* !WIN32 */
10151 }
10152
10153 char *
10154 CmailMsg()
10155 {
10156 #if WIN32
10157     return NULL;
10158 #else
10159     int  prependComma = 0;
10160     char number[5];
10161     char string[MSG_SIZ];       /* Space for game-list */
10162     int  i;
10163
10164     if (!cmailMsgLoaded) return "";
10165
10166     if (cmailMailedMove) {
10167         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10168     } else {
10169         /* Create a list of games left */
10170         sprintf(string, "[");
10171         for (i = 0; i < nCmailGames; i ++) {
10172             if (! (   cmailMoveRegistered[i]
10173                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10174                 if (prependComma) {
10175                     sprintf(number, ",%d", i + 1);
10176                 } else {
10177                     sprintf(number, "%d", i + 1);
10178                     prependComma = 1;
10179                 }
10180
10181                 strcat(string, number);
10182             }
10183         }
10184         strcat(string, "]");
10185
10186         if (nCmailMovesRegistered + nCmailResults == 0) {
10187             switch (nCmailGames) {
10188               case 1:
10189                 sprintf(cmailMsg,
10190                         _("Still need to make move for game\n"));
10191                 break;
10192
10193               case 2:
10194                 sprintf(cmailMsg,
10195                         _("Still need to make moves for both games\n"));
10196                 break;
10197
10198               default:
10199                 sprintf(cmailMsg,
10200                         _("Still need to make moves for all %d games\n"),
10201                         nCmailGames);
10202                 break;
10203             }
10204         } else {
10205             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10206               case 1:
10207                 sprintf(cmailMsg,
10208                         _("Still need to make a move for game %s\n"),
10209                         string);
10210                 break;
10211
10212               case 0:
10213                 if (nCmailResults == nCmailGames) {
10214                     sprintf(cmailMsg, _("No unfinished games\n"));
10215                 } else {
10216                     sprintf(cmailMsg, _("Ready to send mail\n"));
10217                 }
10218                 break;
10219
10220               default:
10221                 sprintf(cmailMsg,
10222                         _("Still need to make moves for games %s\n"),
10223                         string);
10224             }
10225         }
10226     }
10227     return cmailMsg;
10228 #endif /* WIN32 */
10229 }
10230
10231 void
10232 ResetGameEvent()
10233 {
10234     if (gameMode == Training)
10235       SetTrainingModeOff();
10236
10237     Reset(TRUE, TRUE);
10238     cmailMsgLoaded = FALSE;
10239     if (appData.icsActive) {
10240       SendToICS(ics_prefix);
10241       SendToICS("refresh\n");
10242     }
10243 }
10244
10245 void
10246 ExitEvent(status)
10247      int status;
10248 {
10249     exiting++;
10250     if (exiting > 2) {
10251       /* Give up on clean exit */
10252       exit(status);
10253     }
10254     if (exiting > 1) {
10255       /* Keep trying for clean exit */
10256       return;
10257     }
10258
10259     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10260
10261     if (telnetISR != NULL) {
10262       RemoveInputSource(telnetISR);
10263     }
10264     if (icsPR != NoProc) {
10265       DestroyChildProcess(icsPR, TRUE);
10266     }
10267
10268     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10269     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10270
10271     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10272     /* make sure this other one finishes before killing it!                  */
10273     if(endingGame) { int count = 0;
10274         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10275         while(endingGame && count++ < 10) DoSleep(1);
10276         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10277     }
10278
10279     /* Kill off chess programs */
10280     if (first.pr != NoProc) {
10281         ExitAnalyzeMode();
10282
10283         DoSleep( appData.delayBeforeQuit );
10284         SendToProgram("quit\n", &first);
10285         DoSleep( appData.delayAfterQuit );
10286         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10287     }
10288     if (second.pr != NoProc) {
10289         DoSleep( appData.delayBeforeQuit );
10290         SendToProgram("quit\n", &second);
10291         DoSleep( appData.delayAfterQuit );
10292         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10293     }
10294     if (first.isr != NULL) {
10295         RemoveInputSource(first.isr);
10296     }
10297     if (second.isr != NULL) {
10298         RemoveInputSource(second.isr);
10299     }
10300
10301     ShutDownFrontEnd();
10302     exit(status);
10303 }
10304
10305 void
10306 PauseEvent()
10307 {
10308     if (appData.debugMode)
10309         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10310     if (pausing) {
10311         pausing = FALSE;
10312         ModeHighlight();
10313         if (gameMode == MachinePlaysWhite ||
10314             gameMode == MachinePlaysBlack) {
10315             StartClocks();
10316         } else {
10317             DisplayBothClocks();
10318         }
10319         if (gameMode == PlayFromGameFile) {
10320             if (appData.timeDelay >= 0)
10321                 AutoPlayGameLoop();
10322         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10323             Reset(FALSE, TRUE);
10324             SendToICS(ics_prefix);
10325             SendToICS("refresh\n");
10326         } else if (currentMove < forwardMostMove) {
10327             ForwardInner(forwardMostMove);
10328         }
10329         pauseExamInvalid = FALSE;
10330     } else {
10331         switch (gameMode) {
10332           default:
10333             return;
10334           case IcsExamining:
10335             pauseExamForwardMostMove = forwardMostMove;
10336             pauseExamInvalid = FALSE;
10337             /* fall through */
10338           case IcsObserving:
10339           case IcsPlayingWhite:
10340           case IcsPlayingBlack:
10341             pausing = TRUE;
10342             ModeHighlight();
10343             return;
10344           case PlayFromGameFile:
10345             (void) StopLoadGameTimer();
10346             pausing = TRUE;
10347             ModeHighlight();
10348             break;
10349           case BeginningOfGame:
10350             if (appData.icsActive) return;
10351             /* else fall through */
10352           case MachinePlaysWhite:
10353           case MachinePlaysBlack:
10354           case TwoMachinesPlay:
10355             if (forwardMostMove == 0)
10356               return;           /* don't pause if no one has moved */
10357             if ((gameMode == MachinePlaysWhite &&
10358                  !WhiteOnMove(forwardMostMove)) ||
10359                 (gameMode == MachinePlaysBlack &&
10360                  WhiteOnMove(forwardMostMove))) {
10361                 StopClocks();
10362             }
10363             pausing = TRUE;
10364             ModeHighlight();
10365             break;
10366         }
10367     }
10368 }
10369
10370 void
10371 EditCommentEvent()
10372 {
10373     char title[MSG_SIZ];
10374
10375     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10376         strcpy(title, _("Edit comment"));
10377     } else {
10378         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10379                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10380                 parseList[currentMove - 1]);
10381     }
10382
10383     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10384 }
10385
10386
10387 void
10388 EditTagsEvent()
10389 {
10390     char *tags = PGNTags(&gameInfo);
10391     EditTagsPopUp(tags);
10392     free(tags);
10393 }
10394
10395 void
10396 AnalyzeModeEvent()
10397 {
10398     if (appData.noChessProgram || gameMode == AnalyzeMode)
10399       return;
10400
10401     if (gameMode != AnalyzeFile) {
10402         if (!appData.icsEngineAnalyze) {
10403                EditGameEvent();
10404                if (gameMode != EditGame) return;
10405         }
10406         ResurrectChessProgram();
10407         SendToProgram("analyze\n", &first);
10408         first.analyzing = TRUE;
10409         /*first.maybeThinking = TRUE;*/
10410         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10411         EngineOutputPopUp();
10412     }
10413     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10414     pausing = FALSE;
10415     ModeHighlight();
10416     SetGameInfo();
10417
10418     StartAnalysisClock();
10419     GetTimeMark(&lastNodeCountTime);
10420     lastNodeCount = 0;
10421 }
10422
10423 void
10424 AnalyzeFileEvent()
10425 {
10426     if (appData.noChessProgram || gameMode == AnalyzeFile)
10427       return;
10428
10429     if (gameMode != AnalyzeMode) {
10430         EditGameEvent();
10431         if (gameMode != EditGame) return;
10432         ResurrectChessProgram();
10433         SendToProgram("analyze\n", &first);
10434         first.analyzing = TRUE;
10435         /*first.maybeThinking = TRUE;*/
10436         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10437         EngineOutputPopUp();
10438     }
10439     gameMode = AnalyzeFile;
10440     pausing = FALSE;
10441     ModeHighlight();
10442     SetGameInfo();
10443
10444     StartAnalysisClock();
10445     GetTimeMark(&lastNodeCountTime);
10446     lastNodeCount = 0;
10447 }
10448
10449 void
10450 MachineWhiteEvent()
10451 {
10452     char buf[MSG_SIZ];
10453     char *bookHit = NULL;
10454
10455     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10456       return;
10457
10458
10459     if (gameMode == PlayFromGameFile ||
10460         gameMode == TwoMachinesPlay  ||
10461         gameMode == Training         ||
10462         gameMode == AnalyzeMode      ||
10463         gameMode == EndOfGame)
10464         EditGameEvent();
10465
10466     if (gameMode == EditPosition)
10467         EditPositionDone();
10468
10469     if (!WhiteOnMove(currentMove)) {
10470         DisplayError(_("It is not White's turn"), 0);
10471         return;
10472     }
10473
10474     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10475       ExitAnalyzeMode();
10476
10477     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10478         gameMode == AnalyzeFile)
10479         TruncateGame();
10480
10481     ResurrectChessProgram();    /* in case it isn't running */
10482     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10483         gameMode = MachinePlaysWhite;
10484         ResetClocks();
10485     } else
10486     gameMode = MachinePlaysWhite;
10487     pausing = FALSE;
10488     ModeHighlight();
10489     SetGameInfo();
10490     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10491     DisplayTitle(buf);
10492     if (first.sendName) {
10493       sprintf(buf, "name %s\n", gameInfo.black);
10494       SendToProgram(buf, &first);
10495     }
10496     if (first.sendTime) {
10497       if (first.useColors) {
10498         SendToProgram("black\n", &first); /*gnu kludge*/
10499       }
10500       SendTimeRemaining(&first, TRUE);
10501     }
10502     if (first.useColors) {
10503       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10504     }
10505     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10506     SetMachineThinkingEnables();
10507     first.maybeThinking = TRUE;
10508     StartClocks();
10509     firstMove = FALSE;
10510
10511     if (appData.autoFlipView && !flipView) {
10512       flipView = !flipView;
10513       DrawPosition(FALSE, NULL);
10514       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10515     }
10516
10517     if(bookHit) { // [HGM] book: simulate book reply
10518         static char bookMove[MSG_SIZ]; // a bit generous?
10519
10520         programStats.nodes = programStats.depth = programStats.time =
10521         programStats.score = programStats.got_only_move = 0;
10522         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10523
10524         strcpy(bookMove, "move ");
10525         strcat(bookMove, bookHit);
10526         HandleMachineMove(bookMove, &first);
10527     }
10528 }
10529
10530 void
10531 MachineBlackEvent()
10532 {
10533     char buf[MSG_SIZ];
10534    char *bookHit = NULL;
10535
10536     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10537         return;
10538
10539
10540     if (gameMode == PlayFromGameFile ||
10541         gameMode == TwoMachinesPlay  ||
10542         gameMode == Training         ||
10543         gameMode == AnalyzeMode      ||
10544         gameMode == EndOfGame)
10545         EditGameEvent();
10546
10547     if (gameMode == EditPosition)
10548         EditPositionDone();
10549
10550     if (WhiteOnMove(currentMove)) {
10551         DisplayError(_("It is not Black's turn"), 0);
10552         return;
10553     }
10554
10555     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10556       ExitAnalyzeMode();
10557
10558     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10559         gameMode == AnalyzeFile)
10560         TruncateGame();
10561
10562     ResurrectChessProgram();    /* in case it isn't running */
10563     gameMode = MachinePlaysBlack;
10564     pausing = FALSE;
10565     ModeHighlight();
10566     SetGameInfo();
10567     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10568     DisplayTitle(buf);
10569     if (first.sendName) {
10570       sprintf(buf, "name %s\n", gameInfo.white);
10571       SendToProgram(buf, &first);
10572     }
10573     if (first.sendTime) {
10574       if (first.useColors) {
10575         SendToProgram("white\n", &first); /*gnu kludge*/
10576       }
10577       SendTimeRemaining(&first, FALSE);
10578     }
10579     if (first.useColors) {
10580       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10581     }
10582     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10583     SetMachineThinkingEnables();
10584     first.maybeThinking = TRUE;
10585     StartClocks();
10586
10587     if (appData.autoFlipView && flipView) {
10588       flipView = !flipView;
10589       DrawPosition(FALSE, NULL);
10590       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10591     }
10592     if(bookHit) { // [HGM] book: simulate book reply
10593         static char bookMove[MSG_SIZ]; // a bit generous?
10594
10595         programStats.nodes = programStats.depth = programStats.time =
10596         programStats.score = programStats.got_only_move = 0;
10597         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10598
10599         strcpy(bookMove, "move ");
10600         strcat(bookMove, bookHit);
10601         HandleMachineMove(bookMove, &first);
10602     }
10603 }
10604
10605
10606 void
10607 DisplayTwoMachinesTitle()
10608 {
10609     char buf[MSG_SIZ];
10610     if (appData.matchGames > 0) {
10611         if (first.twoMachinesColor[0] == 'w') {
10612             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10613                     gameInfo.white, gameInfo.black,
10614                     first.matchWins, second.matchWins,
10615                     matchGame - 1 - (first.matchWins + second.matchWins));
10616         } else {
10617             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10618                     gameInfo.white, gameInfo.black,
10619                     second.matchWins, first.matchWins,
10620                     matchGame - 1 - (first.matchWins + second.matchWins));
10621         }
10622     } else {
10623         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10624     }
10625     DisplayTitle(buf);
10626 }
10627
10628 void
10629 TwoMachinesEvent P((void))
10630 {
10631     int i;
10632     char buf[MSG_SIZ];
10633     ChessProgramState *onmove;
10634     char *bookHit = NULL;
10635
10636     if (appData.noChessProgram) return;
10637
10638     switch (gameMode) {
10639       case TwoMachinesPlay:
10640         return;
10641       case MachinePlaysWhite:
10642       case MachinePlaysBlack:
10643         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10644             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10645             return;
10646         }
10647         /* fall through */
10648       case BeginningOfGame:
10649       case PlayFromGameFile:
10650       case EndOfGame:
10651         EditGameEvent();
10652         if (gameMode != EditGame) return;
10653         break;
10654       case EditPosition:
10655         EditPositionDone();
10656         break;
10657       case AnalyzeMode:
10658       case AnalyzeFile:
10659         ExitAnalyzeMode();
10660         break;
10661       case EditGame:
10662       default:
10663         break;
10664     }
10665
10666     forwardMostMove = currentMove;
10667     ResurrectChessProgram();    /* in case first program isn't running */
10668
10669     if (second.pr == NULL) {
10670         StartChessProgram(&second);
10671         if (second.protocolVersion == 1) {
10672           TwoMachinesEventIfReady();
10673         } else {
10674           /* kludge: allow timeout for initial "feature" command */
10675           FreezeUI();
10676           DisplayMessage("", _("Starting second chess program"));
10677           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10678         }
10679         return;
10680     }
10681     DisplayMessage("", "");
10682     InitChessProgram(&second, FALSE);
10683     SendToProgram("force\n", &second);
10684     if (startedFromSetupPosition) {
10685         SendBoard(&second, backwardMostMove);
10686     if (appData.debugMode) {
10687         fprintf(debugFP, "Two Machines\n");
10688     }
10689     }
10690     for (i = backwardMostMove; i < forwardMostMove; i++) {
10691         SendMoveToProgram(i, &second);
10692     }
10693
10694     gameMode = TwoMachinesPlay;
10695     pausing = FALSE;
10696     ModeHighlight();
10697     SetGameInfo();
10698     DisplayTwoMachinesTitle();
10699     firstMove = TRUE;
10700     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10701         onmove = &first;
10702     } else {
10703         onmove = &second;
10704     }
10705
10706     SendToProgram(first.computerString, &first);
10707     if (first.sendName) {
10708       sprintf(buf, "name %s\n", second.tidy);
10709       SendToProgram(buf, &first);
10710     }
10711     SendToProgram(second.computerString, &second);
10712     if (second.sendName) {
10713       sprintf(buf, "name %s\n", first.tidy);
10714       SendToProgram(buf, &second);
10715     }
10716
10717     ResetClocks();
10718     if (!first.sendTime || !second.sendTime) {
10719         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10720         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10721     }
10722     if (onmove->sendTime) {
10723       if (onmove->useColors) {
10724         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10725       }
10726       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10727     }
10728     if (onmove->useColors) {
10729       SendToProgram(onmove->twoMachinesColor, onmove);
10730     }
10731     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10732 //    SendToProgram("go\n", onmove);
10733     onmove->maybeThinking = TRUE;
10734     SetMachineThinkingEnables();
10735
10736     StartClocks();
10737
10738     if(bookHit) { // [HGM] book: simulate book reply
10739         static char bookMove[MSG_SIZ]; // a bit generous?
10740
10741         programStats.nodes = programStats.depth = programStats.time =
10742         programStats.score = programStats.got_only_move = 0;
10743         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10744
10745         strcpy(bookMove, "move ");
10746         strcat(bookMove, bookHit);
10747         HandleMachineMove(bookMove, &first);
10748     }
10749 }
10750
10751 void
10752 TrainingEvent()
10753 {
10754     if (gameMode == Training) {
10755       SetTrainingModeOff();
10756       gameMode = PlayFromGameFile;
10757       DisplayMessage("", _("Training mode off"));
10758     } else {
10759       gameMode = Training;
10760       animateTraining = appData.animate;
10761
10762       /* make sure we are not already at the end of the game */
10763       if (currentMove < forwardMostMove) {
10764         SetTrainingModeOn();
10765         DisplayMessage("", _("Training mode on"));
10766       } else {
10767         gameMode = PlayFromGameFile;
10768         DisplayError(_("Already at end of game"), 0);
10769       }
10770     }
10771     ModeHighlight();
10772 }
10773
10774 void
10775 IcsClientEvent()
10776 {
10777     if (!appData.icsActive) return;
10778     switch (gameMode) {
10779       case IcsPlayingWhite:
10780       case IcsPlayingBlack:
10781       case IcsObserving:
10782       case IcsIdle:
10783       case BeginningOfGame:
10784       case IcsExamining:
10785         return;
10786
10787       case EditGame:
10788         break;
10789
10790       case EditPosition:
10791         EditPositionDone();
10792         break;
10793
10794       case AnalyzeMode:
10795       case AnalyzeFile:
10796         ExitAnalyzeMode();
10797         break;
10798
10799       default:
10800         EditGameEvent();
10801         break;
10802     }
10803
10804     gameMode = IcsIdle;
10805     ModeHighlight();
10806     return;
10807 }
10808
10809
10810 void
10811 EditGameEvent()
10812 {
10813     int i;
10814
10815     switch (gameMode) {
10816       case Training:
10817         SetTrainingModeOff();
10818         break;
10819       case MachinePlaysWhite:
10820       case MachinePlaysBlack:
10821       case BeginningOfGame:
10822         SendToProgram("force\n", &first);
10823         SetUserThinkingEnables();
10824         break;
10825       case PlayFromGameFile:
10826         (void) StopLoadGameTimer();
10827         if (gameFileFP != NULL) {
10828             gameFileFP = NULL;
10829         }
10830         break;
10831       case EditPosition:
10832         EditPositionDone();
10833         break;
10834       case AnalyzeMode:
10835       case AnalyzeFile:
10836         ExitAnalyzeMode();
10837         SendToProgram("force\n", &first);
10838         break;
10839       case TwoMachinesPlay:
10840         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10841         ResurrectChessProgram();
10842         SetUserThinkingEnables();
10843         break;
10844       case EndOfGame:
10845         ResurrectChessProgram();
10846         break;
10847       case IcsPlayingBlack:
10848       case IcsPlayingWhite:
10849         DisplayError(_("Warning: You are still playing a game"), 0);
10850         break;
10851       case IcsObserving:
10852         DisplayError(_("Warning: You are still observing a game"), 0);
10853         break;
10854       case IcsExamining:
10855         DisplayError(_("Warning: You are still examining a game"), 0);
10856         break;
10857       case IcsIdle:
10858         break;
10859       case EditGame:
10860       default:
10861         return;
10862     }
10863
10864     pausing = FALSE;
10865     StopClocks();
10866     first.offeredDraw = second.offeredDraw = 0;
10867
10868     if (gameMode == PlayFromGameFile) {
10869         whiteTimeRemaining = timeRemaining[0][currentMove];
10870         blackTimeRemaining = timeRemaining[1][currentMove];
10871         DisplayTitle("");
10872     }
10873
10874     if (gameMode == MachinePlaysWhite ||
10875         gameMode == MachinePlaysBlack ||
10876         gameMode == TwoMachinesPlay ||
10877         gameMode == EndOfGame) {
10878         i = forwardMostMove;
10879         while (i > currentMove) {
10880             SendToProgram("undo\n", &first);
10881             i--;
10882         }
10883         whiteTimeRemaining = timeRemaining[0][currentMove];
10884         blackTimeRemaining = timeRemaining[1][currentMove];
10885         DisplayBothClocks();
10886         if (whiteFlag || blackFlag) {
10887             whiteFlag = blackFlag = 0;
10888         }
10889         DisplayTitle("");
10890     }
10891
10892     gameMode = EditGame;
10893     ModeHighlight();
10894     SetGameInfo();
10895 }
10896
10897
10898 void
10899 EditPositionEvent()
10900 {
10901     if (gameMode == EditPosition) {
10902         EditGameEvent();
10903         return;
10904     }
10905
10906     EditGameEvent();
10907     if (gameMode != EditGame) return;
10908
10909     gameMode = EditPosition;
10910     ModeHighlight();
10911     SetGameInfo();
10912     if (currentMove > 0)
10913       CopyBoard(boards[0], boards[currentMove]);
10914
10915     blackPlaysFirst = !WhiteOnMove(currentMove);
10916     ResetClocks();
10917     currentMove = forwardMostMove = backwardMostMove = 0;
10918     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10919     DisplayMove(-1);
10920 }
10921
10922 void
10923 ExitAnalyzeMode()
10924 {
10925     /* [DM] icsEngineAnalyze - possible call from other functions */
10926     if (appData.icsEngineAnalyze) {
10927         appData.icsEngineAnalyze = FALSE;
10928
10929         DisplayMessage("",_("Close ICS engine analyze..."));
10930     }
10931     if (first.analysisSupport && first.analyzing) {
10932       SendToProgram("exit\n", &first);
10933       first.analyzing = FALSE;
10934     }
10935     EngineOutputPopDown();
10936     thinkOutput[0] = NULLCHAR;
10937 }
10938
10939 void
10940 EditPositionDone()
10941 {
10942     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10943
10944     startedFromSetupPosition = TRUE;
10945     InitChessProgram(&first, FALSE);
10946     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10947     if(boards[0][0][BOARD_WIDTH>>1] == king) {
10948         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10949         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10950     } else castlingRights[0][2] = -1;
10951     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10952         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10953         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10954     } else castlingRights[0][5] = -1;
10955     SendToProgram("force\n", &first);
10956     if (blackPlaysFirst) {
10957         strcpy(moveList[0], "");
10958         strcpy(parseList[0], "");
10959         currentMove = forwardMostMove = backwardMostMove = 1;
10960         CopyBoard(boards[1], boards[0]);
10961         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10962         { int i;
10963           epStatus[1] = epStatus[0];
10964           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10965         }
10966     } else {
10967         currentMove = forwardMostMove = backwardMostMove = 0;
10968     }
10969     SendBoard(&first, forwardMostMove);
10970     if (appData.debugMode) {
10971         fprintf(debugFP, "EditPosDone\n");
10972     }
10973     DisplayTitle("");
10974     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10975     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10976     gameMode = EditGame;
10977     ModeHighlight();
10978     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10979     ClearHighlights(); /* [AS] */
10980 }
10981
10982 /* Pause for `ms' milliseconds */
10983 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10984 void
10985 TimeDelay(ms)
10986      long ms;
10987 {
10988     TimeMark m1, m2;
10989
10990     GetTimeMark(&m1);
10991     do {
10992         GetTimeMark(&m2);
10993     } while (SubtractTimeMarks(&m2, &m1) < ms);
10994 }
10995
10996 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10997 void
10998 SendMultiLineToICS(buf)
10999      char *buf;
11000 {
11001     char temp[MSG_SIZ+1], *p;
11002     int len;
11003
11004     len = strlen(buf);
11005     if (len > MSG_SIZ)
11006       len = MSG_SIZ;
11007
11008     strncpy(temp, buf, len);
11009     temp[len] = 0;
11010
11011     p = temp;
11012     while (*p) {
11013         if (*p == '\n' || *p == '\r')
11014           *p = ' ';
11015         ++p;
11016     }
11017
11018     strcat(temp, "\n");
11019     SendToICS(temp);
11020     SendToPlayer(temp, strlen(temp));
11021 }
11022
11023 void
11024 SetWhiteToPlayEvent()
11025 {
11026     if (gameMode == EditPosition) {
11027         blackPlaysFirst = FALSE;
11028         DisplayBothClocks();    /* works because currentMove is 0 */
11029     } else if (gameMode == IcsExamining) {
11030         SendToICS(ics_prefix);
11031         SendToICS("tomove white\n");
11032     }
11033 }
11034
11035 void
11036 SetBlackToPlayEvent()
11037 {
11038     if (gameMode == EditPosition) {
11039         blackPlaysFirst = TRUE;
11040         currentMove = 1;        /* kludge */
11041         DisplayBothClocks();
11042         currentMove = 0;
11043     } else if (gameMode == IcsExamining) {
11044         SendToICS(ics_prefix);
11045         SendToICS("tomove black\n");
11046     }
11047 }
11048
11049 void
11050 EditPositionMenuEvent(selection, x, y)
11051      ChessSquare selection;
11052      int x, y;
11053 {
11054     char buf[MSG_SIZ];
11055     ChessSquare piece = boards[0][y][x];
11056
11057     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11058
11059     switch (selection) {
11060       case ClearBoard:
11061         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11062             SendToICS(ics_prefix);
11063             SendToICS("bsetup clear\n");
11064         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11065             SendToICS(ics_prefix);
11066             SendToICS("clearboard\n");
11067         } else {
11068             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11069                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11070                 for (y = 0; y < BOARD_HEIGHT; y++) {
11071                     if (gameMode == IcsExamining) {
11072                         if (boards[currentMove][y][x] != EmptySquare) {
11073                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11074                                     AAA + x, ONE + y);
11075                             SendToICS(buf);
11076                         }
11077                     } else {
11078                         boards[0][y][x] = p;
11079                     }
11080                 }
11081             }
11082         }
11083         if (gameMode == EditPosition) {
11084             DrawPosition(FALSE, boards[0]);
11085         }
11086         break;
11087
11088       case WhitePlay:
11089         SetWhiteToPlayEvent();
11090         break;
11091
11092       case BlackPlay:
11093         SetBlackToPlayEvent();
11094         break;
11095
11096       case EmptySquare:
11097         if (gameMode == IcsExamining) {
11098             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11099             SendToICS(buf);
11100         } else {
11101             boards[0][y][x] = EmptySquare;
11102             DrawPosition(FALSE, boards[0]);
11103         }
11104         break;
11105
11106       case PromotePiece:
11107         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11108            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11109             selection = (ChessSquare) (PROMOTED piece);
11110         } else if(piece == EmptySquare) selection = WhiteSilver;
11111         else selection = (ChessSquare)((int)piece - 1);
11112         goto defaultlabel;
11113
11114       case DemotePiece:
11115         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11116            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11117             selection = (ChessSquare) (DEMOTED piece);
11118         } else if(piece == EmptySquare) selection = BlackSilver;
11119         else selection = (ChessSquare)((int)piece + 1);
11120         goto defaultlabel;
11121
11122       case WhiteQueen:
11123       case BlackQueen:
11124         if(gameInfo.variant == VariantShatranj ||
11125            gameInfo.variant == VariantXiangqi  ||
11126            gameInfo.variant == VariantCourier    )
11127             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11128         goto defaultlabel;
11129
11130       case WhiteKing:
11131       case BlackKing:
11132         if(gameInfo.variant == VariantXiangqi)
11133             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11134         if(gameInfo.variant == VariantKnightmate)
11135             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11136       default:
11137         defaultlabel:
11138         if (gameMode == IcsExamining) {
11139             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11140                     PieceToChar(selection), AAA + x, ONE + y);
11141             SendToICS(buf);
11142         } else {
11143             boards[0][y][x] = selection;
11144             DrawPosition(FALSE, boards[0]);
11145         }
11146         break;
11147     }
11148 }
11149
11150
11151 void
11152 DropMenuEvent(selection, x, y)
11153      ChessSquare selection;
11154      int x, y;
11155 {
11156     ChessMove moveType;
11157
11158     switch (gameMode) {
11159       case IcsPlayingWhite:
11160       case MachinePlaysBlack:
11161         if (!WhiteOnMove(currentMove)) {
11162             DisplayMoveError(_("It is Black's turn"));
11163             return;
11164         }
11165         moveType = WhiteDrop;
11166         break;
11167       case IcsPlayingBlack:
11168       case MachinePlaysWhite:
11169         if (WhiteOnMove(currentMove)) {
11170             DisplayMoveError(_("It is White's turn"));
11171             return;
11172         }
11173         moveType = BlackDrop;
11174         break;
11175       case EditGame:
11176         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11177         break;
11178       default:
11179         return;
11180     }
11181
11182     if (moveType == BlackDrop && selection < BlackPawn) {
11183       selection = (ChessSquare) ((int) selection
11184                                  + (int) BlackPawn - (int) WhitePawn);
11185     }
11186     if (boards[currentMove][y][x] != EmptySquare) {
11187         DisplayMoveError(_("That square is occupied"));
11188         return;
11189     }
11190
11191     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11192 }
11193
11194 void
11195 AcceptEvent()
11196 {
11197     /* Accept a pending offer of any kind from opponent */
11198
11199     if (appData.icsActive) {
11200         SendToICS(ics_prefix);
11201         SendToICS("accept\n");
11202     } else if (cmailMsgLoaded) {
11203         if (currentMove == cmailOldMove &&
11204             commentList[cmailOldMove] != NULL &&
11205             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11206                    "Black offers a draw" : "White offers a draw")) {
11207             TruncateGame();
11208             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11209             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11210         } else {
11211             DisplayError(_("There is no pending offer on this move"), 0);
11212             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11213         }
11214     } else {
11215         /* Not used for offers from chess program */
11216     }
11217 }
11218
11219 void
11220 DeclineEvent()
11221 {
11222     /* Decline a pending offer of any kind from opponent */
11223
11224     if (appData.icsActive) {
11225         SendToICS(ics_prefix);
11226         SendToICS("decline\n");
11227     } else if (cmailMsgLoaded) {
11228         if (currentMove == cmailOldMove &&
11229             commentList[cmailOldMove] != NULL &&
11230             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11231                    "Black offers a draw" : "White offers a draw")) {
11232 #ifdef NOTDEF
11233             AppendComment(cmailOldMove, "Draw declined");
11234             DisplayComment(cmailOldMove - 1, "Draw declined");
11235 #endif /*NOTDEF*/
11236         } else {
11237             DisplayError(_("There is no pending offer on this move"), 0);
11238         }
11239     } else {
11240         /* Not used for offers from chess program */
11241     }
11242 }
11243
11244 void
11245 RematchEvent()
11246 {
11247     /* Issue ICS rematch command */
11248     if (appData.icsActive) {
11249         SendToICS(ics_prefix);
11250         SendToICS("rematch\n");
11251     }
11252 }
11253
11254 void
11255 CallFlagEvent()
11256 {
11257     /* Call your opponent's flag (claim a win on time) */
11258     if (appData.icsActive) {
11259         SendToICS(ics_prefix);
11260         SendToICS("flag\n");
11261     } else {
11262         switch (gameMode) {
11263           default:
11264             return;
11265           case MachinePlaysWhite:
11266             if (whiteFlag) {
11267                 if (blackFlag)
11268                   GameEnds(GameIsDrawn, "Both players ran out of time",
11269                            GE_PLAYER);
11270                 else
11271                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11272             } else {
11273                 DisplayError(_("Your opponent is not out of time"), 0);
11274             }
11275             break;
11276           case MachinePlaysBlack:
11277             if (blackFlag) {
11278                 if (whiteFlag)
11279                   GameEnds(GameIsDrawn, "Both players ran out of time",
11280                            GE_PLAYER);
11281                 else
11282                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11283             } else {
11284                 DisplayError(_("Your opponent is not out of time"), 0);
11285             }
11286             break;
11287         }
11288     }
11289 }
11290
11291 void
11292 DrawEvent()
11293 {
11294     /* Offer draw or accept pending draw offer from opponent */
11295
11296     if (appData.icsActive) {
11297         /* Note: tournament rules require draw offers to be
11298            made after you make your move but before you punch
11299            your clock.  Currently ICS doesn't let you do that;
11300            instead, you immediately punch your clock after making
11301            a move, but you can offer a draw at any time. */
11302
11303         SendToICS(ics_prefix);
11304         SendToICS("draw\n");
11305     } else if (cmailMsgLoaded) {
11306         if (currentMove == cmailOldMove &&
11307             commentList[cmailOldMove] != NULL &&
11308             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11309                    "Black offers a draw" : "White offers a draw")) {
11310             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11311             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11312         } else if (currentMove == cmailOldMove + 1) {
11313             char *offer = WhiteOnMove(cmailOldMove) ?
11314               "White offers a draw" : "Black offers a draw";
11315             AppendComment(currentMove, offer);
11316             DisplayComment(currentMove - 1, offer);
11317             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11318         } else {
11319             DisplayError(_("You must make your move before offering a draw"), 0);
11320             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11321         }
11322     } else if (first.offeredDraw) {
11323         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11324     } else {
11325         if (first.sendDrawOffers) {
11326             SendToProgram("draw\n", &first);
11327             userOfferedDraw = TRUE;
11328         }
11329     }
11330 }
11331
11332 void
11333 AdjournEvent()
11334 {
11335     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11336
11337     if (appData.icsActive) {
11338         SendToICS(ics_prefix);
11339         SendToICS("adjourn\n");
11340     } else {
11341         /* Currently GNU Chess doesn't offer or accept Adjourns */
11342     }
11343 }
11344
11345
11346 void
11347 AbortEvent()
11348 {
11349     /* Offer Abort or accept pending Abort offer from opponent */
11350
11351     if (appData.icsActive) {
11352         SendToICS(ics_prefix);
11353         SendToICS("abort\n");
11354     } else {
11355         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11356     }
11357 }
11358
11359 void
11360 ResignEvent()
11361 {
11362     /* Resign.  You can do this even if it's not your turn. */
11363
11364     if (appData.icsActive) {
11365         SendToICS(ics_prefix);
11366         SendToICS("resign\n");
11367     } else {
11368         switch (gameMode) {
11369           case MachinePlaysWhite:
11370             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11371             break;
11372           case MachinePlaysBlack:
11373             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11374             break;
11375           case EditGame:
11376             if (cmailMsgLoaded) {
11377                 TruncateGame();
11378                 if (WhiteOnMove(cmailOldMove)) {
11379                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11380                 } else {
11381                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11382                 }
11383                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11384             }
11385             break;
11386           default:
11387             break;
11388         }
11389     }
11390 }
11391
11392
11393 void
11394 StopObservingEvent()
11395 {
11396     /* Stop observing current games */
11397     SendToICS(ics_prefix);
11398     SendToICS("unobserve\n");
11399 }
11400
11401 void
11402 StopExaminingEvent()
11403 {
11404     /* Stop observing current game */
11405     SendToICS(ics_prefix);
11406     SendToICS("unexamine\n");
11407 }
11408
11409 void
11410 ForwardInner(target)
11411      int target;
11412 {
11413     int limit;
11414
11415     if (appData.debugMode)
11416         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11417                 target, currentMove, forwardMostMove);
11418
11419     if (gameMode == EditPosition)
11420       return;
11421
11422     if (gameMode == PlayFromGameFile && !pausing)
11423       PauseEvent();
11424
11425     if (gameMode == IcsExamining && pausing)
11426       limit = pauseExamForwardMostMove;
11427     else
11428       limit = forwardMostMove;
11429
11430     if (target > limit) target = limit;
11431
11432     if (target > 0 && moveList[target - 1][0]) {
11433         int fromX, fromY, toX, toY;
11434         toX = moveList[target - 1][2] - AAA;
11435         toY = moveList[target - 1][3] - ONE;
11436         if (moveList[target - 1][1] == '@') {
11437             if (appData.highlightLastMove) {
11438                 SetHighlights(-1, -1, toX, toY);
11439             }
11440         } else {
11441             fromX = moveList[target - 1][0] - AAA;
11442             fromY = moveList[target - 1][1] - ONE;
11443             if (target == currentMove + 1) {
11444                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11445             }
11446             if (appData.highlightLastMove) {
11447                 SetHighlights(fromX, fromY, toX, toY);
11448             }
11449         }
11450     }
11451     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11452         gameMode == Training || gameMode == PlayFromGameFile ||
11453         gameMode == AnalyzeFile) {
11454         while (currentMove < target) {
11455             SendMoveToProgram(currentMove++, &first);
11456         }
11457     } else {
11458         currentMove = target;
11459     }
11460
11461     if (gameMode == EditGame || gameMode == EndOfGame) {
11462         whiteTimeRemaining = timeRemaining[0][currentMove];
11463         blackTimeRemaining = timeRemaining[1][currentMove];
11464     }
11465     DisplayBothClocks();
11466     DisplayMove(currentMove - 1);
11467     DrawPosition(FALSE, boards[currentMove]);
11468     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11469     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11470         DisplayComment(currentMove - 1, commentList[currentMove]);
11471     }
11472 }
11473
11474
11475 void
11476 ForwardEvent()
11477 {
11478     if (gameMode == IcsExamining && !pausing) {
11479         SendToICS(ics_prefix);
11480         SendToICS("forward\n");
11481     } else {
11482         ForwardInner(currentMove + 1);
11483     }
11484 }
11485
11486 void
11487 ToEndEvent()
11488 {
11489     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11490         /* to optimze, we temporarily turn off analysis mode while we feed
11491          * the remaining moves to the engine. Otherwise we get analysis output
11492          * after each move.
11493          */
11494         if (first.analysisSupport) {
11495           SendToProgram("exit\nforce\n", &first);
11496           first.analyzing = FALSE;
11497         }
11498     }
11499
11500     if (gameMode == IcsExamining && !pausing) {
11501         SendToICS(ics_prefix);
11502         SendToICS("forward 999999\n");
11503     } else {
11504         ForwardInner(forwardMostMove);
11505     }
11506
11507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11508         /* we have fed all the moves, so reactivate analysis mode */
11509         SendToProgram("analyze\n", &first);
11510         first.analyzing = TRUE;
11511         /*first.maybeThinking = TRUE;*/
11512         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11513     }
11514 }
11515
11516 void
11517 BackwardInner(target)
11518      int target;
11519 {
11520     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11521
11522     if (appData.debugMode)
11523         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11524                 target, currentMove, forwardMostMove);
11525
11526     if (gameMode == EditPosition) return;
11527     if (currentMove <= backwardMostMove) {
11528         ClearHighlights();
11529         DrawPosition(full_redraw, boards[currentMove]);
11530         return;
11531     }
11532     if (gameMode == PlayFromGameFile && !pausing)
11533       PauseEvent();
11534
11535     if (moveList[target][0]) {
11536         int fromX, fromY, toX, toY;
11537         toX = moveList[target][2] - AAA;
11538         toY = moveList[target][3] - ONE;
11539         if (moveList[target][1] == '@') {
11540             if (appData.highlightLastMove) {
11541                 SetHighlights(-1, -1, toX, toY);
11542             }
11543         } else {
11544             fromX = moveList[target][0] - AAA;
11545             fromY = moveList[target][1] - ONE;
11546             if (target == currentMove - 1) {
11547                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11548             }
11549             if (appData.highlightLastMove) {
11550                 SetHighlights(fromX, fromY, toX, toY);
11551             }
11552         }
11553     }
11554     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11555         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11556         while (currentMove > target) {
11557             SendToProgram("undo\n", &first);
11558             currentMove--;
11559         }
11560     } else {
11561         currentMove = target;
11562     }
11563
11564     if (gameMode == EditGame || gameMode == EndOfGame) {
11565         whiteTimeRemaining = timeRemaining[0][currentMove];
11566         blackTimeRemaining = timeRemaining[1][currentMove];
11567     }
11568     DisplayBothClocks();
11569     DisplayMove(currentMove - 1);
11570     DrawPosition(full_redraw, boards[currentMove]);
11571     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11572     // [HGM] PV info: routine tests if comment empty
11573     DisplayComment(currentMove - 1, commentList[currentMove]);
11574 }
11575
11576 void
11577 BackwardEvent()
11578 {
11579     if (gameMode == IcsExamining && !pausing) {
11580         SendToICS(ics_prefix);
11581         SendToICS("backward\n");
11582     } else {
11583         BackwardInner(currentMove - 1);
11584     }
11585 }
11586
11587 void
11588 ToStartEvent()
11589 {
11590     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11591         /* to optimze, we temporarily turn off analysis mode while we undo
11592          * all the moves. Otherwise we get analysis output after each undo.
11593          */
11594         if (first.analysisSupport) {
11595           SendToProgram("exit\nforce\n", &first);
11596           first.analyzing = FALSE;
11597         }
11598     }
11599
11600     if (gameMode == IcsExamining && !pausing) {
11601         SendToICS(ics_prefix);
11602         SendToICS("backward 999999\n");
11603     } else {
11604         BackwardInner(backwardMostMove);
11605     }
11606
11607     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11608         /* we have fed all the moves, so reactivate analysis mode */
11609         SendToProgram("analyze\n", &first);
11610         first.analyzing = TRUE;
11611         /*first.maybeThinking = TRUE;*/
11612         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11613     }
11614 }
11615
11616 void
11617 ToNrEvent(int to)
11618 {
11619   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11620   if (to >= forwardMostMove) to = forwardMostMove;
11621   if (to <= backwardMostMove) to = backwardMostMove;
11622   if (to < currentMove) {
11623     BackwardInner(to);
11624   } else {
11625     ForwardInner(to);
11626   }
11627 }
11628
11629 void
11630 RevertEvent()
11631 {
11632     if (gameMode != IcsExamining) {
11633         DisplayError(_("You are not examining a game"), 0);
11634         return;
11635     }
11636     if (pausing) {
11637         DisplayError(_("You can't revert while pausing"), 0);
11638         return;
11639     }
11640     SendToICS(ics_prefix);
11641     SendToICS("revert\n");
11642 }
11643
11644 void
11645 RetractMoveEvent()
11646 {
11647     switch (gameMode) {
11648       case MachinePlaysWhite:
11649       case MachinePlaysBlack:
11650         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11651             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11652             return;
11653         }
11654         if (forwardMostMove < 2) return;
11655         currentMove = forwardMostMove = forwardMostMove - 2;
11656         whiteTimeRemaining = timeRemaining[0][currentMove];
11657         blackTimeRemaining = timeRemaining[1][currentMove];
11658         DisplayBothClocks();
11659         DisplayMove(currentMove - 1);
11660         ClearHighlights();/*!! could figure this out*/
11661         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11662         SendToProgram("remove\n", &first);
11663         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11664         break;
11665
11666       case BeginningOfGame:
11667       default:
11668         break;
11669
11670       case IcsPlayingWhite:
11671       case IcsPlayingBlack:
11672         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11673             SendToICS(ics_prefix);
11674             SendToICS("takeback 2\n");
11675         } else {
11676             SendToICS(ics_prefix);
11677             SendToICS("takeback 1\n");
11678         }
11679         break;
11680     }
11681 }
11682
11683 void
11684 MoveNowEvent()
11685 {
11686     ChessProgramState *cps;
11687
11688     switch (gameMode) {
11689       case MachinePlaysWhite:
11690         if (!WhiteOnMove(forwardMostMove)) {
11691             DisplayError(_("It is your turn"), 0);
11692             return;
11693         }
11694         cps = &first;
11695         break;
11696       case MachinePlaysBlack:
11697         if (WhiteOnMove(forwardMostMove)) {
11698             DisplayError(_("It is your turn"), 0);
11699             return;
11700         }
11701         cps = &first;
11702         break;
11703       case TwoMachinesPlay:
11704         if (WhiteOnMove(forwardMostMove) ==
11705             (first.twoMachinesColor[0] == 'w')) {
11706             cps = &first;
11707         } else {
11708             cps = &second;
11709         }
11710         break;
11711       case BeginningOfGame:
11712       default:
11713         return;
11714     }
11715     SendToProgram("?\n", cps);
11716 }
11717
11718 void
11719 TruncateGameEvent()
11720 {
11721     EditGameEvent();
11722     if (gameMode != EditGame) return;
11723     TruncateGame();
11724 }
11725
11726 void
11727 TruncateGame()
11728 {
11729     if (forwardMostMove > currentMove) {
11730         if (gameInfo.resultDetails != NULL) {
11731             free(gameInfo.resultDetails);
11732             gameInfo.resultDetails = NULL;
11733             gameInfo.result = GameUnfinished;
11734         }
11735         forwardMostMove = currentMove;
11736         HistorySet(parseList, backwardMostMove, forwardMostMove,
11737                    currentMove-1);
11738     }
11739 }
11740
11741 void
11742 HintEvent()
11743 {
11744     if (appData.noChessProgram) return;
11745     switch (gameMode) {
11746       case MachinePlaysWhite:
11747         if (WhiteOnMove(forwardMostMove)) {
11748             DisplayError(_("Wait until your turn"), 0);
11749             return;
11750         }
11751         break;
11752       case BeginningOfGame:
11753       case MachinePlaysBlack:
11754         if (!WhiteOnMove(forwardMostMove)) {
11755             DisplayError(_("Wait until your turn"), 0);
11756             return;
11757         }
11758         break;
11759       default:
11760         DisplayError(_("No hint available"), 0);
11761         return;
11762     }
11763     SendToProgram("hint\n", &first);
11764     hintRequested = TRUE;
11765 }
11766
11767 void
11768 BookEvent()
11769 {
11770     if (appData.noChessProgram) return;
11771     switch (gameMode) {
11772       case MachinePlaysWhite:
11773         if (WhiteOnMove(forwardMostMove)) {
11774             DisplayError(_("Wait until your turn"), 0);
11775             return;
11776         }
11777         break;
11778       case BeginningOfGame:
11779       case MachinePlaysBlack:
11780         if (!WhiteOnMove(forwardMostMove)) {
11781             DisplayError(_("Wait until your turn"), 0);
11782             return;
11783         }
11784         break;
11785       case EditPosition:
11786         EditPositionDone();
11787         break;
11788       case TwoMachinesPlay:
11789         return;
11790       default:
11791         break;
11792     }
11793     SendToProgram("bk\n", &first);
11794     bookOutput[0] = NULLCHAR;
11795     bookRequested = TRUE;
11796 }
11797
11798 void
11799 AboutGameEvent()
11800 {
11801     char *tags = PGNTags(&gameInfo);
11802     TagsPopUp(tags, CmailMsg());
11803     free(tags);
11804 }
11805
11806 /* end button procedures */
11807
11808 void
11809 PrintPosition(fp, move)
11810      FILE *fp;
11811      int move;
11812 {
11813     int i, j;
11814
11815     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11816         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11817             char c = PieceToChar(boards[move][i][j]);
11818             fputc(c == 'x' ? '.' : c, fp);
11819             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11820         }
11821     }
11822     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11823       fprintf(fp, "white to play\n");
11824     else
11825       fprintf(fp, "black to play\n");
11826 }
11827
11828 void
11829 PrintOpponents(fp)
11830      FILE *fp;
11831 {
11832     if (gameInfo.white != NULL) {
11833         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11834     } else {
11835         fprintf(fp, "\n");
11836     }
11837 }
11838
11839 /* Find last component of program's own name, using some heuristics */
11840 void
11841 TidyProgramName(prog, host, buf)
11842      char *prog, *host, buf[MSG_SIZ];
11843 {
11844     char *p, *q;
11845     int local = (strcmp(host, "localhost") == 0);
11846     while (!local && (p = strchr(prog, ';')) != NULL) {
11847         p++;
11848         while (*p == ' ') p++;
11849         prog = p;
11850     }
11851     if (*prog == '"' || *prog == '\'') {
11852         q = strchr(prog + 1, *prog);
11853     } else {
11854         q = strchr(prog, ' ');
11855     }
11856     if (q == NULL) q = prog + strlen(prog);
11857     p = q;
11858     while (p >= prog && *p != '/' && *p != '\\') p--;
11859     p++;
11860     if(p == prog && *p == '"') p++;
11861     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11862     memcpy(buf, p, q - p);
11863     buf[q - p] = NULLCHAR;
11864     if (!local) {
11865         strcat(buf, "@");
11866         strcat(buf, host);
11867     }
11868 }
11869
11870 char *
11871 TimeControlTagValue()
11872 {
11873     char buf[MSG_SIZ];
11874     if (!appData.clockMode) {
11875         strcpy(buf, "-");
11876     } else if (movesPerSession > 0) {
11877         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11878     } else if (timeIncrement == 0) {
11879         sprintf(buf, "%ld", timeControl/1000);
11880     } else {
11881         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11882     }
11883     return StrSave(buf);
11884 }
11885
11886 void
11887 SetGameInfo()
11888 {
11889     /* This routine is used only for certain modes */
11890     VariantClass v = gameInfo.variant;
11891     ClearGameInfo(&gameInfo);
11892     gameInfo.variant = v;
11893
11894     switch (gameMode) {
11895       case MachinePlaysWhite:
11896         gameInfo.event = StrSave( appData.pgnEventHeader );
11897         gameInfo.site = StrSave(HostName());
11898         gameInfo.date = PGNDate();
11899         gameInfo.round = StrSave("-");
11900         gameInfo.white = StrSave(first.tidy);
11901         gameInfo.black = StrSave(UserName());
11902         gameInfo.timeControl = TimeControlTagValue();
11903         break;
11904
11905       case MachinePlaysBlack:
11906         gameInfo.event = StrSave( appData.pgnEventHeader );
11907         gameInfo.site = StrSave(HostName());
11908         gameInfo.date = PGNDate();
11909         gameInfo.round = StrSave("-");
11910         gameInfo.white = StrSave(UserName());
11911         gameInfo.black = StrSave(first.tidy);
11912         gameInfo.timeControl = TimeControlTagValue();
11913         break;
11914
11915       case TwoMachinesPlay:
11916         gameInfo.event = StrSave( appData.pgnEventHeader );
11917         gameInfo.site = StrSave(HostName());
11918         gameInfo.date = PGNDate();
11919         if (matchGame > 0) {
11920             char buf[MSG_SIZ];
11921             sprintf(buf, "%d", matchGame);
11922             gameInfo.round = StrSave(buf);
11923         } else {
11924             gameInfo.round = StrSave("-");
11925         }
11926         if (first.twoMachinesColor[0] == 'w') {
11927             gameInfo.white = StrSave(first.tidy);
11928             gameInfo.black = StrSave(second.tidy);
11929         } else {
11930             gameInfo.white = StrSave(second.tidy);
11931             gameInfo.black = StrSave(first.tidy);
11932         }
11933         gameInfo.timeControl = TimeControlTagValue();
11934         break;
11935
11936       case EditGame:
11937         gameInfo.event = StrSave("Edited game");
11938         gameInfo.site = StrSave(HostName());
11939         gameInfo.date = PGNDate();
11940         gameInfo.round = StrSave("-");
11941         gameInfo.white = StrSave("-");
11942         gameInfo.black = StrSave("-");
11943         break;
11944
11945       case EditPosition:
11946         gameInfo.event = StrSave("Edited position");
11947         gameInfo.site = StrSave(HostName());
11948         gameInfo.date = PGNDate();
11949         gameInfo.round = StrSave("-");
11950         gameInfo.white = StrSave("-");
11951         gameInfo.black = StrSave("-");
11952         break;
11953
11954       case IcsPlayingWhite:
11955       case IcsPlayingBlack:
11956       case IcsObserving:
11957       case IcsExamining:
11958         break;
11959
11960       case PlayFromGameFile:
11961         gameInfo.event = StrSave("Game from non-PGN file");
11962         gameInfo.site = StrSave(HostName());
11963         gameInfo.date = PGNDate();
11964         gameInfo.round = StrSave("-");
11965         gameInfo.white = StrSave("?");
11966         gameInfo.black = StrSave("?");
11967         break;
11968
11969       default:
11970         break;
11971     }
11972 }
11973
11974 void
11975 ReplaceComment(index, text)
11976      int index;
11977      char *text;
11978 {
11979     int len;
11980
11981     while (*text == '\n') text++;
11982     len = strlen(text);
11983     while (len > 0 && text[len - 1] == '\n') len--;
11984
11985     if (commentList[index] != NULL)
11986       free(commentList[index]);
11987
11988     if (len == 0) {
11989         commentList[index] = NULL;
11990         return;
11991     }
11992     commentList[index] = (char *) malloc(len + 2);
11993     strncpy(commentList[index], text, len);
11994     commentList[index][len] = '\n';
11995     commentList[index][len + 1] = NULLCHAR;
11996 }
11997
11998 void
11999 CrushCRs(text)
12000      char *text;
12001 {
12002   char *p = text;
12003   char *q = text;
12004   char ch;
12005
12006   do {
12007     ch = *p++;
12008     if (ch == '\r') continue;
12009     *q++ = ch;
12010   } while (ch != '\0');
12011 }
12012
12013 void
12014 AppendComment(index, text)
12015      int index;
12016      char *text;
12017 {
12018     int oldlen, len;
12019     char *old;
12020
12021     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12022
12023     CrushCRs(text);
12024     while (*text == '\n') text++;
12025     len = strlen(text);
12026     while (len > 0 && text[len - 1] == '\n') len--;
12027
12028     if (len == 0) return;
12029
12030     if (commentList[index] != NULL) {
12031         old = commentList[index];
12032         oldlen = strlen(old);
12033         commentList[index] = (char *) malloc(oldlen + len + 2);
12034         strcpy(commentList[index], old);
12035         free(old);
12036         strncpy(&commentList[index][oldlen], text, len);
12037         commentList[index][oldlen + len] = '\n';
12038         commentList[index][oldlen + len + 1] = NULLCHAR;
12039     } else {
12040         commentList[index] = (char *) malloc(len + 2);
12041         strncpy(commentList[index], text, len);
12042         commentList[index][len] = '\n';
12043         commentList[index][len + 1] = NULLCHAR;
12044     }
12045 }
12046
12047 static char * FindStr( char * text, char * sub_text )
12048 {
12049     char * result = strstr( text, sub_text );
12050
12051     if( result != NULL ) {
12052         result += strlen( sub_text );
12053     }
12054
12055     return result;
12056 }
12057
12058 /* [AS] Try to extract PV info from PGN comment */
12059 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12060 char *GetInfoFromComment( int index, char * text )
12061 {
12062     char * sep = text;
12063
12064     if( text != NULL && index > 0 ) {
12065         int score = 0;
12066         int depth = 0;
12067         int time = -1, sec = 0, deci;
12068         char * s_eval = FindStr( text, "[%eval " );
12069         char * s_emt = FindStr( text, "[%emt " );
12070
12071         if( s_eval != NULL || s_emt != NULL ) {
12072             /* New style */
12073             char delim;
12074
12075             if( s_eval != NULL ) {
12076                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12077                     return text;
12078                 }
12079
12080                 if( delim != ']' ) {
12081                     return text;
12082                 }
12083             }
12084
12085             if( s_emt != NULL ) {
12086             }
12087         }
12088         else {
12089             /* We expect something like: [+|-]nnn.nn/dd */
12090             int score_lo = 0;
12091
12092             sep = strchr( text, '/' );
12093             if( sep == NULL || sep < (text+4) ) {
12094                 return text;
12095             }
12096
12097             time = -1; sec = -1; deci = -1;
12098             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12099                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12100                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12101                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12102                 return text;
12103             }
12104
12105             if( score_lo < 0 || score_lo >= 100 ) {
12106                 return text;
12107             }
12108
12109             if(sec >= 0) time = 600*time + 10*sec; else
12110             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12111
12112             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12113
12114             /* [HGM] PV time: now locate end of PV info */
12115             while( *++sep >= '0' && *sep <= '9'); // strip depth
12116             if(time >= 0)
12117             while( *++sep >= '0' && *sep <= '9'); // strip time
12118             if(sec >= 0)
12119             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12120             if(deci >= 0)
12121             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12122             while(*sep == ' ') sep++;
12123         }
12124
12125         if( depth <= 0 ) {
12126             return text;
12127         }
12128
12129         if( time < 0 ) {
12130             time = -1;
12131         }
12132
12133         pvInfoList[index-1].depth = depth;
12134         pvInfoList[index-1].score = score;
12135         pvInfoList[index-1].time  = 10*time; // centi-sec
12136     }
12137     return sep;
12138 }
12139
12140 void
12141 SendToProgram(message, cps)
12142      char *message;
12143      ChessProgramState *cps;
12144 {
12145     int count, outCount, error;
12146     char buf[MSG_SIZ];
12147
12148     if (cps->pr == NULL) return;
12149     Attention(cps);
12150
12151     if (appData.debugMode) {
12152         TimeMark now;
12153         GetTimeMark(&now);
12154         fprintf(debugFP, "%ld >%-6s: %s",
12155                 SubtractTimeMarks(&now, &programStartTime),
12156                 cps->which, message);
12157     }
12158
12159     count = strlen(message);
12160     outCount = OutputToProcess(cps->pr, message, count, &error);
12161     if (outCount < count && !exiting
12162                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12163         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12164         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12165             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12166                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12167                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12168             } else {
12169                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12170             }
12171             gameInfo.resultDetails = buf;
12172         }
12173         DisplayFatalError(buf, error, 1);
12174     }
12175 }
12176
12177 void
12178 ReceiveFromProgram(isr, closure, message, count, error)
12179      InputSourceRef isr;
12180      VOIDSTAR closure;
12181      char *message;
12182      int count;
12183      int error;
12184 {
12185     char *end_str;
12186     char buf[MSG_SIZ];
12187     ChessProgramState *cps = (ChessProgramState *)closure;
12188
12189     if (isr != cps->isr) return; /* Killed intentionally */
12190     if (count <= 0) {
12191         if (count == 0) {
12192             sprintf(buf,
12193                     _("Error: %s chess program (%s) exited unexpectedly"),
12194                     cps->which, cps->program);
12195         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12196                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12197                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12198                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12199                 } else {
12200                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12201                 }
12202                 gameInfo.resultDetails = buf;
12203             }
12204             RemoveInputSource(cps->isr);
12205             DisplayFatalError(buf, 0, 1);
12206         } else {
12207             sprintf(buf,
12208                     _("Error reading from %s chess program (%s)"),
12209                     cps->which, cps->program);
12210             RemoveInputSource(cps->isr);
12211
12212             /* [AS] Program is misbehaving badly... kill it */
12213             if( count == -2 ) {
12214                 DestroyChildProcess( cps->pr, 9 );
12215                 cps->pr = NoProc;
12216             }
12217
12218             DisplayFatalError(buf, error, 1);
12219         }
12220         return;
12221     }
12222
12223     if ((end_str = strchr(message, '\r')) != NULL)
12224       *end_str = NULLCHAR;
12225     if ((end_str = strchr(message, '\n')) != NULL)
12226       *end_str = NULLCHAR;
12227
12228     if (appData.debugMode) {
12229         TimeMark now; int print = 1;
12230         char *quote = ""; char c; int i;
12231
12232         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12233                 char start = message[0];
12234                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12235                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12236                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12237                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12238                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12239                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12240                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12241                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12242                         { quote = "# "; print = (appData.engineComments == 2); }
12243                 message[0] = start; // restore original message
12244         }
12245         if(print) {
12246                 GetTimeMark(&now);
12247                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12248                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12249                         quote,
12250                         message);
12251         }
12252     }
12253
12254     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12255     if (appData.icsEngineAnalyze) {
12256         if (strstr(message, "whisper") != NULL ||
12257              strstr(message, "kibitz") != NULL ||
12258             strstr(message, "tellics") != NULL) return;
12259     }
12260
12261     HandleMachineMove(message, cps);
12262 }
12263
12264
12265 void
12266 SendTimeControl(cps, mps, tc, inc, sd, st)
12267      ChessProgramState *cps;
12268      int mps, inc, sd, st;
12269      long tc;
12270 {
12271     char buf[MSG_SIZ];
12272     int seconds;
12273
12274     if( timeControl_2 > 0 ) {
12275         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12276             tc = timeControl_2;
12277         }
12278     }
12279     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12280     inc /= cps->timeOdds;
12281     st  /= cps->timeOdds;
12282
12283     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12284
12285     if (st > 0) {
12286       /* Set exact time per move, normally using st command */
12287       if (cps->stKludge) {
12288         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12289         seconds = st % 60;
12290         if (seconds == 0) {
12291           sprintf(buf, "level 1 %d\n", st/60);
12292         } else {
12293           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12294         }
12295       } else {
12296         sprintf(buf, "st %d\n", st);
12297       }
12298     } else {
12299       /* Set conventional or incremental time control, using level command */
12300       if (seconds == 0) {
12301         /* Note old gnuchess bug -- minutes:seconds used to not work.
12302            Fixed in later versions, but still avoid :seconds
12303            when seconds is 0. */
12304         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12305       } else {
12306         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12307                 seconds, inc/1000);
12308       }
12309     }
12310     SendToProgram(buf, cps);
12311
12312     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12313     /* Orthogonally, limit search to given depth */
12314     if (sd > 0) {
12315       if (cps->sdKludge) {
12316         sprintf(buf, "depth\n%d\n", sd);
12317       } else {
12318         sprintf(buf, "sd %d\n", sd);
12319       }
12320       SendToProgram(buf, cps);
12321     }
12322
12323     if(cps->nps > 0) { /* [HGM] nps */
12324         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12325         else {
12326                 sprintf(buf, "nps %d\n", cps->nps);
12327               SendToProgram(buf, cps);
12328         }
12329     }
12330 }
12331
12332 ChessProgramState *WhitePlayer()
12333 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12334 {
12335     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12336        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12337         return &second;
12338     return &first;
12339 }
12340
12341 void
12342 SendTimeRemaining(cps, machineWhite)
12343      ChessProgramState *cps;
12344      int /*boolean*/ machineWhite;
12345 {
12346     char message[MSG_SIZ];
12347     long time, otime;
12348
12349     /* Note: this routine must be called when the clocks are stopped
12350        or when they have *just* been set or switched; otherwise
12351        it will be off by the time since the current tick started.
12352     */
12353     if (machineWhite) {
12354         time = whiteTimeRemaining / 10;
12355         otime = blackTimeRemaining / 10;
12356     } else {
12357         time = blackTimeRemaining / 10;
12358         otime = whiteTimeRemaining / 10;
12359     }
12360     /* [HGM] translate opponent's time by time-odds factor */
12361     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12362     if (appData.debugMode) {
12363         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12364     }
12365
12366     if (time <= 0) time = 1;
12367     if (otime <= 0) otime = 1;
12368
12369     sprintf(message, "time %ld\n", time);
12370     SendToProgram(message, cps);
12371
12372     sprintf(message, "otim %ld\n", otime);
12373     SendToProgram(message, cps);
12374 }
12375
12376 int
12377 BoolFeature(p, name, loc, cps)
12378      char **p;
12379      char *name;
12380      int *loc;
12381      ChessProgramState *cps;
12382 {
12383   char buf[MSG_SIZ];
12384   int len = strlen(name);
12385   int val;
12386   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12387     (*p) += len + 1;
12388     sscanf(*p, "%d", &val);
12389     *loc = (val != 0);
12390     while (**p && **p != ' ') (*p)++;
12391     sprintf(buf, "accepted %s\n", name);
12392     SendToProgram(buf, cps);
12393     return TRUE;
12394   }
12395   return FALSE;
12396 }
12397
12398 int
12399 IntFeature(p, name, loc, cps)
12400      char **p;
12401      char *name;
12402      int *loc;
12403      ChessProgramState *cps;
12404 {
12405   char buf[MSG_SIZ];
12406   int len = strlen(name);
12407   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12408     (*p) += len + 1;
12409     sscanf(*p, "%d", loc);
12410     while (**p && **p != ' ') (*p)++;
12411     sprintf(buf, "accepted %s\n", name);
12412     SendToProgram(buf, cps);
12413     return TRUE;
12414   }
12415   return FALSE;
12416 }
12417
12418 int
12419 StringFeature(p, name, loc, cps)
12420      char **p;
12421      char *name;
12422      char loc[];
12423      ChessProgramState *cps;
12424 {
12425   char buf[MSG_SIZ];
12426   int len = strlen(name);
12427   if (strncmp((*p), name, len) == 0
12428       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12429     (*p) += len + 2;
12430     sscanf(*p, "%[^\"]", loc);
12431     while (**p && **p != '\"') (*p)++;
12432     if (**p == '\"') (*p)++;
12433     sprintf(buf, "accepted %s\n", name);
12434     SendToProgram(buf, cps);
12435     return TRUE;
12436   }
12437   return FALSE;
12438 }
12439
12440 int
12441 ParseOption(Option *opt, ChessProgramState *cps)
12442 // [HGM] options: process the string that defines an engine option, and determine
12443 // name, type, default value, and allowed value range
12444 {
12445         char *p, *q, buf[MSG_SIZ];
12446         int n, min = (-1)<<31, max = 1<<31, def;
12447
12448         if(p = strstr(opt->name, " -spin ")) {
12449             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12450             if(max < min) max = min; // enforce consistency
12451             if(def < min) def = min;
12452             if(def > max) def = max;
12453             opt->value = def;
12454             opt->min = min;
12455             opt->max = max;
12456             opt->type = Spin;
12457         } else if((p = strstr(opt->name, " -slider "))) {
12458             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12459             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12460             if(max < min) max = min; // enforce consistency
12461             if(def < min) def = min;
12462             if(def > max) def = max;
12463             opt->value = def;
12464             opt->min = min;
12465             opt->max = max;
12466             opt->type = Spin; // Slider;
12467         } else if((p = strstr(opt->name, " -string "))) {
12468             opt->textValue = p+9;
12469             opt->type = TextBox;
12470         } else if((p = strstr(opt->name, " -file "))) {
12471             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12472             opt->textValue = p+7;
12473             opt->type = TextBox; // FileName;
12474         } else if((p = strstr(opt->name, " -path "))) {
12475             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12476             opt->textValue = p+7;
12477             opt->type = TextBox; // PathName;
12478         } else if(p = strstr(opt->name, " -check ")) {
12479             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12480             opt->value = (def != 0);
12481             opt->type = CheckBox;
12482         } else if(p = strstr(opt->name, " -combo ")) {
12483             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12484             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12485             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12486             opt->value = n = 0;
12487             while(q = StrStr(q, " /// ")) {
12488                 n++; *q = 0;    // count choices, and null-terminate each of them
12489                 q += 5;
12490                 if(*q == '*') { // remember default, which is marked with * prefix
12491                     q++;
12492                     opt->value = n;
12493                 }
12494                 cps->comboList[cps->comboCnt++] = q;
12495             }
12496             cps->comboList[cps->comboCnt++] = NULL;
12497             opt->max = n + 1;
12498             opt->type = ComboBox;
12499         } else if(p = strstr(opt->name, " -button")) {
12500             opt->type = Button;
12501         } else if(p = strstr(opt->name, " -save")) {
12502             opt->type = SaveButton;
12503         } else return FALSE;
12504         *p = 0; // terminate option name
12505         // now look if the command-line options define a setting for this engine option.
12506         if(cps->optionSettings && cps->optionSettings[0])
12507             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12508         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12509                 sprintf(buf, "option %s", p);
12510                 if(p = strstr(buf, ",")) *p = 0;
12511                 strcat(buf, "\n");
12512                 SendToProgram(buf, cps);
12513         }
12514         return TRUE;
12515 }
12516
12517 void
12518 FeatureDone(cps, val)
12519      ChessProgramState* cps;
12520      int val;
12521 {
12522   DelayedEventCallback cb = GetDelayedEvent();
12523   if ((cb == InitBackEnd3 && cps == &first) ||
12524       (cb == TwoMachinesEventIfReady && cps == &second)) {
12525     CancelDelayedEvent();
12526     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12527   }
12528   cps->initDone = val;
12529 }
12530
12531 /* Parse feature command from engine */
12532 void
12533 ParseFeatures(args, cps)
12534      char* args;
12535      ChessProgramState *cps;
12536 {
12537   char *p = args;
12538   char *q;
12539   int val;
12540   char buf[MSG_SIZ];
12541
12542   for (;;) {
12543     while (*p == ' ') p++;
12544     if (*p == NULLCHAR) return;
12545
12546     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12547     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12548     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12549     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12550     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12551     if (BoolFeature(&p, "reuse", &val, cps)) {
12552       /* Engine can disable reuse, but can't enable it if user said no */
12553       if (!val) cps->reuse = FALSE;
12554       continue;
12555     }
12556     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12557     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12558       if (gameMode == TwoMachinesPlay) {
12559         DisplayTwoMachinesTitle();
12560       } else {
12561         DisplayTitle("");
12562       }
12563       continue;
12564     }
12565     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12566     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12567     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12568     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12569     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12570     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12571     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12572     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12573     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12574     if (IntFeature(&p, "done", &val, cps)) {
12575       FeatureDone(cps, val);
12576       continue;
12577     }
12578     /* Added by Tord: */
12579     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12580     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12581     /* End of additions by Tord */
12582
12583     /* [HGM] added features: */
12584     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12585     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12586     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12587     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12588     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12589     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12590     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12591         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12592             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12593             SendToProgram(buf, cps);
12594             continue;
12595         }
12596         if(cps->nrOptions >= MAX_OPTIONS) {
12597             cps->nrOptions--;
12598             sprintf(buf, "%s engine has too many options\n", cps->which);
12599             DisplayError(buf, 0);
12600         }
12601         continue;
12602     }
12603     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12604     /* End of additions by HGM */
12605
12606     /* unknown feature: complain and skip */
12607     q = p;
12608     while (*q && *q != '=') q++;
12609     sprintf(buf, "rejected %.*s\n", q-p, p);
12610     SendToProgram(buf, cps);
12611     p = q;
12612     if (*p == '=') {
12613       p++;
12614       if (*p == '\"') {
12615         p++;
12616         while (*p && *p != '\"') p++;
12617         if (*p == '\"') p++;
12618       } else {
12619         while (*p && *p != ' ') p++;
12620       }
12621     }
12622   }
12623
12624 }
12625
12626 void
12627 PeriodicUpdatesEvent(newState)
12628      int newState;
12629 {
12630     if (newState == appData.periodicUpdates)
12631       return;
12632
12633     appData.periodicUpdates=newState;
12634
12635     /* Display type changes, so update it now */
12636     DisplayAnalysis();
12637
12638     /* Get the ball rolling again... */
12639     if (newState) {
12640         AnalysisPeriodicEvent(1);
12641         StartAnalysisClock();
12642     }
12643 }
12644
12645 void
12646 PonderNextMoveEvent(newState)
12647      int newState;
12648 {
12649     if (newState == appData.ponderNextMove) return;
12650     if (gameMode == EditPosition) EditPositionDone();
12651     if (newState) {
12652         SendToProgram("hard\n", &first);
12653         if (gameMode == TwoMachinesPlay) {
12654             SendToProgram("hard\n", &second);
12655         }
12656     } else {
12657         SendToProgram("easy\n", &first);
12658         thinkOutput[0] = NULLCHAR;
12659         if (gameMode == TwoMachinesPlay) {
12660             SendToProgram("easy\n", &second);
12661         }
12662     }
12663     appData.ponderNextMove = newState;
12664 }
12665
12666 void
12667 NewSettingEvent(option, command, value)
12668      char *command;
12669      int option, value;
12670 {
12671     char buf[MSG_SIZ];
12672
12673     if (gameMode == EditPosition) EditPositionDone();
12674     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12675     SendToProgram(buf, &first);
12676     if (gameMode == TwoMachinesPlay) {
12677         SendToProgram(buf, &second);
12678     }
12679 }
12680
12681 void
12682 ShowThinkingEvent()
12683 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12684 {
12685     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12686     int newState = appData.showThinking
12687         // [HGM] thinking: other features now need thinking output as well
12688         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12689
12690     if (oldState == newState) return;
12691     oldState = newState;
12692     if (gameMode == EditPosition) EditPositionDone();
12693     if (oldState) {
12694         SendToProgram("post\n", &first);
12695         if (gameMode == TwoMachinesPlay) {
12696             SendToProgram("post\n", &second);
12697         }
12698     } else {
12699         SendToProgram("nopost\n", &first);
12700         thinkOutput[0] = NULLCHAR;
12701         if (gameMode == TwoMachinesPlay) {
12702             SendToProgram("nopost\n", &second);
12703         }
12704     }
12705 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12706 }
12707
12708 void
12709 AskQuestionEvent(title, question, replyPrefix, which)
12710      char *title; char *question; char *replyPrefix; char *which;
12711 {
12712   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12713   if (pr == NoProc) return;
12714   AskQuestion(title, question, replyPrefix, pr);
12715 }
12716
12717 void
12718 DisplayMove(moveNumber)
12719      int moveNumber;
12720 {
12721     char message[MSG_SIZ];
12722     char res[MSG_SIZ];
12723     char cpThinkOutput[MSG_SIZ];
12724
12725     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12726
12727     if (moveNumber == forwardMostMove - 1 ||
12728         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12729
12730         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12731
12732         if (strchr(cpThinkOutput, '\n')) {
12733             *strchr(cpThinkOutput, '\n') = NULLCHAR;
12734         }
12735     } else {
12736         *cpThinkOutput = NULLCHAR;
12737     }
12738
12739     /* [AS] Hide thinking from human user */
12740     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12741         *cpThinkOutput = NULLCHAR;
12742         if( thinkOutput[0] != NULLCHAR ) {
12743             int i;
12744
12745             for( i=0; i<=hiddenThinkOutputState; i++ ) {
12746                 cpThinkOutput[i] = '.';
12747             }
12748             cpThinkOutput[i] = NULLCHAR;
12749             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12750         }
12751     }
12752
12753     if (moveNumber == forwardMostMove - 1 &&
12754         gameInfo.resultDetails != NULL) {
12755         if (gameInfo.resultDetails[0] == NULLCHAR) {
12756             sprintf(res, " %s", PGNResult(gameInfo.result));
12757         } else {
12758             sprintf(res, " {%s} %s",
12759                     gameInfo.resultDetails, PGNResult(gameInfo.result));
12760         }
12761     } else {
12762         res[0] = NULLCHAR;
12763     }
12764
12765     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12766         DisplayMessage(res, cpThinkOutput);
12767     } else {
12768         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12769                 WhiteOnMove(moveNumber) ? " " : ".. ",
12770                 parseList[moveNumber], res);
12771         DisplayMessage(message, cpThinkOutput);
12772     }
12773 }
12774
12775 void
12776 DisplayAnalysisText(text)
12777      char *text;
12778 {
12779   if (gameMode == AnalyzeMode || gameMode == AnalyzeFile 
12780       || appData.icsEngineAnalyze) 
12781     {
12782       EngineOutputPopUp();
12783     }
12784 }
12785
12786 static int
12787 only_one_move(str)
12788      char *str;
12789 {
12790     while (*str && isspace(*str)) ++str;
12791     while (*str && !isspace(*str)) ++str;
12792     if (!*str) return 1;
12793     while (*str && isspace(*str)) ++str;
12794     if (!*str) return 1;
12795     return 0;
12796 }
12797
12798 void
12799 DisplayAnalysis()
12800 {
12801     char buf[MSG_SIZ];
12802     char lst[MSG_SIZ / 2];
12803     double nps;
12804     static char *xtra[] = { "", " (--)", " (++)" };
12805     int h, m, s, cs;
12806
12807     if (programStats.time == 0) {
12808         programStats.time = 1;
12809     }
12810
12811     if (programStats.got_only_move) {
12812         safeStrCpy(buf, programStats.movelist, sizeof(buf));
12813     } else {
12814         safeStrCpy( lst, programStats.movelist, sizeof(lst));
12815
12816         nps = (u64ToDouble(programStats.nodes) /
12817              ((double)programStats.time /100.0));
12818
12819         cs = programStats.time % 100;
12820         s = programStats.time / 100;
12821         h = (s / (60*60));
12822         s = s - h*60*60;
12823         m = (s/60);
12824         s = s - m*60;
12825
12826         if (programStats.moves_left > 0 && appData.periodicUpdates) {
12827           if (programStats.move_name[0] != NULLCHAR) {
12828             sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12829                     programStats.depth,
12830                     programStats.nr_moves-programStats.moves_left,
12831                     programStats.nr_moves, programStats.move_name,
12832                     ((float)programStats.score)/100.0, lst,
12833                     only_one_move(lst)?
12834                     xtra[programStats.got_fail] : "",
12835                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12836           } else {
12837             sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12838                     programStats.depth,
12839                     programStats.nr_moves-programStats.moves_left,
12840                     programStats.nr_moves, ((float)programStats.score)/100.0,
12841                     lst,
12842                     only_one_move(lst)?
12843                     xtra[programStats.got_fail] : "",
12844                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12845           }
12846         } else {
12847             sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12848                     programStats.depth,
12849                     ((float)programStats.score)/100.0,
12850                     lst,
12851                     only_one_move(lst)?
12852                     xtra[programStats.got_fail] : "",
12853                     (u64)programStats.nodes, (int)nps, h, m, s, cs);
12854         }
12855     }
12856     DisplayAnalysisText(buf);
12857 }
12858
12859 void
12860 DisplayComment(moveNumber, text)
12861      int moveNumber;
12862      char *text;
12863 {
12864     char title[MSG_SIZ];
12865     char buf[8000]; // comment can be long!
12866     int score, depth;
12867
12868     if( appData.autoDisplayComment ) {
12869         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12870             strcpy(title, "Comment");
12871         } else {
12872             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12873                     WhiteOnMove(moveNumber) ? " " : ".. ",
12874                     parseList[moveNumber]);
12875         }
12876         // [HGM] PV info: display PV info together with (or as) comment
12877         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12878             if(text == NULL) text = "";
12879             score = pvInfoList[moveNumber].score;
12880             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12881                               depth, (pvInfoList[moveNumber].time+50)/100, text);
12882             text = buf;
12883         }
12884     } else title[0] = 0;
12885
12886     if (text != NULL)
12887         CommentPopUp(title, text);
12888 }
12889
12890 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12891  * might be busy thinking or pondering.  It can be omitted if your
12892  * gnuchess is configured to stop thinking immediately on any user
12893  * input.  However, that gnuchess feature depends on the FIONREAD
12894  * ioctl, which does not work properly on some flavors of Unix.
12895  */
12896 void
12897 Attention(cps)
12898      ChessProgramState *cps;
12899 {
12900 #if ATTENTION
12901     if (!cps->useSigint) return;
12902     if (appData.noChessProgram || (cps->pr == NoProc)) return;
12903     switch (gameMode) {
12904       case MachinePlaysWhite:
12905       case MachinePlaysBlack:
12906       case TwoMachinesPlay:
12907       case IcsPlayingWhite:
12908       case IcsPlayingBlack:
12909       case AnalyzeMode:
12910       case AnalyzeFile:
12911         /* Skip if we know it isn't thinking */
12912         if (!cps->maybeThinking) return;
12913         if (appData.debugMode)
12914           fprintf(debugFP, "Interrupting %s\n", cps->which);
12915         InterruptChildProcess(cps->pr);
12916         cps->maybeThinking = FALSE;
12917         break;
12918       default:
12919         break;
12920     }
12921 #endif /*ATTENTION*/
12922 }
12923
12924 int
12925 CheckFlags()
12926 {
12927     if (whiteTimeRemaining <= 0) {
12928         if (!whiteFlag) {
12929             whiteFlag = TRUE;
12930             if (appData.icsActive) {
12931                 if (appData.autoCallFlag &&
12932                     gameMode == IcsPlayingBlack && !blackFlag) {
12933                   SendToICS(ics_prefix);
12934                   SendToICS("flag\n");
12935                 }
12936             } else {
12937                 if (blackFlag) {
12938                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12939                 } else {
12940                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12941                     if (appData.autoCallFlag) {
12942                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12943                         return TRUE;
12944                     }
12945                 }
12946             }
12947         }
12948     }
12949     if (blackTimeRemaining <= 0) {
12950         if (!blackFlag) {
12951             blackFlag = TRUE;
12952             if (appData.icsActive) {
12953                 if (appData.autoCallFlag &&
12954                     gameMode == IcsPlayingWhite && !whiteFlag) {
12955                   SendToICS(ics_prefix);
12956                   SendToICS("flag\n");
12957                 }
12958             } else {
12959                 if (whiteFlag) {
12960                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12961                 } else {
12962                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12963                     if (appData.autoCallFlag) {
12964                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12965                         return TRUE;
12966                     }
12967                 }
12968             }
12969         }
12970     }
12971     return FALSE;
12972 }
12973
12974 void
12975 CheckTimeControl()
12976 {
12977     if (!appData.clockMode || appData.icsActive ||
12978         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12979
12980     /*
12981      * add time to clocks when time control is achieved ([HGM] now also used for increment)
12982      */
12983     if ( !WhiteOnMove(forwardMostMove) )
12984         /* White made time control */
12985         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12986         /* [HGM] time odds: correct new time quota for time odds! */
12987                                             / WhitePlayer()->timeOdds;
12988       else
12989         /* Black made time control */
12990         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12991                                             / WhitePlayer()->other->timeOdds;
12992 }
12993
12994 void
12995 DisplayBothClocks()
12996 {
12997     int wom = gameMode == EditPosition ?
12998       !blackPlaysFirst : WhiteOnMove(currentMove);
12999     DisplayWhiteClock(whiteTimeRemaining, wom);
13000     DisplayBlackClock(blackTimeRemaining, !wom);
13001 }
13002
13003
13004 /* Timekeeping seems to be a portability nightmare.  I think everyone
13005    has ftime(), but I'm really not sure, so I'm including some ifdefs
13006    to use other calls if you don't.  Clocks will be less accurate if
13007    you have neither ftime nor gettimeofday.
13008 */
13009
13010 /* VS 2008 requires the #include outside of the function */
13011 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13012 #include <sys/timeb.h>
13013 #endif
13014
13015 /* Get the current time as a TimeMark */
13016 void
13017 GetTimeMark(tm)
13018      TimeMark *tm;
13019 {
13020 #if HAVE_GETTIMEOFDAY
13021
13022     struct timeval timeVal;
13023     struct timezone timeZone;
13024
13025     gettimeofday(&timeVal, &timeZone);
13026     tm->sec = (long) timeVal.tv_sec;
13027     tm->ms = (int) (timeVal.tv_usec / 1000L);
13028
13029 #else /*!HAVE_GETTIMEOFDAY*/
13030 #if HAVE_FTIME
13031
13032 // include <sys/timeb.h> / moved to just above start of function
13033     struct timeb timeB;
13034
13035     ftime(&timeB);
13036     tm->sec = (long) timeB.time;
13037     tm->ms = (int) timeB.millitm;
13038
13039 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13040     tm->sec = (long) time(NULL);
13041     tm->ms = 0;
13042 #endif
13043 #endif
13044 }
13045
13046 /* Return the difference in milliseconds between two
13047    time marks.  We assume the difference will fit in a long!
13048 */
13049 long
13050 SubtractTimeMarks(tm2, tm1)
13051      TimeMark *tm2, *tm1;
13052 {
13053     return 1000L*(tm2->sec - tm1->sec) +
13054            (long) (tm2->ms - tm1->ms);
13055 }
13056
13057
13058 /*
13059  * Code to manage the game clocks.
13060  *
13061  * In tournament play, black starts the clock and then white makes a move.
13062  * We give the human user a slight advantage if he is playing white---the
13063  * clocks don't run until he makes his first move, so it takes zero time.
13064  * Also, we don't account for network lag, so we could get out of sync
13065  * with GNU Chess's clock -- but then, referees are always right.
13066  */
13067
13068 static TimeMark tickStartTM;
13069 static long intendedTickLength;
13070
13071 long
13072 NextTickLength(timeRemaining)
13073      long timeRemaining;
13074 {
13075     long nominalTickLength, nextTickLength;
13076
13077     if (timeRemaining > 0L && timeRemaining <= 10000L)
13078       nominalTickLength = 100L;
13079     else
13080       nominalTickLength = 1000L;
13081     nextTickLength = timeRemaining % nominalTickLength;
13082     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13083
13084     return nextTickLength;
13085 }
13086
13087 /* Adjust clock one minute up or down */
13088 void
13089 AdjustClock(Boolean which, int dir)
13090 {
13091     if(which) blackTimeRemaining += 60000*dir;
13092     else      whiteTimeRemaining += 60000*dir;
13093     DisplayBothClocks();
13094 }
13095
13096 /* Stop clocks and reset to a fresh time control */
13097 void
13098 ResetClocks()
13099 {
13100     (void) StopClockTimer();
13101     if (appData.icsActive) {
13102         whiteTimeRemaining = blackTimeRemaining = 0;
13103     } else { /* [HGM] correct new time quote for time odds */
13104         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13105         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13106     }
13107     if (whiteFlag || blackFlag) {
13108         DisplayTitle("");
13109         whiteFlag = blackFlag = FALSE;
13110     }
13111     DisplayBothClocks();
13112 }
13113
13114 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13115
13116 /* Decrement running clock by amount of time that has passed */
13117 void
13118 DecrementClocks()
13119 {
13120     long timeRemaining;
13121     long lastTickLength, fudge;
13122     TimeMark now;
13123
13124     if (!appData.clockMode) return;
13125     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13126
13127     GetTimeMark(&now);
13128
13129     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13130
13131     /* Fudge if we woke up a little too soon */
13132     fudge = intendedTickLength - lastTickLength;
13133     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13134
13135     if (WhiteOnMove(forwardMostMove)) {
13136         if(whiteNPS >= 0) lastTickLength = 0;
13137         timeRemaining = whiteTimeRemaining -= lastTickLength;
13138         DisplayWhiteClock(whiteTimeRemaining - fudge,
13139                           WhiteOnMove(currentMove));
13140     } else {
13141         if(blackNPS >= 0) lastTickLength = 0;
13142         timeRemaining = blackTimeRemaining -= lastTickLength;
13143         DisplayBlackClock(blackTimeRemaining - fudge,
13144                           !WhiteOnMove(currentMove));
13145     }
13146
13147     if (CheckFlags()) return;
13148
13149     tickStartTM = now;
13150     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13151     StartClockTimer(intendedTickLength);
13152
13153     /* if the time remaining has fallen below the alarm threshold, sound the
13154      * alarm. if the alarm has sounded and (due to a takeback or time control
13155      * with increment) the time remaining has increased to a level above the
13156      * threshold, reset the alarm so it can sound again.
13157      */
13158
13159     if (appData.icsActive && appData.icsAlarm) {
13160
13161         /* make sure we are dealing with the user's clock */
13162         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13163                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13164            )) return;
13165
13166         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13167             alarmSounded = FALSE;
13168         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13169             PlayAlarmSound();
13170             alarmSounded = TRUE;
13171         }
13172     }
13173 }
13174
13175
13176 /* A player has just moved, so stop the previously running
13177    clock and (if in clock mode) start the other one.
13178    We redisplay both clocks in case we're in ICS mode, because
13179    ICS gives us an update to both clocks after every move.
13180    Note that this routine is called *after* forwardMostMove
13181    is updated, so the last fractional tick must be subtracted
13182    from the color that is *not* on move now.
13183 */
13184 void
13185 SwitchClocks()
13186 {
13187     long lastTickLength;
13188     TimeMark now;
13189     int flagged = FALSE;
13190
13191     GetTimeMark(&now);
13192
13193     if (StopClockTimer() && appData.clockMode) {
13194         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13195         if (WhiteOnMove(forwardMostMove)) {
13196             if(blackNPS >= 0) lastTickLength = 0;
13197             blackTimeRemaining -= lastTickLength;
13198            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13199 //         if(pvInfoList[forwardMostMove-1].time == -1)
13200                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13201                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13202         } else {
13203            if(whiteNPS >= 0) lastTickLength = 0;
13204            whiteTimeRemaining -= lastTickLength;
13205            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13206 //         if(pvInfoList[forwardMostMove-1].time == -1)
13207                  pvInfoList[forwardMostMove-1].time =
13208                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13209         }
13210         flagged = CheckFlags();
13211     }
13212     CheckTimeControl();
13213
13214     if (flagged || !appData.clockMode) return;
13215
13216     switch (gameMode) {
13217       case MachinePlaysBlack:
13218       case MachinePlaysWhite:
13219       case BeginningOfGame:
13220         if (pausing) return;
13221         break;
13222
13223       case EditGame:
13224       case PlayFromGameFile:
13225       case IcsExamining:
13226         return;
13227
13228       default:
13229         break;
13230     }
13231
13232     tickStartTM = now;
13233     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13234       whiteTimeRemaining : blackTimeRemaining);
13235     StartClockTimer(intendedTickLength);
13236 }
13237
13238
13239 /* Stop both clocks */
13240 void
13241 StopClocks()
13242 {
13243     long lastTickLength;
13244     TimeMark now;
13245
13246     if (!StopClockTimer()) return;
13247     if (!appData.clockMode) return;
13248
13249     GetTimeMark(&now);
13250
13251     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13252     if (WhiteOnMove(forwardMostMove)) {
13253         if(whiteNPS >= 0) lastTickLength = 0;
13254         whiteTimeRemaining -= lastTickLength;
13255         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13256     } else {
13257         if(blackNPS >= 0) lastTickLength = 0;
13258         blackTimeRemaining -= lastTickLength;
13259         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13260     }
13261     CheckFlags();
13262 }
13263
13264 /* Start clock of player on move.  Time may have been reset, so
13265    if clock is already running, stop and restart it. */
13266 void
13267 StartClocks()
13268 {
13269     (void) StopClockTimer(); /* in case it was running already */
13270     DisplayBothClocks();
13271     if (CheckFlags()) return;
13272
13273     if (!appData.clockMode) return;
13274     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13275
13276     GetTimeMark(&tickStartTM);
13277     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13278       whiteTimeRemaining : blackTimeRemaining);
13279
13280    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13281     whiteNPS = blackNPS = -1;
13282     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13283        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13284         whiteNPS = first.nps;
13285     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13286        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13287         blackNPS = first.nps;
13288     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13289         whiteNPS = second.nps;
13290     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13291         blackNPS = second.nps;
13292     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13293
13294     StartClockTimer(intendedTickLength);
13295 }
13296
13297 char *
13298 TimeString(ms)
13299      long ms;
13300 {
13301     long second, minute, hour, day;
13302     char *sign = "";
13303     static char buf[32];
13304
13305     if (ms > 0 && ms <= 9900) {
13306       /* convert milliseconds to tenths, rounding up */
13307       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13308
13309       sprintf(buf, " %03.1f ", tenths/10.0);
13310       return buf;
13311     }
13312
13313     /* convert milliseconds to seconds, rounding up */
13314     /* use floating point to avoid strangeness of integer division
13315        with negative dividends on many machines */
13316     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13317
13318     if (second < 0) {
13319         sign = "-";
13320         second = -second;
13321     }
13322
13323     day = second / (60 * 60 * 24);
13324     second = second % (60 * 60 * 24);
13325     hour = second / (60 * 60);
13326     second = second % (60 * 60);
13327     minute = second / 60;
13328     second = second % 60;
13329
13330     if (day > 0)
13331       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13332               sign, day, hour, minute, second);
13333     else if (hour > 0)
13334       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13335     else
13336       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13337
13338     return buf;
13339 }
13340
13341
13342 /*
13343  * This is necessary because some C libraries aren't ANSI C compliant yet.
13344  */
13345 char *
13346 StrStr(string, match)
13347      char *string, *match;
13348 {
13349     int i, length;
13350
13351     length = strlen(match);
13352
13353     for (i = strlen(string) - length; i >= 0; i--, string++)
13354       if (!strncmp(match, string, length))
13355         return string;
13356
13357     return NULL;
13358 }
13359
13360 char *
13361 StrCaseStr(string, match)
13362      char *string, *match;
13363 {
13364     int i, j, length;
13365
13366     length = strlen(match);
13367
13368     for (i = strlen(string) - length; i >= 0; i--, string++) {
13369         for (j = 0; j < length; j++) {
13370             if (ToLower(match[j]) != ToLower(string[j]))
13371               break;
13372         }
13373         if (j == length) return string;
13374     }
13375
13376     return NULL;
13377 }
13378
13379 #ifndef _amigados
13380 int
13381 StrCaseCmp(s1, s2)
13382      char *s1, *s2;
13383 {
13384     char c1, c2;
13385
13386     for (;;) {
13387         c1 = ToLower(*s1++);
13388         c2 = ToLower(*s2++);
13389         if (c1 > c2) return 1;
13390         if (c1 < c2) return -1;
13391         if (c1 == NULLCHAR) return 0;
13392     }
13393 }
13394
13395
13396 int
13397 ToLower(c)
13398      int c;
13399 {
13400     return isupper(c) ? tolower(c) : c;
13401 }
13402
13403
13404 int
13405 ToUpper(c)
13406      int c;
13407 {
13408     return islower(c) ? toupper(c) : c;
13409 }
13410 #endif /* !_amigados    */
13411
13412 char *
13413 StrSave(s)
13414      char *s;
13415 {
13416     char *ret;
13417
13418     if ((ret = (char *) malloc(strlen(s) + 1))) {
13419         strcpy(ret, s);
13420     }
13421     return ret;
13422 }
13423
13424 char *
13425 StrSavePtr(s, savePtr)
13426      char *s, **savePtr;
13427 {
13428     if (*savePtr) {
13429         free(*savePtr);
13430     }
13431     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13432         strcpy(*savePtr, s);
13433     }
13434     return(*savePtr);
13435 }
13436
13437 char *
13438 PGNDate()
13439 {
13440     time_t clock;
13441     struct tm *tm;
13442     char buf[MSG_SIZ];
13443
13444     clock = time((time_t *)NULL);
13445     tm = localtime(&clock);
13446     sprintf(buf, "%04d.%02d.%02d",
13447             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13448     return StrSave(buf);
13449 }
13450
13451
13452 char *
13453 PositionToFEN(move, overrideCastling)
13454      int move;
13455      char *overrideCastling;
13456 {
13457     int i, j, fromX, fromY, toX, toY;
13458     int whiteToPlay;
13459     char buf[128];
13460     char *p, *q;
13461     int emptycount;
13462     ChessSquare piece;
13463
13464     whiteToPlay = (gameMode == EditPosition) ?
13465       !blackPlaysFirst : (move % 2 == 0);
13466     p = buf;
13467
13468     /* Piece placement data */
13469     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13470         emptycount = 0;
13471         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13472             if (boards[move][i][j] == EmptySquare) {
13473                 emptycount++;
13474             } else { ChessSquare piece = boards[move][i][j];
13475                 if (emptycount > 0) {
13476                     if(emptycount<10) /* [HGM] can be >= 10 */
13477                         *p++ = '0' + emptycount;
13478                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13479                     emptycount = 0;
13480                 }
13481                 if(PieceToChar(piece) == '+') {
13482                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13483                     *p++ = '+';
13484                     piece = (ChessSquare)(DEMOTED piece);
13485                 }
13486                 *p++ = PieceToChar(piece);
13487                 if(p[-1] == '~') {
13488                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13489                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13490                     *p++ = '~';
13491                 }
13492             }
13493         }
13494         if (emptycount > 0) {
13495             if(emptycount<10) /* [HGM] can be >= 10 */
13496                 *p++ = '0' + emptycount;
13497             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13498             emptycount = 0;
13499         }
13500         *p++ = '/';
13501     }
13502     *(p - 1) = ' ';
13503
13504     /* [HGM] print Crazyhouse or Shogi holdings */
13505     if( gameInfo.holdingsWidth ) {
13506         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13507         q = p;
13508         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13509             piece = boards[move][i][BOARD_WIDTH-1];
13510             if( piece != EmptySquare )
13511               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13512                   *p++ = PieceToChar(piece);
13513         }
13514         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13515             piece = boards[move][BOARD_HEIGHT-i-1][0];
13516             if( piece != EmptySquare )
13517               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13518                   *p++ = PieceToChar(piece);
13519         }
13520
13521         if( q == p ) *p++ = '-';
13522         *p++ = ']';
13523         *p++ = ' ';
13524     }
13525
13526     /* Active color */
13527     *p++ = whiteToPlay ? 'w' : 'b';
13528     *p++ = ' ';
13529
13530   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13531     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13532   } else {
13533   if(nrCastlingRights) {
13534      q = p;
13535      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13536        /* [HGM] write directly from rights */
13537            if(castlingRights[move][2] >= 0 &&
13538               castlingRights[move][0] >= 0   )
13539                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13540            if(castlingRights[move][2] >= 0 &&
13541               castlingRights[move][1] >= 0   )
13542                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13543            if(castlingRights[move][5] >= 0 &&
13544               castlingRights[move][3] >= 0   )
13545                 *p++ = castlingRights[move][3] + AAA;
13546            if(castlingRights[move][5] >= 0 &&
13547               castlingRights[move][4] >= 0   )
13548                 *p++ = castlingRights[move][4] + AAA;
13549      } else {
13550
13551         /* [HGM] write true castling rights */
13552         if( nrCastlingRights == 6 ) {
13553             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13554                castlingRights[move][2] >= 0  ) *p++ = 'K';
13555             if(castlingRights[move][1] == BOARD_LEFT &&
13556                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13557             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13558                castlingRights[move][5] >= 0  ) *p++ = 'k';
13559             if(castlingRights[move][4] == BOARD_LEFT &&
13560                castlingRights[move][5] >= 0  ) *p++ = 'q';
13561         }
13562      }
13563      if (q == p) *p++ = '-'; /* No castling rights */
13564      *p++ = ' ';
13565   }
13566
13567   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13568      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13569     /* En passant target square */
13570     if (move > backwardMostMove) {
13571         fromX = moveList[move - 1][0] - AAA;
13572         fromY = moveList[move - 1][1] - ONE;
13573         toX = moveList[move - 1][2] - AAA;
13574         toY = moveList[move - 1][3] - ONE;
13575         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13576             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13577             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13578             fromX == toX) {
13579             /* 2-square pawn move just happened */
13580             *p++ = toX + AAA;
13581             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13582         } else {
13583             *p++ = '-';
13584         }
13585     } else if(move == backwardMostMove) {
13586         // [HGM] perhaps we should always do it like this, and forget the above?
13587         if(epStatus[move] >= 0) {
13588             *p++ = epStatus[move] + AAA;
13589             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13590         } else {
13591             *p++ = '-';
13592         }
13593     } else {
13594         *p++ = '-';
13595     }
13596     *p++ = ' ';
13597   }
13598   }
13599
13600     /* [HGM] find reversible plies */
13601     {   int i = 0, j=move;
13602
13603         if (appData.debugMode) { int k;
13604             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13605             for(k=backwardMostMove; k<=forwardMostMove; k++)
13606                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13607
13608         }
13609
13610         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13611         if( j == backwardMostMove ) i += initialRulePlies;
13612         sprintf(p, "%d ", i);
13613         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13614     }
13615     /* Fullmove number */
13616     sprintf(p, "%d", (move / 2) + 1);
13617
13618     return StrSave(buf);
13619 }
13620
13621 Boolean
13622 ParseFEN(board, blackPlaysFirst, fen)
13623     Board board;
13624      int *blackPlaysFirst;
13625      char *fen;
13626 {
13627     int i, j;
13628     char *p;
13629     int emptycount;
13630     ChessSquare piece;
13631
13632     p = fen;
13633
13634     /* [HGM] by default clear Crazyhouse holdings, if present */
13635     if(gameInfo.holdingsWidth) {
13636        for(i=0; i<BOARD_HEIGHT; i++) {
13637            board[i][0]             = EmptySquare; /* black holdings */
13638            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13639            board[i][1]             = (ChessSquare) 0; /* black counts */
13640            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13641        }
13642     }
13643
13644     /* Piece placement data */
13645     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13646         j = 0;
13647         for (;;) {
13648             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13649                 if (*p == '/') p++;
13650                 emptycount = gameInfo.boardWidth - j;
13651                 while (emptycount--)
13652                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13653                 break;
13654 #if(BOARD_SIZE >= 10)
13655             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13656                 p++; emptycount=10;
13657                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13658                 while (emptycount--)
13659                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13660 #endif
13661             } else if (isdigit(*p)) {
13662                 emptycount = *p++ - '0';
13663                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13664                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13665                 while (emptycount--)
13666                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13667             } else if (*p == '+' || isalpha(*p)) {
13668                 if (j >= gameInfo.boardWidth) return FALSE;
13669                 if(*p=='+') {
13670                     piece = CharToPiece(*++p);
13671                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13672                     piece = (ChessSquare) (PROMOTED piece ); p++;
13673                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13674                 } else piece = CharToPiece(*p++);
13675
13676                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13677                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13678                     piece = (ChessSquare) (PROMOTED piece);
13679                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13680                     p++;
13681                 }
13682                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13683             } else {
13684                 return FALSE;
13685             }
13686         }
13687     }
13688     while (*p == '/' || *p == ' ') p++;
13689
13690     /* [HGM] look for Crazyhouse holdings here */
13691     while(*p==' ') p++;
13692     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13693         if(*p == '[') p++;
13694         if(*p == '-' ) *p++; /* empty holdings */ else {
13695             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13696             /* if we would allow FEN reading to set board size, we would   */
13697             /* have to add holdings and shift the board read so far here   */
13698             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13699                 *p++;
13700                 if((int) piece >= (int) BlackPawn ) {
13701                     i = (int)piece - (int)BlackPawn;
13702                     i = PieceToNumber((ChessSquare)i);
13703                     if( i >= gameInfo.holdingsSize ) return FALSE;
13704                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13705                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13706                 } else {
13707                     i = (int)piece - (int)WhitePawn;
13708                     i = PieceToNumber((ChessSquare)i);
13709                     if( i >= gameInfo.holdingsSize ) return FALSE;
13710                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13711                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13712                 }
13713             }
13714         }
13715         if(*p == ']') *p++;
13716     }
13717
13718     while(*p == ' ') p++;
13719
13720     /* Active color */
13721     switch (*p++) {
13722       case 'w':
13723         *blackPlaysFirst = FALSE;
13724         break;
13725       case 'b':
13726         *blackPlaysFirst = TRUE;
13727         break;
13728       default:
13729         return FALSE;
13730     }
13731
13732     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13733     /* return the extra info in global variiables             */
13734
13735     /* set defaults in case FEN is incomplete */
13736     FENepStatus = EP_UNKNOWN;
13737     for(i=0; i<nrCastlingRights; i++ ) {
13738         FENcastlingRights[i] =
13739             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13740     }   /* assume possible unless obviously impossible */
13741     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13742     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13743     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13744     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13745     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13746     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13747     FENrulePlies = 0;
13748
13749     while(*p==' ') p++;
13750     if(nrCastlingRights) {
13751       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13752           /* castling indicator present, so default becomes no castlings */
13753           for(i=0; i<nrCastlingRights; i++ ) {
13754                  FENcastlingRights[i] = -1;
13755           }
13756       }
13757       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13758              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13759              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13760              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13761         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13762
13763         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13764             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13765             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13766         }
13767         switch(c) {
13768           case'K':
13769               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13770               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13771               FENcastlingRights[2] = whiteKingFile;
13772               break;
13773           case'Q':
13774               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13775               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13776               FENcastlingRights[2] = whiteKingFile;
13777               break;
13778           case'k':
13779               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13780               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13781               FENcastlingRights[5] = blackKingFile;
13782               break;
13783           case'q':
13784               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13785               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13786               FENcastlingRights[5] = blackKingFile;
13787           case '-':
13788               break;
13789           default: /* FRC castlings */
13790               if(c >= 'a') { /* black rights */
13791                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13792                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13793                   if(i == BOARD_RGHT) break;
13794                   FENcastlingRights[5] = i;
13795                   c -= AAA;
13796                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
13797                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
13798                   if(c > i)
13799                       FENcastlingRights[3] = c;
13800                   else
13801                       FENcastlingRights[4] = c;
13802               } else { /* white rights */
13803                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13804                     if(board[0][i] == WhiteKing) break;
13805                   if(i == BOARD_RGHT) break;
13806                   FENcastlingRights[2] = i;
13807                   c -= AAA - 'a' + 'A';
13808                   if(board[0][c] >= WhiteKing) break;
13809                   if(c > i)
13810                       FENcastlingRights[0] = c;
13811                   else
13812                       FENcastlingRights[1] = c;
13813               }
13814         }
13815       }
13816     if (appData.debugMode) {
13817         fprintf(debugFP, "FEN castling rights:");
13818         for(i=0; i<nrCastlingRights; i++)
13819         fprintf(debugFP, " %d", FENcastlingRights[i]);
13820         fprintf(debugFP, "\n");
13821     }
13822
13823       while(*p==' ') p++;
13824     }
13825
13826     /* read e.p. field in games that know e.p. capture */
13827     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13828        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13829       if(*p=='-') {
13830         p++; FENepStatus = EP_NONE;
13831       } else {
13832          char c = *p++ - AAA;
13833
13834          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13835          if(*p >= '0' && *p <='9') *p++;
13836          FENepStatus = c;
13837       }
13838     }
13839
13840
13841     if(sscanf(p, "%d", &i) == 1) {
13842         FENrulePlies = i; /* 50-move ply counter */
13843         /* (The move number is still ignored)    */
13844     }
13845
13846     return TRUE;
13847 }
13848
13849 void
13850 EditPositionPasteFEN(char *fen)
13851 {
13852   if (fen != NULL) {
13853     Board initial_position;
13854
13855     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13856       DisplayError(_("Bad FEN position in clipboard"), 0);
13857       return ;
13858     } else {
13859       int savedBlackPlaysFirst = blackPlaysFirst;
13860       EditPositionEvent();
13861       blackPlaysFirst = savedBlackPlaysFirst;
13862       CopyBoard(boards[0], initial_position);
13863           /* [HGM] copy FEN attributes as well */
13864           {   int i;
13865               initialRulePlies = FENrulePlies;
13866               epStatus[0] = FENepStatus;
13867               for( i=0; i<nrCastlingRights; i++ )
13868                   castlingRights[0][i] = FENcastlingRights[i];
13869           }
13870       EditPositionDone();
13871       DisplayBothClocks();
13872       DrawPosition(FALSE, boards[currentMove]);
13873     }
13874   }
13875 }