Merge commit 'v4.4.1' into 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
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
241 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
249 int chattingPartner;
250
251 /* States for ics_getting_history */
252 #define H_FALSE 0
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
258
259 /* whosays values for GameEnds */
260 #define GE_ICS 0
261 #define GE_ENGINE 1
262 #define GE_PLAYER 2
263 #define GE_FILE 3
264 #define GE_XBOARD 4
265 #define GE_ENGINE1 5
266 #define GE_ENGINE2 6
267
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
270
271 /* Different types of move when calling RegisterMove */
272 #define CMAIL_MOVE   0
273 #define CMAIL_RESIGN 1
274 #define CMAIL_DRAW   2
275 #define CMAIL_ACCEPT 3
276
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
281
282 /* Telnet protocol constants */
283 #define TN_WILL 0373
284 #define TN_WONT 0374
285 #define TN_DO   0375
286 #define TN_DONT 0376
287 #define TN_IAC  0377
288 #define TN_ECHO 0001
289 #define TN_SGA  0003
290 #define TN_PORT 23
291
292 /* [AS] */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
294 {
295     assert( dst != NULL );
296     assert( src != NULL );
297     assert( count > 0 );
298
299     strncpy( dst, src, count );
300     dst[ count-1 ] = '\0';
301     return dst;
302 }
303
304 /* Some compiler can't cast u64 to double
305  * This function do the job for us:
306
307  * We use the highest bit for cast, this only
308  * works if the highest bit is not
309  * in use (This should not happen)
310  *
311  * We used this for all compiler
312  */
313 double
314 u64ToDouble(u64 value)
315 {
316   double r;
317   u64 tmp = value & u64Const(0x7fffffffffffffff);
318   r = (double)(s64)tmp;
319   if (value & u64Const(0x8000000000000000))
320        r +=  9.2233720368547758080e18; /* 2^63 */
321  return r;
322 }
323
324 /* Fake up flags for now, as we aren't keeping track of castling
325    availability yet. [HGM] Change of logic: the flag now only
326    indicates the type of castlings allowed by the rule of the game.
327    The actual rights themselves are maintained in the array
328    castlingRights, as part of the game history, and are not probed
329    by this function.
330  */
331 int
332 PosFlags(index)
333 {
334   int flags = F_ALL_CASTLE_OK;
335   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336   switch (gameInfo.variant) {
337   case VariantSuicide:
338     flags &= ~F_ALL_CASTLE_OK;
339   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340     flags |= F_IGNORE_CHECK;
341   case VariantLosers:
342     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343     break;
344   case VariantAtomic:
345     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
346     break;
347   case VariantKriegspiel:
348     flags |= F_KRIEGSPIEL_CAPTURE;
349     break;
350   case VariantCapaRandom:
351   case VariantFischeRandom:
352     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353   case VariantNoCastle:
354   case VariantShatranj:
355   case VariantCourier:
356     flags &= ~F_ALL_CASTLE_OK;
357     break;
358   default:
359     break;
360   }
361   return flags;
362 }
363
364 FILE *gameFileFP, *debugFP;
365
366 /*
367     [AS] Note: sometimes, the sscanf() function is used to parse the input
368     into a fixed-size buffer. Because of this, we must be prepared to
369     receive strings as long as the size of the input buffer, which is currently
370     set to 4K for Windows and 8K for the rest.
371     So, we must either allocate sufficiently large buffers here, or
372     reduce the size of the input buffer in the input reading part.
373 */
374
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
378
379 ChessProgramState first, second;
380
381 /* premove variables */
382 int premoveToX = 0;
383 int premoveToY = 0;
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
387 int gotPremove = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
390
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
393
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
421 int movesPerSession;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
426 int matchGame = 0;
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
430
431 /* animateTraining preserves the state of appData.animate
432  * when Training mode is activated. This allows the
433  * response to be animated when appData.animate == TRUE and
434  * appData.animateDragging == TRUE.
435  */
436 Boolean animateTraining;
437
438 GameInfo gameInfo;
439
440 AppData appData;
441
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char  epStatus[MAX_MOVES];
445 signed char  castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char  castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char  initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int   initialRulePlies, FENrulePlies;
450 char  FENepStatus;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0;
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 ChessSquare  FIDEArray[2][BOARD_SIZE] = {
457     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460         BlackKing, BlackBishop, BlackKnight, BlackRook }
461 };
462
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467         BlackKing, BlackKing, BlackKnight, BlackRook }
468 };
469
470 ChessSquare  KnightmateArray[2][BOARD_SIZE] = {
471     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473     { BlackRook, BlackMan, BlackBishop, BlackQueen,
474         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 };
476
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481         BlackKing, BlackBishop, BlackKnight, BlackRook }
482 };
483
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 };
490
491
492 #if (BOARD_SIZE>=10)
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 };
499
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 };
506
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 };
513
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 };
520
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 };
527
528 #ifdef GOTHIC
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 };
535 #else // !GOTHIC
536 #define GothicArray CapablancaArray
537 #endif // !GOTHIC
538
539 #ifdef FALCON
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 };
546 #else // !FALCON
547 #define FalconArray CapablancaArray
548 #endif // !FALCON
549
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
556
557 #if (BOARD_SIZE>=12)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
563 };
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
567
568
569 Board initialPosition;
570
571
572 /* Convert str to a rating. Checks for special cases of "----",
573
574    "++++", etc. Also strips ()'s */
575 int
576 string_to_rating(str)
577   char *str;
578 {
579   while(*str && !isdigit(*str)) ++str;
580   if (!*str)
581     return 0;   /* One of the special "no rating" cases */
582   else
583     return atoi(str);
584 }
585
586 void
587 ClearProgramStats()
588 {
589     /* Init programStats */
590     programStats.movelist[0] = 0;
591     programStats.depth = 0;
592     programStats.nr_moves = 0;
593     programStats.moves_left = 0;
594     programStats.nodes = 0;
595     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
596     programStats.score = 0;
597     programStats.got_only_move = 0;
598     programStats.got_fail = 0;
599     programStats.line_is_book = 0;
600 }
601
602 void
603 InitBackEnd1()
604 {
605     int matched, min, sec;
606
607     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
608
609     GetTimeMark(&programStartTime);
610     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
611
612     ClearProgramStats();
613     programStats.ok_to_send = 1;
614     programStats.seen_stat = 0;
615
616     /*
617      * Initialize game list
618      */
619     ListNew(&gameList);
620
621
622     /*
623      * Internet chess server status
624      */
625     if (appData.icsActive) {
626         appData.matchMode = FALSE;
627         appData.matchGames = 0;
628 #if ZIPPY
629         appData.noChessProgram = !appData.zippyPlay;
630 #else
631         appData.zippyPlay = FALSE;
632         appData.zippyTalk = FALSE;
633         appData.noChessProgram = TRUE;
634 #endif
635         if (*appData.icsHelper != NULLCHAR) {
636             appData.useTelnet = TRUE;
637             appData.telnetProgram = appData.icsHelper;
638         }
639     } else {
640         appData.zippyTalk = appData.zippyPlay = FALSE;
641     }
642
643     /* [AS] Initialize pv info list [HGM] and game state */
644     {
645         int i, j;
646
647         for( i=0; i<MAX_MOVES; i++ ) {
648             pvInfoList[i].depth = -1;
649             epStatus[i]=EP_NONE;
650             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
651         }
652     }
653
654     /*
655      * Parse timeControl resource
656      */
657     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658                           appData.movesPerSession)) {
659         char buf[MSG_SIZ];
660         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661         DisplayFatalError(buf, 0, 2);
662     }
663
664     /*
665      * Parse searchTime resource
666      */
667     if (*appData.searchTime != NULLCHAR) {
668         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
669         if (matched == 1) {
670             searchTime = min * 60;
671         } else if (matched == 2) {
672             searchTime = min * 60 + sec;
673         } else {
674             char buf[MSG_SIZ];
675             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676             DisplayFatalError(buf, 0, 2);
677         }
678     }
679
680     /* [AS] Adjudication threshold */
681     adjudicateLossThreshold = appData.adjudicateLossThreshold;
682
683     first.which = "first";
684     second.which = "second";
685     first.maybeThinking = second.maybeThinking = FALSE;
686     first.pr = second.pr = NoProc;
687     first.isr = second.isr = NULL;
688     first.sendTime = second.sendTime = 2;
689     first.sendDrawOffers = 1;
690     if (appData.firstPlaysBlack) {
691         first.twoMachinesColor = "black\n";
692         second.twoMachinesColor = "white\n";
693     } else {
694         first.twoMachinesColor = "white\n";
695         second.twoMachinesColor = "black\n";
696     }
697     first.program = appData.firstChessProgram;
698     second.program = appData.secondChessProgram;
699     first.host = appData.firstHost;
700     second.host = appData.secondHost;
701     first.dir = appData.firstDirectory;
702     second.dir = appData.secondDirectory;
703     first.other = &second;
704     second.other = &first;
705     first.initString = appData.initString;
706     second.initString = appData.secondInitString;
707     first.computerString = appData.firstComputerString;
708     second.computerString = appData.secondComputerString;
709     first.useSigint = second.useSigint = TRUE;
710     first.useSigterm = second.useSigterm = TRUE;
711     first.reuse = appData.reuseFirst;
712     second.reuse = appData.reuseSecond;
713     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
714     second.nps = appData.secondNPS;
715     first.useSetboard = second.useSetboard = FALSE;
716     first.useSAN = second.useSAN = FALSE;
717     first.usePing = second.usePing = FALSE;
718     first.lastPing = second.lastPing = 0;
719     first.lastPong = second.lastPong = 0;
720     first.usePlayother = second.usePlayother = FALSE;
721     first.useColors = second.useColors = TRUE;
722     first.useUsermove = second.useUsermove = FALSE;
723     first.sendICS = second.sendICS = FALSE;
724     first.sendName = second.sendName = appData.icsActive;
725     first.sdKludge = second.sdKludge = FALSE;
726     first.stKludge = second.stKludge = FALSE;
727     TidyProgramName(first.program, first.host, first.tidy);
728     TidyProgramName(second.program, second.host, second.tidy);
729     first.matchWins = second.matchWins = 0;
730     strcpy(first.variants, appData.variant);
731     strcpy(second.variants, appData.variant);
732     first.analysisSupport = second.analysisSupport = 2; /* detect */
733     first.analyzing = second.analyzing = FALSE;
734     first.initDone = second.initDone = FALSE;
735
736     /* New features added by Tord: */
737     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739     /* End of new features added by Tord. */
740     first.fenOverride  = appData.fenOverride1;
741     second.fenOverride = appData.fenOverride2;
742
743     /* [HGM] time odds: set factor for each machine */
744     first.timeOdds  = appData.firstTimeOdds;
745     second.timeOdds = appData.secondTimeOdds;
746     { int norm = 1;
747         if(appData.timeOddsMode) {
748             norm = first.timeOdds;
749             if(norm > second.timeOdds) norm = second.timeOdds;
750         }
751         first.timeOdds /= norm;
752         second.timeOdds /= norm;
753     }
754
755     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756     first.accumulateTC = appData.firstAccumulateTC;
757     second.accumulateTC = appData.secondAccumulateTC;
758     first.maxNrOfSessions = second.maxNrOfSessions = 1;
759
760     /* [HGM] debug */
761     first.debug = second.debug = FALSE;
762     first.supportsNPS = second.supportsNPS = UNKNOWN;
763
764     /* [HGM] options */
765     first.optionSettings  = appData.firstOptions;
766     second.optionSettings = appData.secondOptions;
767
768     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770     first.isUCI = appData.firstIsUCI; /* [AS] */
771     second.isUCI = appData.secondIsUCI; /* [AS] */
772     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
774
775     if (appData.firstProtocolVersion > PROTOVER ||
776         appData.firstProtocolVersion < 1) {
777       char buf[MSG_SIZ];
778       sprintf(buf, _("protocol version %d not supported"),
779               appData.firstProtocolVersion);
780       DisplayFatalError(buf, 0, 2);
781     } else {
782       first.protocolVersion = appData.firstProtocolVersion;
783     }
784
785     if (appData.secondProtocolVersion > PROTOVER ||
786         appData.secondProtocolVersion < 1) {
787       char buf[MSG_SIZ];
788       sprintf(buf, _("protocol version %d not supported"),
789               appData.secondProtocolVersion);
790       DisplayFatalError(buf, 0, 2);
791     } else {
792       second.protocolVersion = appData.secondProtocolVersion;
793     }
794
795     if (appData.icsActive) {
796         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
797     } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798         appData.clockMode = FALSE;
799         first.sendTime = second.sendTime = 0;
800     }
801
802 #if ZIPPY
803     /* Override some settings from environment variables, for backward
804        compatibility.  Unfortunately it's not feasible to have the env
805        vars just set defaults, at least in xboard.  Ugh.
806     */
807     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
808       ZippyInit();
809     }
810 #endif
811
812     if (appData.noChessProgram) {
813         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814         sprintf(programVersion, "%s", PACKAGE_STRING);
815     } else {
816       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819     }
820
821     if (!appData.icsActive) {
822       char buf[MSG_SIZ];
823       /* Check for variants that are supported only in ICS mode,
824          or not at all.  Some that are accepted here nevertheless
825          have bugs; see comments below.
826       */
827       VariantClass variant = StringToVariant(appData.variant);
828       switch (variant) {
829       case VariantBughouse:     /* need four players and two boards */
830       case VariantKriegspiel:   /* need to hide pieces and move details */
831       /* case VariantFischeRandom: (Fabien: moved below) */
832         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833         DisplayFatalError(buf, 0, 2);
834         return;
835
836       case VariantUnknown:
837       case VariantLoadable:
838       case Variant29:
839       case Variant30:
840       case Variant31:
841       case Variant32:
842       case Variant33:
843       case Variant34:
844       case Variant35:
845       case Variant36:
846       default:
847         sprintf(buf, _("Unknown variant name %s"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
852       case VariantFairy:      /* [HGM] TestLegality definitely off! */
853       case VariantGothic:     /* [HGM] should work */
854       case VariantCapablanca: /* [HGM] should work */
855       case VariantCourier:    /* [HGM] initial forced moves not implemented */
856       case VariantShogi:      /* [HGM] drops not tested for legality */
857       case VariantKnightmate: /* [HGM] should work */
858       case VariantCylinder:   /* [HGM] untested */
859       case VariantFalcon:     /* [HGM] untested */
860       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861                                  offboard interposition not understood */
862       case VariantNormal:     /* definitely works! */
863       case VariantWildCastle: /* pieces not automatically shuffled */
864       case VariantNoCastle:   /* pieces not automatically shuffled */
865       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866       case VariantLosers:     /* should work except for win condition,
867                                  and doesn't know captures are mandatory */
868       case VariantSuicide:    /* should work except for win condition,
869                                  and doesn't know captures are mandatory */
870       case VariantGiveaway:   /* should work except for win condition,
871                                  and doesn't know captures are mandatory */
872       case VariantTwoKings:   /* should work */
873       case VariantAtomic:     /* should work except for win condition */
874       case Variant3Check:     /* should work except for win condition */
875       case VariantShatranj:   /* should work except for all win conditions */
876       case VariantBerolina:   /* might work if TestLegality is off */
877       case VariantCapaRandom: /* should work */
878       case VariantJanus:      /* should work */
879       case VariantSuper:      /* experimental */
880       case VariantGreat:      /* experimental, requires legality testing to be off */
881         break;
882       }
883     }
884
885     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
886     InitEngineUCI( installDir, &second );
887 }
888
889 int NextIntegerFromString( char ** str, long * value )
890 {
891     int result = -1;
892     char * s = *str;
893
894     while( *s == ' ' || *s == '\t' ) {
895         s++;
896     }
897
898     *value = 0;
899
900     if( *s >= '0' && *s <= '9' ) {
901         while( *s >= '0' && *s <= '9' ) {
902             *value = *value * 10 + (*s - '0');
903             s++;
904         }
905
906         result = 0;
907     }
908
909     *str = s;
910
911     return result;
912 }
913
914 int NextTimeControlFromString( char ** str, long * value )
915 {
916     long temp;
917     int result = NextIntegerFromString( str, &temp );
918
919     if( result == 0 ) {
920         *value = temp * 60; /* Minutes */
921         if( **str == ':' ) {
922             (*str)++;
923             result = NextIntegerFromString( str, &temp );
924             *value += temp; /* Seconds */
925         }
926     }
927
928     return result;
929 }
930
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
933     int result = -1; long temp, temp2;
934
935     if(**str != '+') return -1; // old params remain in force!
936     (*str)++;
937     if( NextTimeControlFromString( str, &temp ) ) return -1;
938
939     if(**str != '/') {
940         /* time only: incremental or sudden-death time control */
941         if(**str == '+') { /* increment follows; read it */
942             (*str)++;
943             if(result = NextIntegerFromString( str, &temp2)) return -1;
944             *inc = temp2 * 1000;
945         } else *inc = 0;
946         *moves = 0; *tc = temp * 1000;
947         return 0;
948     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
949
950     (*str)++; /* classical time control */
951     result = NextTimeControlFromString( str, &temp2);
952     if(result == 0) {
953         *moves = temp/60;
954         *tc    = temp2 * 1000;
955         *inc   = 0;
956     }
957     return result;
958 }
959
960 int GetTimeQuota(int movenr)
961 {   /* [HGM] get time to add from the multi-session time-control string */
962     int moves=1; /* kludge to force reading of first session */
963     long time, increment;
964     char *s = fullTimeControlString;
965
966     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
967     do {
968         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970         if(movenr == -1) return time;    /* last move before new session     */
971         if(!moves) return increment;     /* current session is incremental   */
972         if(movenr >= 0) movenr -= moves; /* we already finished this session */
973     } while(movenr >= -1);               /* try again for next session       */
974
975     return 0; // no new time quota on this move
976 }
977
978 int
979 ParseTimeControl(tc, ti, mps)
980      char *tc;
981      int ti;
982      int mps;
983 {
984   long tc1;
985   long tc2;
986   char buf[MSG_SIZ];
987   
988   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989   if(ti > 0) {
990     if(mps)
991       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992     else sprintf(buf, "+%s+%d", tc, ti);
993   } else {
994     if(mps)
995              sprintf(buf, "+%d/%s", mps, tc);
996     else sprintf(buf, "+%s", tc);
997   }
998   fullTimeControlString = StrSave(buf);
999   
1000   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1001     return FALSE;
1002   }
1003   
1004   if( *tc == '/' ) {
1005     /* Parse second time control */
1006     tc++;
1007     
1008     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1009       return FALSE;
1010     }
1011     
1012     if( tc2 == 0 ) {
1013       return FALSE;
1014     }
1015     
1016     timeControl_2 = tc2 * 1000;
1017   }
1018   else {
1019     timeControl_2 = 0;
1020   }
1021   
1022   if( tc1 == 0 ) {
1023     return FALSE;
1024   }
1025   
1026   timeControl = tc1 * 1000;
1027   
1028   if (ti >= 0) {
1029     timeIncrement = ti * 1000;  /* convert to ms */
1030     movesPerSession = 0;
1031   } else {
1032     timeIncrement = 0;
1033     movesPerSession = mps;
1034   }
1035   return TRUE;
1036 }
1037
1038 void
1039 InitBackEnd2()
1040 {
1041   if (appData.debugMode) {
1042     fprintf(debugFP, "%s\n", programVersion);
1043   }
1044
1045   set_cont_sequence(appData.wrapContSeq);
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     if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908         return; // prevent overwriting by pre-board holdings
1909
1910     if( (int)lowestPiece >= BlackPawn ) {
1911         holdingsColumn = 0;
1912         countsColumn = 1;
1913         holdingsStartRow = BOARD_HEIGHT-1;
1914         direction = -1;
1915     } else {
1916         holdingsColumn = BOARD_WIDTH-1;
1917         countsColumn = BOARD_WIDTH-2;
1918         holdingsStartRow = 0;
1919         direction = 1;
1920     }
1921
1922     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923         board[i][holdingsColumn] = EmptySquare;
1924         board[i][countsColumn]   = (ChessSquare) 0;
1925     }
1926     while( (p=*holdings++) != NULLCHAR ) {
1927         piece = CharToPiece( ToUpper(p) );
1928         if(piece == EmptySquare) continue;
1929         /*j = (int) piece - (int) WhitePawn;*/
1930         j = PieceToNumber(piece);
1931         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932         if(j < 0) continue;               /* should not happen */
1933         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935         board[holdingsStartRow+j*direction][countsColumn]++;
1936     }
1937 }
1938
1939
1940 void
1941 VariantSwitch(Board board, VariantClass newVariant)
1942 {
1943    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944    Board oldBoard;
1945
1946    startedFromPositionFile = FALSE;
1947    if(gameInfo.variant == newVariant) return;
1948
1949    /* [HGM] This routine is called each time an assignment is made to
1950     * gameInfo.variant during a game, to make sure the board sizes
1951     * are set to match the new variant. If that means adding or deleting
1952     * holdings, we shift the playing board accordingly
1953     * This kludge is needed because in ICS observe mode, we get boards
1954     * of an ongoing game without knowing the variant, and learn about the
1955     * latter only later. This can be because of the move list we requested,
1956     * in which case the game history is refilled from the beginning anyway,
1957     * but also when receiving holdings of a crazyhouse game. In the latter
1958     * case we want to add those holdings to the already received position.
1959     */
1960    
1961    if (appData.debugMode) {
1962      fprintf(debugFP, "Switch board from %s to %s\n",
1963              VariantName(gameInfo.variant), VariantName(newVariant));
1964      setbuf(debugFP, NULL);
1965    }
1966    shuffleOpenings = 0;       /* [HGM] shuffle */
1967    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1968    switch(newVariant) 
1969      {
1970      case VariantShogi:
1971        newWidth = 9;  newHeight = 9;
1972        gameInfo.holdingsSize = 7;
1973      case VariantBughouse:
1974      case VariantCrazyhouse:
1975        newHoldingsWidth = 2; break;
1976      case VariantGreat:
1977        newWidth = 10;
1978      case VariantSuper:
1979        newHoldingsWidth = 2;
1980        gameInfo.holdingsSize = 8;
1981        break;
1982      case VariantGothic:
1983      case VariantCapablanca:
1984      case VariantCapaRandom:
1985        newWidth = 10;
1986      default:
1987        newHoldingsWidth = gameInfo.holdingsSize = 0;
1988      };
1989    
1990    if(newWidth  != gameInfo.boardWidth  ||
1991       newHeight != gameInfo.boardHeight ||
1992       newHoldingsWidth != gameInfo.holdingsWidth ) {
1993      
1994      /* shift position to new playing area, if needed */
1995      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996        for(i=0; i<BOARD_HEIGHT; i++) 
1997          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999              board[i][j];
2000        for(i=0; i<newHeight; i++) {
2001          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003        }
2004      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005        for(i=0; i<BOARD_HEIGHT; i++)
2006          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008              board[i][j];
2009      }
2010      gameInfo.boardWidth  = newWidth;
2011      gameInfo.boardHeight = newHeight;
2012      gameInfo.holdingsWidth = newHoldingsWidth;
2013      gameInfo.variant = newVariant;
2014      InitDrawingSizes(-2, 0);
2015    } else gameInfo.variant = newVariant;
2016    CopyBoard(oldBoard, board);   // remember correctly formatted board
2017      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2018    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2019 }
2020
2021 static int loggedOn = FALSE;
2022
2023 /*-- Game start info cache: --*/
2024 int gs_gamenum;
2025 char gs_kind[MSG_SIZ];
2026 static char player1Name[128] = "";
2027 static char player2Name[128] = "";
2028 static char cont_seq[] = "\n\\   ";
2029 static int player1Rating = -1;
2030 static int player2Rating = -1;
2031 /*----------------------------*/
2032
2033 ColorClass curColor = ColorNormal;
2034 int suppressKibitz = 0;
2035
2036 void
2037 read_from_ics(isr, closure, data, count, error)
2038      InputSourceRef isr;
2039      VOIDSTAR closure;
2040      char *data;
2041      int count;
2042      int error;
2043 {
2044 #define BUF_SIZE 8192
2045 #define STARTED_NONE 0
2046 #define STARTED_MOVES 1
2047 #define STARTED_BOARD 2
2048 #define STARTED_OBSERVE 3
2049 #define STARTED_HOLDINGS 4
2050 #define STARTED_CHATTER 5
2051 #define STARTED_COMMENT 6
2052 #define STARTED_MOVES_NOHIDE 7
2053
2054     static int started = STARTED_NONE;
2055     static char parse[20000];
2056     static int parse_pos = 0;
2057     static char buf[BUF_SIZE + 1];
2058     static int firstTime = TRUE, intfSet = FALSE;
2059     static ColorClass prevColor = ColorNormal;
2060     static int savingComment = FALSE;
2061     static int cmatch = 0; // continuation sequence match
2062     char *bp;
2063     char str[500];
2064     int i, oldi;
2065     int buf_len;
2066     int next_out;
2067     int tkind;
2068     int backup;    /* [DM] For zippy color lines */
2069     char *p;
2070     char talker[MSG_SIZ]; // [HGM] chat
2071     int channel;
2072
2073     if (appData.debugMode) {
2074       if (!error) {
2075         fprintf(debugFP, "<ICS: ");
2076         show_bytes(debugFP, data, count);
2077         fprintf(debugFP, "\n");
2078       }
2079     }
2080
2081     if (appData.debugMode) { int f = forwardMostMove;
2082         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2083                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2084     }
2085     if (count > 0) {
2086         /* If last read ended with a partial line that we couldn't parse,
2087            prepend it to the new read and try again. */
2088         if (leftover_len > 0) {
2089             for (i=0; i<leftover_len; i++)
2090               buf[i] = buf[leftover_start + i];
2091         }
2092
2093     /* copy new characters into the buffer */
2094     bp = buf + leftover_len;
2095     buf_len=leftover_len;
2096     for (i=0; i<count; i++)
2097     {
2098         // ignore these
2099         if (data[i] == '\r')
2100             continue;
2101
2102         // join lines split by ICS?
2103         if (!appData.noJoin)
2104         {
2105             /*
2106                 Joining just consists of finding matches against the
2107                 continuation sequence, and discarding that sequence
2108                 if found instead of copying it.  So, until a match
2109                 fails, there's nothing to do since it might be the
2110                 complete sequence, and thus, something we don't want
2111                 copied.
2112             */
2113             if (data[i] == cont_seq[cmatch])
2114             {
2115                 cmatch++;
2116                 if (cmatch == strlen(cont_seq))
2117                 {
2118                     cmatch = 0; // complete match.  just reset the counter
2119
2120                     /*
2121                         it's possible for the ICS to not include the space
2122                         at the end of the last word, making our [correct]
2123                         join operation fuse two separate words.  the server
2124                         does this when the space occurs at the width setting.
2125                     */
2126                     if (!buf_len || buf[buf_len-1] != ' ')
2127                     {
2128                         *bp++ = ' ';
2129                         buf_len++;
2130                     }
2131                 }
2132                 continue;
2133             }
2134             else if (cmatch)
2135             {
2136                 /*
2137                     match failed, so we have to copy what matched before
2138                     falling through and copying this character.  In reality,
2139                     this will only ever be just the newline character, but
2140                     it doesn't hurt to be precise.
2141                 */
2142                 strncpy(bp, cont_seq, cmatch);
2143                 bp += cmatch;
2144                 buf_len += cmatch;
2145                 cmatch = 0;
2146             }
2147         }
2148
2149         // copy this char
2150         *bp++ = data[i];
2151         buf_len++;
2152     }
2153
2154         buf[buf_len] = NULLCHAR;
2155         next_out = leftover_len;
2156         leftover_start = 0;
2157
2158         i = 0;
2159         while (i < buf_len) {
2160             /* Deal with part of the TELNET option negotiation
2161                protocol.  We refuse to do anything beyond the
2162                defaults, except that we allow the WILL ECHO option,
2163                which ICS uses to turn off password echoing when we are
2164                directly connected to it.  We reject this option
2165                if localLineEditing mode is on (always on in xboard)
2166                and we are talking to port 23, which might be a real
2167                telnet server that will try to keep WILL ECHO on permanently.
2168              */
2169             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2170                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2171                 unsigned char option;
2172                 oldi = i;
2173                 switch ((unsigned char) buf[++i]) {
2174                   case TN_WILL:
2175                     if (appData.debugMode)
2176                       fprintf(debugFP, "\n<WILL ");
2177                     switch (option = (unsigned char) buf[++i]) {
2178                       case TN_ECHO:
2179                         if (appData.debugMode)
2180                           fprintf(debugFP, "ECHO ");
2181                         /* Reply only if this is a change, according
2182                            to the protocol rules. */
2183                         if (remoteEchoOption) break;
2184                         if (appData.localLineEditing &&
2185                             atoi(appData.icsPort) == TN_PORT) {
2186                             TelnetRequest(TN_DONT, TN_ECHO);
2187                         } else {
2188                             EchoOff();
2189                             TelnetRequest(TN_DO, TN_ECHO);
2190                             remoteEchoOption = TRUE;
2191                         }
2192                         break;
2193                       default:
2194                         if (appData.debugMode)
2195                           fprintf(debugFP, "%d ", option);
2196                         /* Whatever this is, we don't want it. */
2197                         TelnetRequest(TN_DONT, option);
2198                         break;
2199                     }
2200                     break;
2201                   case TN_WONT:
2202                     if (appData.debugMode)
2203                       fprintf(debugFP, "\n<WONT ");
2204                     switch (option = (unsigned char) buf[++i]) {
2205                       case TN_ECHO:
2206                         if (appData.debugMode)
2207                           fprintf(debugFP, "ECHO ");
2208                         /* Reply only if this is a change, according
2209                            to the protocol rules. */
2210                         if (!remoteEchoOption) break;
2211                         EchoOn();
2212                         TelnetRequest(TN_DONT, TN_ECHO);
2213                         remoteEchoOption = FALSE;
2214                         break;
2215                       default:
2216                         if (appData.debugMode)
2217                           fprintf(debugFP, "%d ", (unsigned char) option);
2218                         /* Whatever this is, it must already be turned
2219                            off, because we never agree to turn on
2220                            anything non-default, so according to the
2221                            protocol rules, we don't reply. */
2222                         break;
2223                     }
2224                     break;
2225                   case TN_DO:
2226                     if (appData.debugMode)
2227                       fprintf(debugFP, "\n<DO ");
2228                     switch (option = (unsigned char) buf[++i]) {
2229                       default:
2230                         /* Whatever this is, we refuse to do it. */
2231                         if (appData.debugMode)
2232                           fprintf(debugFP, "%d ", option);
2233                         TelnetRequest(TN_WONT, option);
2234                         break;
2235                     }
2236                     break;
2237                   case TN_DONT:
2238                     if (appData.debugMode)
2239                       fprintf(debugFP, "\n<DONT ");
2240                     switch (option = (unsigned char) buf[++i]) {
2241                       default:
2242                         if (appData.debugMode)
2243                           fprintf(debugFP, "%d ", option);
2244                         /* Whatever this is, we are already not doing
2245                            it, because we never agree to do anything
2246                            non-default, so according to the protocol
2247                            rules, we don't reply. */
2248                         break;
2249                     }
2250                     break;
2251                   case TN_IAC:
2252                     if (appData.debugMode)
2253                       fprintf(debugFP, "\n<IAC ");
2254                     /* Doubled IAC; pass it through */
2255                     i--;
2256                     break;
2257                   default:
2258                     if (appData.debugMode)
2259                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2260                     /* Drop all other telnet commands on the floor */
2261                     break;
2262                 }
2263                 if (oldi > next_out)
2264                   SendToPlayer(&buf[next_out], oldi - next_out);
2265                 if (++i > next_out)
2266                   next_out = i;
2267                 continue;
2268             }
2269
2270             /* OK, this at least will *usually* work */
2271             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2272                 loggedOn = TRUE;
2273             }
2274
2275             if (loggedOn && !intfSet) {
2276                 if (ics_type == ICS_ICC) {
2277                   sprintf(str,
2278                           "/set-quietly interface %s\n/set-quietly style 12\n",
2279                           programVersion);
2280                 } else if (ics_type == ICS_CHESSNET) {
2281                   sprintf(str, "/style 12\n");
2282                 } else {
2283                   strcpy(str, "alias $ @\n$set interface ");
2284                   strcat(str, programVersion);
2285                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2286 #ifdef WIN32
2287                   strcat(str, "$iset nohighlight 1\n");
2288 #endif
2289                   strcat(str, "$iset lock 1\n$style 12\n");
2290                 }
2291                 SendToICS(str);
2292                 NotifyFrontendLogin();
2293                 intfSet = TRUE;
2294             }
2295
2296             if (started == STARTED_COMMENT) {
2297                 /* Accumulate characters in comment */
2298                 parse[parse_pos++] = buf[i];
2299                 if (buf[i] == '\n') {
2300                     parse[parse_pos] = NULLCHAR;
2301                     if(chattingPartner>=0) {
2302                         char mess[MSG_SIZ];
2303                         sprintf(mess, "%s%s", talker, parse);
2304                         OutputChatMessage(chattingPartner, mess);
2305                         chattingPartner = -1;
2306                     } else
2307                     if(!suppressKibitz) // [HGM] kibitz
2308                         AppendComment(forwardMostMove, StripHighlight(parse));
2309                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2310                         int nrDigit = 0, nrAlph = 0, i;
2311                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2312                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2313                         parse[parse_pos] = NULLCHAR;
2314                         // try to be smart: if it does not look like search info, it should go to
2315                         // ICS interaction window after all, not to engine-output window.
2316                         for(i=0; i<parse_pos; i++) { // count letters and digits
2317                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2318                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2319                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2320                         }
2321                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2322                             int depth=0; float score;
2323                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2324                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2325                                 pvInfoList[forwardMostMove-1].depth = depth;
2326                                 pvInfoList[forwardMostMove-1].score = 100*score;
2327                             }
2328                             OutputKibitz(suppressKibitz, parse);
2329                         } else {
2330                             char tmp[MSG_SIZ];
2331                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2332                             SendToPlayer(tmp, strlen(tmp));
2333                         }
2334                     }
2335                     started = STARTED_NONE;
2336                 } else {
2337                     /* Don't match patterns against characters in chatter */
2338                     i++;
2339                     continue;
2340                 }
2341             }
2342             if (started == STARTED_CHATTER) {
2343                 if (buf[i] != '\n') {
2344                     /* Don't match patterns against characters in chatter */
2345                     i++;
2346                     continue;
2347                 }
2348                 started = STARTED_NONE;
2349             }
2350
2351             /* Kludge to deal with rcmd protocol */
2352             if (firstTime && looking_at(buf, &i, "\001*")) {
2353                 DisplayFatalError(&buf[1], 0, 1);
2354                 continue;
2355             } else {
2356                 firstTime = FALSE;
2357             }
2358
2359             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2360                 ics_type = ICS_ICC;
2361                 ics_prefix = "/";
2362                 if (appData.debugMode)
2363                   fprintf(debugFP, "ics_type %d\n", ics_type);
2364                 continue;
2365             }
2366             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2367                 ics_type = ICS_FICS;
2368                 ics_prefix = "$";
2369                 if (appData.debugMode)
2370                   fprintf(debugFP, "ics_type %d\n", ics_type);
2371                 continue;
2372             }
2373             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2374                 ics_type = ICS_CHESSNET;
2375                 ics_prefix = "/";
2376                 if (appData.debugMode)
2377                   fprintf(debugFP, "ics_type %d\n", ics_type);
2378                 continue;
2379             }
2380
2381             if (!loggedOn &&
2382                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2383                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2384                  looking_at(buf, &i, "will be \"*\""))) {
2385               strcpy(ics_handle, star_match[0]);
2386               continue;
2387             }
2388
2389             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2390               char buf[MSG_SIZ];
2391               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2392               DisplayIcsInteractionTitle(buf);
2393               have_set_title = TRUE;
2394             }
2395
2396             /* skip finger notes */
2397             if (started == STARTED_NONE &&
2398                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2399                  (buf[i] == '1' && buf[i+1] == '0')) &&
2400                 buf[i+2] == ':' && buf[i+3] == ' ') {
2401               started = STARTED_CHATTER;
2402               i += 3;
2403               continue;
2404             }
2405
2406             /* skip formula vars */
2407             if (started == STARTED_NONE &&
2408                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2409               started = STARTED_CHATTER;
2410               i += 3;
2411               continue;
2412             }
2413
2414             oldi = i;
2415             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2416             if (appData.autoKibitz && started == STARTED_NONE &&
2417                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2418                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2419                 if(looking_at(buf, &i, "* kibitzes: ") &&
2420                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2421                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2422                         suppressKibitz = TRUE;
2423                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2424                                 && (gameMode == IcsPlayingWhite)) ||
2425                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2426                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2427                             started = STARTED_CHATTER; // own kibitz we simply discard
2428                         else {
2429                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2430                             parse_pos = 0; parse[0] = NULLCHAR;
2431                             savingComment = TRUE;
2432                             suppressKibitz = gameMode != IcsObserving ? 2 :
2433                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2434                         }
2435                         continue;
2436                 } else
2437                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2438                     started = STARTED_CHATTER;
2439                     suppressKibitz = TRUE;
2440                 }
2441             } // [HGM] kibitz: end of patch
2442
2443 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2444
2445             // [HGM] chat: intercept tells by users for which we have an open chat window
2446             channel = -1;
2447             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2448                                            looking_at(buf, &i, "* whispers:") ||
2449                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2450                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2451                 int p;
2452                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2453                 chattingPartner = -1;
2454
2455                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2456                 for(p=0; p<MAX_CHAT; p++) {
2457                     if(channel == atoi(chatPartner[p])) {
2458                     talker[0] = '['; strcat(talker, "]");
2459                     chattingPartner = p; break;
2460                     }
2461                 } else
2462                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2463                 for(p=0; p<MAX_CHAT; p++) {
2464                     if(!strcmp("WHISPER", chatPartner[p])) {
2465                         talker[0] = '['; strcat(talker, "]");
2466                         chattingPartner = p; break;
2467                     }
2468                 }
2469                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2470                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2471                     talker[0] = 0;
2472                     chattingPartner = p; break;
2473                 }
2474                 if(chattingPartner<0) i = oldi; else {
2475                     started = STARTED_COMMENT;
2476                     parse_pos = 0; parse[0] = NULLCHAR;
2477                     savingComment = TRUE;
2478                     suppressKibitz = TRUE;
2479                 }
2480             } // [HGM] chat: end of patch
2481
2482             if (appData.zippyTalk || appData.zippyPlay) {
2483                 /* [DM] Backup address for color zippy lines */
2484                 backup = i;
2485 #if ZIPPY
2486        #ifdef WIN32
2487                if (loggedOn == TRUE)
2488                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2489                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2490        #else
2491                 if (ZippyControl(buf, &i) ||
2492                     ZippyConverse(buf, &i) ||
2493                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2494                       loggedOn = TRUE;
2495                       if (!appData.colorize) continue;
2496                 }
2497        #endif
2498 #endif
2499             } // [DM] 'else { ' deleted
2500                 if (
2501                     /* Regular tells and says */
2502                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2503                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2504                     looking_at(buf, &i, "* says: ") ||
2505                     /* Don't color "message" or "messages" output */
2506                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2507                     looking_at(buf, &i, "*. * at *:*: ") ||
2508                     looking_at(buf, &i, "--* (*:*): ") ||
2509                     /* Message notifications (same color as tells) */
2510                     looking_at(buf, &i, "* has left a message ") ||
2511                     looking_at(buf, &i, "* just sent you a message:\n") ||
2512                     /* Whispers and kibitzes */
2513                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2514                     looking_at(buf, &i, "* kibitzes: ") ||
2515                     /* Channel tells */
2516                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2517
2518                   if (tkind == 1 && strchr(star_match[0], ':')) {
2519                       /* Avoid "tells you:" spoofs in channels */
2520                      tkind = 3;
2521                   }
2522                   if (star_match[0][0] == NULLCHAR ||
2523                       strchr(star_match[0], ' ') ||
2524                       (tkind == 3 && strchr(star_match[1], ' '))) {
2525                     /* Reject bogus matches */
2526                     i = oldi;
2527                   } else {
2528                     if (appData.colorize) {
2529                       if (oldi > next_out) {
2530                         SendToPlayer(&buf[next_out], oldi - next_out);
2531                         next_out = oldi;
2532                       }
2533                       switch (tkind) {
2534                       case 1:
2535                         Colorize(ColorTell, FALSE);
2536                         curColor = ColorTell;
2537                         break;
2538                       case 2:
2539                         Colorize(ColorKibitz, FALSE);
2540                         curColor = ColorKibitz;
2541                         break;
2542                       case 3:
2543                         p = strrchr(star_match[1], '(');
2544                         if (p == NULL) {
2545                           p = star_match[1];
2546                         } else {
2547                           p++;
2548                         }
2549                         if (atoi(p) == 1) {
2550                           Colorize(ColorChannel1, FALSE);
2551                           curColor = ColorChannel1;
2552                         } else {
2553                           Colorize(ColorChannel, FALSE);
2554                           curColor = ColorChannel;
2555                         }
2556                         break;
2557                       case 5:
2558                         curColor = ColorNormal;
2559                         break;
2560                       }
2561                     }
2562                     if (started == STARTED_NONE && appData.autoComment &&
2563                         (gameMode == IcsObserving ||
2564                          gameMode == IcsPlayingWhite ||
2565                          gameMode == IcsPlayingBlack)) {
2566                       parse_pos = i - oldi;
2567                       memcpy(parse, &buf[oldi], parse_pos);
2568                       parse[parse_pos] = NULLCHAR;
2569                       started = STARTED_COMMENT;
2570                       savingComment = TRUE;
2571                     } else {
2572                       started = STARTED_CHATTER;
2573                       savingComment = FALSE;
2574                     }
2575                     loggedOn = TRUE;
2576                     continue;
2577                   }
2578                 }
2579
2580                 if (looking_at(buf, &i, "* s-shouts: ") ||
2581                     looking_at(buf, &i, "* c-shouts: ")) {
2582                     if (appData.colorize) {
2583                         if (oldi > next_out) {
2584                             SendToPlayer(&buf[next_out], oldi - next_out);
2585                             next_out = oldi;
2586                         }
2587                         Colorize(ColorSShout, FALSE);
2588                         curColor = ColorSShout;
2589                     }
2590                     loggedOn = TRUE;
2591                     started = STARTED_CHATTER;
2592                     continue;
2593                 }
2594
2595                 if (looking_at(buf, &i, "--->")) {
2596                     loggedOn = TRUE;
2597                     continue;
2598                 }
2599
2600                 if (looking_at(buf, &i, "* shouts: ") ||
2601                     looking_at(buf, &i, "--> ")) {
2602                     if (appData.colorize) {
2603                         if (oldi > next_out) {
2604                             SendToPlayer(&buf[next_out], oldi - next_out);
2605                             next_out = oldi;
2606                         }
2607                         Colorize(ColorShout, FALSE);
2608                         curColor = ColorShout;
2609                     }
2610                     loggedOn = TRUE;
2611                     started = STARTED_CHATTER;
2612                     continue;
2613                 }
2614
2615                 if (looking_at( buf, &i, "Challenge:")) {
2616                     if (appData.colorize) {
2617                         if (oldi > next_out) {
2618                             SendToPlayer(&buf[next_out], oldi - next_out);
2619                             next_out = oldi;
2620                         }
2621                         Colorize(ColorChallenge, FALSE);
2622                         curColor = ColorChallenge;
2623                     }
2624                     loggedOn = TRUE;
2625                     continue;
2626                 }
2627
2628                 if (looking_at(buf, &i, "* offers you") ||
2629                     looking_at(buf, &i, "* offers to be") ||
2630                     looking_at(buf, &i, "* would like to") ||
2631                     looking_at(buf, &i, "* requests to") ||
2632                     looking_at(buf, &i, "Your opponent offers") ||
2633                     looking_at(buf, &i, "Your opponent requests")) {
2634
2635                     if (appData.colorize) {
2636                         if (oldi > next_out) {
2637                             SendToPlayer(&buf[next_out], oldi - next_out);
2638                             next_out = oldi;
2639                         }
2640                         Colorize(ColorRequest, FALSE);
2641                         curColor = ColorRequest;
2642                     }
2643                     continue;
2644                 }
2645
2646                 if (looking_at(buf, &i, "* (*) seeking")) {
2647                     if (appData.colorize) {
2648                         if (oldi > next_out) {
2649                             SendToPlayer(&buf[next_out], oldi - next_out);
2650                             next_out = oldi;
2651                         }
2652                         Colorize(ColorSeek, FALSE);
2653                         curColor = ColorSeek;
2654                     }
2655                     continue;
2656             }
2657
2658             if (looking_at(buf, &i, "\\   ")) {
2659                 if (prevColor != ColorNormal) {
2660                     if (oldi > next_out) {
2661                         SendToPlayer(&buf[next_out], oldi - next_out);
2662                         next_out = oldi;
2663                     }
2664                     Colorize(prevColor, TRUE);
2665                     curColor = prevColor;
2666                 }
2667                 if (savingComment) {
2668                     parse_pos = i - oldi;
2669                     memcpy(parse, &buf[oldi], parse_pos);
2670                     parse[parse_pos] = NULLCHAR;
2671                     started = STARTED_COMMENT;
2672                 } else {
2673                     started = STARTED_CHATTER;
2674                 }
2675                 continue;
2676             }
2677
2678             if (looking_at(buf, &i, "Black Strength :") ||
2679                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2680                 looking_at(buf, &i, "<10>") ||
2681                 looking_at(buf, &i, "#@#")) {
2682                 /* Wrong board style */
2683                 loggedOn = TRUE;
2684                 SendToICS(ics_prefix);
2685                 SendToICS("set style 12\n");
2686                 SendToICS(ics_prefix);
2687                 SendToICS("refresh\n");
2688                 continue;
2689             }
2690
2691             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2692                 ICSInitScript();
2693                 have_sent_ICS_logon = 1;
2694                 continue;
2695             }
2696
2697             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2698                 (looking_at(buf, &i, "\n<12> ") ||
2699                  looking_at(buf, &i, "<12> "))) {
2700                 loggedOn = TRUE;
2701                 if (oldi > next_out) {
2702                     SendToPlayer(&buf[next_out], oldi - next_out);
2703                 }
2704                 next_out = i;
2705                 started = STARTED_BOARD;
2706                 parse_pos = 0;
2707                 continue;
2708             }
2709
2710             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2711                 looking_at(buf, &i, "<b1> ")) {
2712                 if (oldi > next_out) {
2713                     SendToPlayer(&buf[next_out], oldi - next_out);
2714                 }
2715                 next_out = i;
2716                 started = STARTED_HOLDINGS;
2717                 parse_pos = 0;
2718                 continue;
2719             }
2720
2721             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2722                 loggedOn = TRUE;
2723                 /* Header for a move list -- first line */
2724
2725                 switch (ics_getting_history) {
2726                   case H_FALSE:
2727                     switch (gameMode) {
2728                       case IcsIdle:
2729                       case BeginningOfGame:
2730                         /* User typed "moves" or "oldmoves" while we
2731                            were idle.  Pretend we asked for these
2732                            moves and soak them up so user can step
2733                            through them and/or save them.
2734                            */
2735                         Reset(FALSE, TRUE);
2736                         gameMode = IcsObserving;
2737                         ModeHighlight();
2738                         ics_gamenum = -1;
2739                         ics_getting_history = H_GOT_UNREQ_HEADER;
2740                         break;
2741                       case EditGame: /*?*/
2742                       case EditPosition: /*?*/
2743                         /* Should above feature work in these modes too? */
2744                         /* For now it doesn't */
2745                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2746                         break;
2747                       default:
2748                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2749                         break;
2750                     }
2751                     break;
2752                   case H_REQUESTED:
2753                     /* Is this the right one? */
2754                     if (gameInfo.white && gameInfo.black &&
2755                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2756                         strcmp(gameInfo.black, star_match[2]) == 0) {
2757                         /* All is well */
2758                         ics_getting_history = H_GOT_REQ_HEADER;
2759                     }
2760                     break;
2761                   case H_GOT_REQ_HEADER:
2762                   case H_GOT_UNREQ_HEADER:
2763                   case H_GOT_UNWANTED_HEADER:
2764                   case H_GETTING_MOVES:
2765                     /* Should not happen */
2766                     DisplayError(_("Error gathering move list: two headers"), 0);
2767                     ics_getting_history = H_FALSE;
2768                     break;
2769                 }
2770
2771                 /* Save player ratings into gameInfo if needed */
2772                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2773                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2774                     (gameInfo.whiteRating == -1 ||
2775                      gameInfo.blackRating == -1)) {
2776
2777                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2778                     gameInfo.blackRating = string_to_rating(star_match[3]);
2779                     if (appData.debugMode)
2780                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2781                               gameInfo.whiteRating, gameInfo.blackRating);
2782                 }
2783                 continue;
2784             }
2785
2786             if (looking_at(buf, &i,
2787               "* * match, initial time: * minute*, increment: * second")) {
2788                 /* Header for a move list -- second line */
2789                 /* Initial board will follow if this is a wild game */
2790                 if (gameInfo.event != NULL) free(gameInfo.event);
2791                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2792                 gameInfo.event = StrSave(str);
2793                 /* [HGM] we switched variant. Translate boards if needed. */
2794                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2795                 continue;
2796             }
2797
2798             if (looking_at(buf, &i, "Move  ")) {
2799                 /* Beginning of a move list */
2800                 switch (ics_getting_history) {
2801                   case H_FALSE:
2802                     /* Normally should not happen */
2803                     /* Maybe user hit reset while we were parsing */
2804                     break;
2805                   case H_REQUESTED:
2806                     /* Happens if we are ignoring a move list that is not
2807                      * the one we just requested.  Common if the user
2808                      * tries to observe two games without turning off
2809                      * getMoveList */
2810                     break;
2811                   case H_GETTING_MOVES:
2812                     /* Should not happen */
2813                     DisplayError(_("Error gathering move list: nested"), 0);
2814                     ics_getting_history = H_FALSE;
2815                     break;
2816                   case H_GOT_REQ_HEADER:
2817                     ics_getting_history = H_GETTING_MOVES;
2818                     started = STARTED_MOVES;
2819                     parse_pos = 0;
2820                     if (oldi > next_out) {
2821                         SendToPlayer(&buf[next_out], oldi - next_out);
2822                     }
2823                     break;
2824                   case H_GOT_UNREQ_HEADER:
2825                     ics_getting_history = H_GETTING_MOVES;
2826                     started = STARTED_MOVES_NOHIDE;
2827                     parse_pos = 0;
2828                     break;
2829                   case H_GOT_UNWANTED_HEADER:
2830                     ics_getting_history = H_FALSE;
2831                     break;
2832                 }
2833                 continue;
2834             }
2835
2836             if (looking_at(buf, &i, "% ") ||
2837                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2838                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2839                 savingComment = FALSE;
2840                 switch (started) {
2841                   case STARTED_MOVES:
2842                   case STARTED_MOVES_NOHIDE:
2843                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2844                     parse[parse_pos + i - oldi] = NULLCHAR;
2845                     ParseGameHistory(parse);
2846 #if ZIPPY
2847                     if (appData.zippyPlay && first.initDone) {
2848                         FeedMovesToProgram(&first, forwardMostMove);
2849                         if (gameMode == IcsPlayingWhite) {
2850                             if (WhiteOnMove(forwardMostMove)) {
2851                                 if (first.sendTime) {
2852                                   if (first.useColors) {
2853                                     SendToProgram("black\n", &first);
2854                                   }
2855                                   SendTimeRemaining(&first, TRUE);
2856                                 }
2857                                 if (first.useColors) {
2858                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2859                                 }
2860                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2861                                 first.maybeThinking = TRUE;
2862                             } else {
2863                                 if (first.usePlayother) {
2864                                   if (first.sendTime) {
2865                                     SendTimeRemaining(&first, TRUE);
2866                                   }
2867                                   SendToProgram("playother\n", &first);
2868                                   firstMove = FALSE;
2869                                 } else {
2870                                   firstMove = TRUE;
2871                                 }
2872                             }
2873                         } else if (gameMode == IcsPlayingBlack) {
2874                             if (!WhiteOnMove(forwardMostMove)) {
2875                                 if (first.sendTime) {
2876                                   if (first.useColors) {
2877                                     SendToProgram("white\n", &first);
2878                                   }
2879                                   SendTimeRemaining(&first, FALSE);
2880                                 }
2881                                 if (first.useColors) {
2882                                   SendToProgram("black\n", &first);
2883                                 }
2884                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2885                                 first.maybeThinking = TRUE;
2886                             } else {
2887                                 if (first.usePlayother) {
2888                                   if (first.sendTime) {
2889                                     SendTimeRemaining(&first, FALSE);
2890                                   }
2891                                   SendToProgram("playother\n", &first);
2892                                   firstMove = FALSE;
2893                                 } else {
2894                                   firstMove = TRUE;
2895                                 }
2896                             }
2897                         }
2898                     }
2899 #endif
2900                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2901                         /* Moves came from oldmoves or moves command
2902                            while we weren't doing anything else.
2903                            */
2904                         currentMove = forwardMostMove;
2905                         ClearHighlights();/*!!could figure this out*/
2906                         flipView = appData.flipView;
2907                         DrawPosition(TRUE, boards[currentMove]);
2908                         DisplayBothClocks();
2909                         sprintf(str, "%s vs. %s",
2910                                 gameInfo.white, gameInfo.black);
2911                         DisplayTitle(str);
2912                         gameMode = IcsIdle;
2913                     } else {
2914                         /* Moves were history of an active game */
2915                         if (gameInfo.resultDetails != NULL) {
2916                             free(gameInfo.resultDetails);
2917                             gameInfo.resultDetails = NULL;
2918                         }
2919                     }
2920                     HistorySet(parseList, backwardMostMove,
2921                                forwardMostMove, currentMove-1);
2922                     DisplayMove(currentMove - 1);
2923                     if (started == STARTED_MOVES) next_out = i;
2924                     started = STARTED_NONE;
2925                     ics_getting_history = H_FALSE;
2926                     break;
2927
2928                   case STARTED_OBSERVE:
2929                     started = STARTED_NONE;
2930                     SendToICS(ics_prefix);
2931                     SendToICS("refresh\n");
2932                     break;
2933
2934                   default:
2935                     break;
2936                 }
2937                 if(bookHit) { // [HGM] book: simulate book reply
2938                     static char bookMove[MSG_SIZ]; // a bit generous?
2939
2940                     programStats.nodes = programStats.depth = programStats.time =
2941                     programStats.score = programStats.got_only_move = 0;
2942                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2943
2944                     strcpy(bookMove, "move ");
2945                     strcat(bookMove, bookHit);
2946                     HandleMachineMove(bookMove, &first);
2947                 }
2948                 continue;
2949             }
2950
2951             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2952                  started == STARTED_HOLDINGS ||
2953                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2954                 /* Accumulate characters in move list or board */
2955                 parse[parse_pos++] = buf[i];
2956             }
2957
2958             /* Start of game messages.  Mostly we detect start of game
2959                when the first board image arrives.  On some versions
2960                of the ICS, though, we need to do a "refresh" after starting
2961                to observe in order to get the current board right away. */
2962             if (looking_at(buf, &i, "Adding game * to observation list")) {
2963                 started = STARTED_OBSERVE;
2964                 continue;
2965             }
2966
2967             /* Handle auto-observe */
2968             if (appData.autoObserve &&
2969                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2970                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2971                 char *player;
2972                 /* Choose the player that was highlighted, if any. */
2973                 if (star_match[0][0] == '\033' ||
2974                     star_match[1][0] != '\033') {
2975                     player = star_match[0];
2976                 } else {
2977                     player = star_match[2];
2978                 }
2979                 sprintf(str, "%sobserve %s\n",
2980                         ics_prefix, StripHighlightAndTitle(player));
2981                 SendToICS(str);
2982
2983                 /* Save ratings from notify string */
2984                 strcpy(player1Name, star_match[0]);
2985                 player1Rating = string_to_rating(star_match[1]);
2986                 strcpy(player2Name, star_match[2]);
2987                 player2Rating = string_to_rating(star_match[3]);
2988
2989                 if (appData.debugMode)
2990                   fprintf(debugFP,
2991                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2992                           player1Name, player1Rating,
2993                           player2Name, player2Rating);
2994
2995                 continue;
2996             }
2997
2998             /* Deal with automatic examine mode after a game,
2999                and with IcsObserving -> IcsExamining transition */
3000             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3001                 looking_at(buf, &i, "has made you an examiner of game *")) {
3002
3003                 int gamenum = atoi(star_match[0]);
3004                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3005                     gamenum == ics_gamenum) {
3006                     /* We were already playing or observing this game;
3007                        no need to refetch history */
3008                     gameMode = IcsExamining;
3009                     if (pausing) {
3010                         pauseExamForwardMostMove = forwardMostMove;
3011                     } else if (currentMove < forwardMostMove) {
3012                         ForwardInner(forwardMostMove);
3013                     }
3014                 } else {
3015                     /* I don't think this case really can happen */
3016                     SendToICS(ics_prefix);
3017                     SendToICS("refresh\n");
3018                 }
3019                 continue;
3020             }
3021
3022             /* Error messages */
3023 //          if (ics_user_moved) {
3024             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3025                 if (looking_at(buf, &i, "Illegal move") ||
3026                     looking_at(buf, &i, "Not a legal move") ||
3027                     looking_at(buf, &i, "Your king is in check") ||
3028                     looking_at(buf, &i, "It isn't your turn") ||
3029                     looking_at(buf, &i, "It is not your move")) {
3030                     /* Illegal move */
3031                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3032                         currentMove = --forwardMostMove;
3033                         DisplayMove(currentMove - 1); /* before DMError */
3034                         DrawPosition(FALSE, boards[currentMove]);
3035                         SwitchClocks();
3036                         DisplayBothClocks();
3037                     }
3038                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3039                     ics_user_moved = 0;
3040                     continue;
3041                 }
3042             }
3043
3044             if (looking_at(buf, &i, "still have time") ||
3045                 looking_at(buf, &i, "not out of time") ||
3046                 looking_at(buf, &i, "either player is out of time") ||
3047                 looking_at(buf, &i, "has timeseal; checking")) {
3048                 /* We must have called his flag a little too soon */
3049                 whiteFlag = blackFlag = FALSE;
3050                 continue;
3051             }
3052
3053             if (looking_at(buf, &i, "added * seconds to") ||
3054                 looking_at(buf, &i, "seconds were added to")) {
3055                 /* Update the clocks */
3056                 SendToICS(ics_prefix);
3057                 SendToICS("refresh\n");
3058                 continue;
3059             }
3060
3061             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3062                 ics_clock_paused = TRUE;
3063                 StopClocks();
3064                 continue;
3065             }
3066
3067             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3068                 ics_clock_paused = FALSE;
3069                 StartClocks();
3070                 continue;
3071             }
3072
3073             /* Grab player ratings from the Creating: message.
3074                Note we have to check for the special case when
3075                the ICS inserts things like [white] or [black]. */
3076             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3077                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3078                 /* star_matches:
3079                    0    player 1 name (not necessarily white)
3080                    1    player 1 rating
3081                    2    empty, white, or black (IGNORED)
3082                    3    player 2 name (not necessarily black)
3083                    4    player 2 rating
3084
3085                    The names/ratings are sorted out when the game
3086                    actually starts (below).
3087                 */
3088                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3089                 player1Rating = string_to_rating(star_match[1]);
3090                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3091                 player2Rating = string_to_rating(star_match[4]);
3092
3093                 if (appData.debugMode)
3094                   fprintf(debugFP,
3095                           "Ratings from 'Creating:' %s %d, %s %d\n",
3096                           player1Name, player1Rating,
3097                           player2Name, player2Rating);
3098
3099                 continue;
3100             }
3101
3102             /* Improved generic start/end-of-game messages */
3103             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3104                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3105                 /* If tkind == 0: */
3106                 /* star_match[0] is the game number */
3107                 /*           [1] is the white player's name */
3108                 /*           [2] is the black player's name */
3109                 /* For end-of-game: */
3110                 /*           [3] is the reason for the game end */
3111                 /*           [4] is a PGN end game-token, preceded by " " */
3112                 /* For start-of-game: */
3113                 /*           [3] begins with "Creating" or "Continuing" */
3114                 /*           [4] is " *" or empty (don't care). */
3115                 int gamenum = atoi(star_match[0]);
3116                 char *whitename, *blackname, *why, *endtoken;
3117                 ChessMove endtype = (ChessMove) 0;
3118
3119                 if (tkind == 0) {
3120                   whitename = star_match[1];
3121                   blackname = star_match[2];
3122                   why = star_match[3];
3123                   endtoken = star_match[4];
3124                 } else {
3125                   whitename = star_match[1];
3126                   blackname = star_match[3];
3127                   why = star_match[5];
3128                   endtoken = star_match[6];
3129                 }
3130
3131                 /* Game start messages */
3132                 if (strncmp(why, "Creating ", 9) == 0 ||
3133                     strncmp(why, "Continuing ", 11) == 0) {
3134                     gs_gamenum = gamenum;
3135                     strcpy(gs_kind, strchr(why, ' ') + 1);
3136 #if ZIPPY
3137                     if (appData.zippyPlay) {
3138                         ZippyGameStart(whitename, blackname);
3139                     }
3140 #endif /*ZIPPY*/
3141                     continue;
3142                 }
3143
3144                 /* Game end messages */
3145                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3146                     ics_gamenum != gamenum) {
3147                     continue;
3148                 }
3149                 while (endtoken[0] == ' ') endtoken++;
3150                 switch (endtoken[0]) {
3151                   case '*':
3152                   default:
3153                     endtype = GameUnfinished;
3154                     break;
3155                   case '0':
3156                     endtype = BlackWins;
3157                     break;
3158                   case '1':
3159                     if (endtoken[1] == '/')
3160                       endtype = GameIsDrawn;
3161                     else
3162                       endtype = WhiteWins;
3163                     break;
3164                 }
3165                 GameEnds(endtype, why, GE_ICS);
3166 #if ZIPPY
3167                 if (appData.zippyPlay && first.initDone) {
3168                     ZippyGameEnd(endtype, why);
3169                     if (first.pr == NULL) {
3170                       /* Start the next process early so that we'll
3171                          be ready for the next challenge */
3172                       StartChessProgram(&first);
3173                     }
3174                     /* Send "new" early, in case this command takes
3175                        a long time to finish, so that we'll be ready
3176                        for the next challenge. */
3177                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3178                     Reset(TRUE, TRUE);
3179                 }
3180 #endif /*ZIPPY*/
3181                 continue;
3182             }
3183
3184             if (looking_at(buf, &i, "Removing game * from observation") ||
3185                 looking_at(buf, &i, "no longer observing game *") ||
3186                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3187                 if (gameMode == IcsObserving &&
3188                     atoi(star_match[0]) == ics_gamenum)
3189                   {
3190                       /* icsEngineAnalyze */
3191                       if (appData.icsEngineAnalyze) {
3192                             ExitAnalyzeMode();
3193                             ModeHighlight();
3194                       }
3195                       StopClocks();
3196                       gameMode = IcsIdle;
3197                       ics_gamenum = -1;
3198                       ics_user_moved = FALSE;
3199                   }
3200                 continue;
3201             }
3202
3203             if (looking_at(buf, &i, "no longer examining game *")) {
3204                 if (gameMode == IcsExamining &&
3205                     atoi(star_match[0]) == ics_gamenum)
3206                   {
3207                       gameMode = IcsIdle;
3208                       ics_gamenum = -1;
3209                       ics_user_moved = FALSE;
3210                   }
3211                 continue;
3212             }
3213
3214             /* Advance leftover_start past any newlines we find,
3215                so only partial lines can get reparsed */
3216             if (looking_at(buf, &i, "\n")) {
3217                 prevColor = curColor;
3218                 if (curColor != ColorNormal) {
3219                     if (oldi > next_out) {
3220                         SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = oldi;
3222                     }
3223                     Colorize(ColorNormal, FALSE);
3224                     curColor = ColorNormal;
3225                 }
3226                 if (started == STARTED_BOARD) {
3227                     started = STARTED_NONE;
3228                     parse[parse_pos] = NULLCHAR;
3229                     ParseBoard12(parse);
3230                     ics_user_moved = 0;
3231
3232                     /* Send premove here */
3233                     if (appData.premove) {
3234                       char str[MSG_SIZ];
3235                       if (currentMove == 0 &&
3236                           gameMode == IcsPlayingWhite &&
3237                           appData.premoveWhite) {
3238                         sprintf(str, "%s\n", appData.premoveWhiteText);
3239                         if (appData.debugMode)
3240                           fprintf(debugFP, "Sending premove:\n");
3241                         SendToICS(str);
3242                       } else if (currentMove == 1 &&
3243                                  gameMode == IcsPlayingBlack &&
3244                                  appData.premoveBlack) {
3245                         sprintf(str, "%s\n", appData.premoveBlackText);
3246                         if (appData.debugMode)
3247                           fprintf(debugFP, "Sending premove:\n");
3248                         SendToICS(str);
3249                       } else if (gotPremove) {
3250                         gotPremove = 0;
3251                         ClearPremoveHighlights();
3252                         if (appData.debugMode)
3253                           fprintf(debugFP, "Sending premove:\n");
3254                           UserMoveEvent(premoveFromX, premoveFromY,
3255                                         premoveToX, premoveToY,
3256                                         premovePromoChar);
3257                       }
3258                     }
3259
3260                     /* Usually suppress following prompt */
3261                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3263                         if (looking_at(buf, &i, "*% ")) {
3264                             savingComment = FALSE;
3265                         }
3266                     }
3267                     next_out = i;
3268                 } else if (started == STARTED_HOLDINGS) {
3269                     int gamenum;
3270                     char new_piece[MSG_SIZ];
3271                     started = STARTED_NONE;
3272                     parse[parse_pos] = NULLCHAR;
3273                     if (appData.debugMode)
3274                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275                                                         parse, currentMove);
3276                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277                         gamenum == ics_gamenum) {
3278                         if (gameInfo.variant == VariantNormal) {
3279                           /* [HGM] We seem to switch variant during a game!
3280                            * Presumably no holdings were displayed, so we have
3281                            * to move the position two files to the right to
3282                            * create room for them!
3283                            */
3284                           VariantClass newVariant;
3285                           switch(gameInfo.boardWidth) { // base guess on board width
3286                                 case 9:  newVariant = VariantShogi; break;
3287                                 case 10: newVariant = VariantGreat; break;
3288                                 default: newVariant = VariantCrazyhouse; break;
3289                           }
3290                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3291                           /* Get a move list just to see the header, which
3292                              will tell us whether this is really bug or zh */
3293                           if (ics_getting_history == H_FALSE) {
3294                             ics_getting_history = H_REQUESTED;
3295                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3296                             SendToICS(str);
3297                           }
3298                         }
3299                         new_piece[0] = NULLCHAR;
3300                         sscanf(parse, "game %d white [%s black [%s <- %s",
3301                                &gamenum, white_holding, black_holding,
3302                                new_piece);
3303                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3304                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3305                         /* [HGM] copy holdings to board holdings area */
3306                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3307                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3308                         boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3309 #if ZIPPY
3310                         if (appData.zippyPlay && first.initDone) {
3311                             ZippyHoldings(white_holding, black_holding,
3312                                           new_piece);
3313                         }
3314 #endif /*ZIPPY*/
3315                         if (tinyLayout || smallLayout) {
3316                             char wh[16], bh[16];
3317                             PackHolding(wh, white_holding);
3318                             PackHolding(bh, black_holding);
3319                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3320                                     gameInfo.white, gameInfo.black);
3321                         } else {
3322                             sprintf(str, "%s [%s] vs. %s [%s]",
3323                                     gameInfo.white, white_holding,
3324                                     gameInfo.black, black_holding);
3325                         }
3326
3327                         DrawPosition(FALSE, boards[currentMove]);
3328                         DisplayTitle(str);
3329                     }
3330                     /* Suppress following prompt */
3331                     if (looking_at(buf, &i, "*% ")) {
3332                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3333                         savingComment = FALSE;
3334                     }
3335                     next_out = i;
3336                 }
3337                 continue;
3338             }
3339
3340             i++;                /* skip unparsed character and loop back */
3341         }
3342
3343         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3344             started != STARTED_HOLDINGS && i > next_out) {
3345             SendToPlayer(&buf[next_out], i - next_out);
3346             next_out = i;
3347         }
3348         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3349
3350         leftover_len = buf_len - leftover_start;
3351         /* if buffer ends with something we couldn't parse,
3352            reparse it after appending the next read */
3353
3354     } else if (count == 0) {
3355         RemoveInputSource(isr);
3356         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3357     } else {
3358         DisplayFatalError(_("Error reading from ICS"), error, 1);
3359     }
3360 }
3361
3362
3363 /* Board style 12 looks like this:
3364
3365    <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
3366
3367  * The "<12> " is stripped before it gets to this routine.  The two
3368  * trailing 0's (flip state and clock ticking) are later addition, and
3369  * some chess servers may not have them, or may have only the first.
3370  * Additional trailing fields may be added in the future.
3371  */
3372
3373 #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"
3374
3375 #define RELATION_OBSERVING_PLAYED    0
3376 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3377 #define RELATION_PLAYING_MYMOVE      1
3378 #define RELATION_PLAYING_NOTMYMOVE  -1
3379 #define RELATION_EXAMINING           2
3380 #define RELATION_ISOLATED_BOARD     -3
3381 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3382
3383 void
3384 ParseBoard12(string)
3385      char *string;
3386 {
3387     GameMode newGameMode;
3388     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3389     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3390     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3391     char to_play, board_chars[200];
3392     char move_str[500], str[500], elapsed_time[500];
3393     char black[32], white[32];
3394     Board board;
3395     int prevMove = currentMove;
3396     int ticking = 2;
3397     ChessMove moveType;
3398     int fromX, fromY, toX, toY;
3399     char promoChar;
3400     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3401     char *bookHit = NULL; // [HGM] book
3402     Boolean weird = FALSE, reqFlag = FALSE;
3403
3404     fromX = fromY = toX = toY = -1;
3405
3406     newGame = FALSE;
3407
3408     if (appData.debugMode)
3409       fprintf(debugFP, _("Parsing board: %s\n"), string);
3410
3411     move_str[0] = NULLCHAR;
3412     elapsed_time[0] = NULLCHAR;
3413     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3414         int  i = 0, j;
3415         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3416             if(string[i] == ' ') { ranks++; files = 0; }
3417             else files++;
3418             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3419             i++;
3420         }
3421         for(j = 0; j <i; j++) board_chars[j] = string[j];
3422         board_chars[i] = '\0';
3423         string += i + 1;
3424     }
3425     n = sscanf(string, PATTERN, &to_play, &double_push,
3426                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3427                &gamenum, white, black, &relation, &basetime, &increment,
3428                &white_stren, &black_stren, &white_time, &black_time,
3429                &moveNum, str, elapsed_time, move_str, &ics_flip,
3430                &ticking);
3431
3432     if (n < 21) {
3433         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3434         DisplayError(str, 0);
3435         return;
3436     }
3437
3438     /* Convert the move number to internal form */
3439     moveNum = (moveNum - 1) * 2;
3440     if (to_play == 'B') moveNum++;
3441     if (moveNum >= MAX_MOVES) {
3442       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3443                         0, 1);
3444       return;
3445     }
3446
3447     switch (relation) {
3448       case RELATION_OBSERVING_PLAYED:
3449       case RELATION_OBSERVING_STATIC:
3450         if (gamenum == -1) {
3451             /* Old ICC buglet */
3452             relation = RELATION_OBSERVING_STATIC;
3453         }
3454         newGameMode = IcsObserving;
3455         break;
3456       case RELATION_PLAYING_MYMOVE:
3457       case RELATION_PLAYING_NOTMYMOVE:
3458         newGameMode =
3459           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3460             IcsPlayingWhite : IcsPlayingBlack;
3461         break;
3462       case RELATION_EXAMINING:
3463         newGameMode = IcsExamining;
3464         break;
3465       case RELATION_ISOLATED_BOARD:
3466       default:
3467         /* Just display this board.  If user was doing something else,
3468            we will forget about it until the next board comes. */
3469         newGameMode = IcsIdle;
3470         break;
3471       case RELATION_STARTING_POSITION:
3472         newGameMode = gameMode;
3473         break;
3474     }
3475
3476     /* Modify behavior for initial board display on move listing
3477        of wild games.
3478        */
3479     switch (ics_getting_history) {
3480       case H_FALSE:
3481       case H_REQUESTED:
3482         break;
3483       case H_GOT_REQ_HEADER:
3484       case H_GOT_UNREQ_HEADER:
3485         /* This is the initial position of the current game */
3486         gamenum = ics_gamenum;
3487         moveNum = 0;            /* old ICS bug workaround */
3488         if (to_play == 'B') {
3489           startedFromSetupPosition = TRUE;
3490           blackPlaysFirst = TRUE;
3491           moveNum = 1;
3492           if (forwardMostMove == 0) forwardMostMove = 1;
3493           if (backwardMostMove == 0) backwardMostMove = 1;
3494           if (currentMove == 0) currentMove = 1;
3495         }
3496         newGameMode = gameMode;
3497         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3498         break;
3499       case H_GOT_UNWANTED_HEADER:
3500         /* This is an initial board that we don't want */
3501         return;
3502       case H_GETTING_MOVES:
3503         /* Should not happen */
3504         DisplayError(_("Error gathering move list: extra board"), 0);
3505         ics_getting_history = H_FALSE;
3506         return;
3507     }
3508
3509    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3510                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3511      /* [HGM] We seem to have switched variant unexpectedly
3512       * Try to guess new variant from board size
3513       */
3514           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3515           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3516           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3517           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3518           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3519           if(!weird) newVariant = VariantNormal;
3520           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3521           /* Get a move list just to see the header, which
3522              will tell us whether this is really bug or zh */
3523           if (ics_getting_history == H_FALSE) {
3524             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3525             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3526             SendToICS(str);
3527           }
3528     }
3529     
3530     /* Take action if this is the first board of a new game, or of a
3531        different game than is currently being displayed.  */
3532     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3533         relation == RELATION_ISOLATED_BOARD) {
3534
3535         /* Forget the old game and get the history (if any) of the new one */
3536         if (gameMode != BeginningOfGame) {
3537           Reset(TRUE, TRUE);
3538         }
3539         newGame = TRUE;
3540         if (appData.autoRaiseBoard) BoardToTop();
3541         prevMove = -3;
3542         if (gamenum == -1) {
3543             newGameMode = IcsIdle;
3544         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3545                    appData.getMoveList && !reqFlag) {
3546             /* Need to get game history */
3547             ics_getting_history = H_REQUESTED;
3548             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3549             SendToICS(str);
3550         }
3551
3552         /* Initially flip the board to have black on the bottom if playing
3553            black or if the ICS flip flag is set, but let the user change
3554            it with the Flip View button. */
3555         flipView = appData.autoFlipView ?
3556           (newGameMode == IcsPlayingBlack) || ics_flip :
3557           appData.flipView;
3558
3559         /* Done with values from previous mode; copy in new ones */
3560         gameMode = newGameMode;
3561         ModeHighlight();
3562         ics_gamenum = gamenum;
3563         if (gamenum == gs_gamenum) {
3564             int klen = strlen(gs_kind);
3565             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3566             sprintf(str, "ICS %s", gs_kind);
3567             gameInfo.event = StrSave(str);
3568         } else {
3569             gameInfo.event = StrSave("ICS game");
3570         }
3571         gameInfo.site = StrSave(appData.icsHost);
3572         gameInfo.date = PGNDate();
3573         gameInfo.round = StrSave("-");
3574         gameInfo.white = StrSave(white);
3575         gameInfo.black = StrSave(black);
3576         timeControl = basetime * 60 * 1000;
3577         timeControl_2 = 0;
3578         timeIncrement = increment * 1000;
3579         movesPerSession = 0;
3580         gameInfo.timeControl = TimeControlTagValue();
3581         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3582   if (appData.debugMode) {
3583     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3584     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3585     setbuf(debugFP, NULL);
3586   }
3587
3588         gameInfo.outOfBook = NULL;
3589
3590         /* Do we have the ratings? */
3591         if (strcmp(player1Name, white) == 0 &&
3592             strcmp(player2Name, black) == 0) {
3593             if (appData.debugMode)
3594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3595                       player1Rating, player2Rating);
3596             gameInfo.whiteRating = player1Rating;
3597             gameInfo.blackRating = player2Rating;
3598         } else if (strcmp(player2Name, white) == 0 &&
3599                    strcmp(player1Name, black) == 0) {
3600             if (appData.debugMode)
3601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3602                       player2Rating, player1Rating);
3603             gameInfo.whiteRating = player2Rating;
3604             gameInfo.blackRating = player1Rating;
3605         }
3606         player1Name[0] = player2Name[0] = NULLCHAR;
3607
3608         /* Silence shouts if requested */
3609         if (appData.quietPlay &&
3610             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3611             SendToICS(ics_prefix);
3612             SendToICS("set shout 0\n");
3613         }
3614     }
3615
3616     /* Deal with midgame name changes */
3617     if (!newGame) {
3618         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3619             if (gameInfo.white) free(gameInfo.white);
3620             gameInfo.white = StrSave(white);
3621         }
3622         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3623             if (gameInfo.black) free(gameInfo.black);
3624             gameInfo.black = StrSave(black);
3625         }
3626     }
3627
3628     /* Throw away game result if anything actually changes in examine mode */
3629     if (gameMode == IcsExamining && !newGame) {
3630         gameInfo.result = GameUnfinished;
3631         if (gameInfo.resultDetails != NULL) {
3632             free(gameInfo.resultDetails);
3633             gameInfo.resultDetails = NULL;
3634         }
3635     }
3636
3637     /* In pausing && IcsExamining mode, we ignore boards coming
3638        in if they are in a different variation than we are. */
3639     if (pauseExamInvalid) return;
3640     if (pausing && gameMode == IcsExamining) {
3641         if (moveNum <= pauseExamForwardMostMove) {
3642             pauseExamInvalid = TRUE;
3643             forwardMostMove = pauseExamForwardMostMove;
3644             return;
3645         }
3646     }
3647
3648   if (appData.debugMode) {
3649     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3650   }
3651     /* Parse the board */
3652     for (k = 0; k < ranks; k++) {
3653       for (j = 0; j < files; j++)
3654         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3655       if(gameInfo.holdingsWidth > 1) {
3656            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3657            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3658       }
3659     }
3660     CopyBoard(boards[moveNum], board);
3661     boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3662     if (moveNum == 0) {
3663         startedFromSetupPosition =
3664           !CompareBoards(board, initialPosition);
3665         if(startedFromSetupPosition)
3666             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3667     }
3668
3669     /* [HGM] Set castling rights. Take the outermost Rooks,
3670        to make it also work for FRC opening positions. Note that board12
3671        is really defective for later FRC positions, as it has no way to
3672        indicate which Rook can castle if they are on the same side of King.
3673        For the initial position we grant rights to the outermost Rooks,
3674        and remember thos rights, and we then copy them on positions
3675        later in an FRC game. This means WB might not recognize castlings with
3676        Rooks that have moved back to their original position as illegal,
3677        but in ICS mode that is not its job anyway.
3678     */
3679     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3680     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3681
3682         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3683             if(board[0][i] == WhiteRook) j = i;
3684         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3686             if(board[0][i] == WhiteRook) j = i;
3687         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3692             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3693         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3694
3695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3697             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699             if(board[BOARD_HEIGHT-1][k] == bKing)
3700                 initialRights[5] = castlingRights[moveNum][5] = k;
3701     } else { int r;
3702         r = castlingRights[moveNum][0] = initialRights[0];
3703         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3704         r = castlingRights[moveNum][1] = initialRights[1];
3705         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3706         r = castlingRights[moveNum][3] = initialRights[3];
3707         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3708         r = castlingRights[moveNum][4] = initialRights[4];
3709         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3710         /* wildcastle kludge: always assume King has rights */
3711         r = castlingRights[moveNum][2] = initialRights[2];
3712         r = castlingRights[moveNum][5] = initialRights[5];
3713     }
3714     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3715     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3716
3717
3718     if (ics_getting_history == H_GOT_REQ_HEADER ||
3719         ics_getting_history == H_GOT_UNREQ_HEADER) {
3720         /* This was an initial position from a move list, not
3721            the current position */
3722         return;
3723     }
3724
3725     /* Update currentMove and known move number limits */
3726     newMove = newGame || moveNum > forwardMostMove;
3727
3728     if (newGame) {
3729         forwardMostMove = backwardMostMove = currentMove = moveNum;
3730         if (gameMode == IcsExamining && moveNum == 0) {
3731           /* Workaround for ICS limitation: we are not told the wild
3732              type when starting to examine a game.  But if we ask for
3733              the move list, the move list header will tell us */
3734             ics_getting_history = H_REQUESTED;
3735             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3736             SendToICS(str);
3737         }
3738     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3739                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3740 #if ZIPPY
3741         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3742         /* [HGM] applied this also to an engine that is silently watching        */
3743         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3744             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3745             gameInfo.variant == currentlyInitializedVariant) {
3746           takeback = forwardMostMove - moveNum;
3747           for (i = 0; i < takeback; i++) {
3748             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3749             SendToProgram("undo\n", &first);
3750           }
3751         }
3752 #endif
3753
3754         forwardMostMove = moveNum;
3755         if (!pausing || currentMove > forwardMostMove)
3756           currentMove = forwardMostMove;
3757     } else {
3758         /* New part of history that is not contiguous with old part */
3759         if (pausing && gameMode == IcsExamining) {
3760             pauseExamInvalid = TRUE;
3761             forwardMostMove = pauseExamForwardMostMove;
3762             return;
3763         }
3764         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3765 #if ZIPPY
3766             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3767                 // [HGM] when we will receive the move list we now request, it will be
3768                 // fed to the engine from the first move on. So if the engine is not
3769                 // in the initial position now, bring it there.
3770                 InitChessProgram(&first, 0);
3771             }
3772 #endif
3773             ics_getting_history = H_REQUESTED;
3774             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3775             SendToICS(str);
3776         }
3777         forwardMostMove = backwardMostMove = currentMove = moveNum;
3778     }
3779
3780     /* Update the clocks */
3781     if (strchr(elapsed_time, '.')) {
3782       /* Time is in ms */
3783       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3784       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3785     } else {
3786       /* Time is in seconds */
3787       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3788       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3789     }
3790
3791
3792 #if ZIPPY
3793     if (appData.zippyPlay && newGame &&
3794         gameMode != IcsObserving && gameMode != IcsIdle &&
3795         gameMode != IcsExamining)
3796       ZippyFirstBoard(moveNum, basetime, increment);
3797 #endif
3798
3799     /* Put the move on the move list, first converting
3800        to canonical algebraic form. */
3801     if (moveNum > 0) {
3802   if (appData.debugMode) {
3803     if (appData.debugMode) { int f = forwardMostMove;
3804         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3805                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3806     }
3807     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808     fprintf(debugFP, "moveNum = %d\n", moveNum);
3809     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810     setbuf(debugFP, NULL);
3811   }
3812         if (moveNum <= backwardMostMove) {
3813             /* We don't know what the board looked like before
3814                this move.  Punt. */
3815             strcpy(parseList[moveNum - 1], move_str);
3816             strcat(parseList[moveNum - 1], " ");
3817             strcat(parseList[moveNum - 1], elapsed_time);
3818             moveList[moveNum - 1][0] = NULLCHAR;
3819         } else if (strcmp(move_str, "none") == 0) {
3820             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821             /* Again, we don't know what the board looked like;
3822                this is really the start of the game. */
3823             parseList[moveNum - 1][0] = NULLCHAR;
3824             moveList[moveNum - 1][0] = NULLCHAR;
3825             backwardMostMove = moveNum;
3826             startedFromSetupPosition = TRUE;
3827             fromX = fromY = toX = toY = -1;
3828         } else {
3829           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3830           //                 So we parse the long-algebraic move string in stead of the SAN move
3831           int valid; char buf[MSG_SIZ], *prom;
3832
3833           // str looks something like "Q/a1-a2"; kill the slash
3834           if(str[1] == '/')
3835                 sprintf(buf, "%c%s", str[0], str+2);
3836           else  strcpy(buf, str); // might be castling
3837           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3838                 strcat(buf, prom); // long move lacks promo specification!
3839           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840                 if(appData.debugMode)
3841                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842                 strcpy(move_str, buf);
3843           }
3844           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845                                 &fromX, &fromY, &toX, &toY, &promoChar)
3846                || ParseOneMove(buf, moveNum - 1, &moveType,
3847                                 &fromX, &fromY, &toX, &toY, &promoChar);
3848           // end of long SAN patch
3849           if (valid) {
3850             (void) CoordsToAlgebraic(boards[moveNum - 1],
3851                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3852                                      fromY, fromX, toY, toX, promoChar,
3853                                      parseList[moveNum-1]);
3854             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3855                              castlingRights[moveNum]) ) {
3856               case MT_NONE:
3857               case MT_STALEMATE:
3858               default:
3859                 break;
3860               case MT_CHECK:
3861                 if(gameInfo.variant != VariantShogi)
3862                     strcat(parseList[moveNum - 1], "+");
3863                 break;
3864               case MT_CHECKMATE:
3865               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3866                 strcat(parseList[moveNum - 1], "#");
3867                 break;
3868             }
3869             strcat(parseList[moveNum - 1], " ");
3870             strcat(parseList[moveNum - 1], elapsed_time);
3871             /* currentMoveString is set as a side-effect of ParseOneMove */
3872             strcpy(moveList[moveNum - 1], currentMoveString);
3873             strcat(moveList[moveNum - 1], "\n");
3874           } else {
3875             /* Move from ICS was illegal!?  Punt. */
3876   if (appData.debugMode) {
3877     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3878     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3879   }
3880             strcpy(parseList[moveNum - 1], move_str);
3881             strcat(parseList[moveNum - 1], " ");
3882             strcat(parseList[moveNum - 1], elapsed_time);
3883             moveList[moveNum - 1][0] = NULLCHAR;
3884             fromX = fromY = toX = toY = -1;
3885           }
3886         }
3887   if (appData.debugMode) {
3888     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3889     setbuf(debugFP, NULL);
3890   }
3891
3892 #if ZIPPY
3893         /* Send move to chess program (BEFORE animating it). */
3894         if (appData.zippyPlay && !newGame && newMove &&
3895            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3896
3897             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3898                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3899                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3900                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3901                             move_str);
3902                     DisplayError(str, 0);
3903                 } else {
3904                     if (first.sendTime) {
3905                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3906                     }
3907                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3908                     if (firstMove && !bookHit) {
3909                         firstMove = FALSE;
3910                         if (first.useColors) {
3911                           SendToProgram(gameMode == IcsPlayingWhite ?
3912                                         "white\ngo\n" :
3913                                         "black\ngo\n", &first);
3914                         } else {
3915                           SendToProgram("go\n", &first);
3916                         }
3917                         first.maybeThinking = TRUE;
3918                     }
3919                 }
3920             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3921               if (moveList[moveNum - 1][0] == NULLCHAR) {
3922                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3923                 DisplayError(str, 0);
3924               } else {
3925                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3926                 SendMoveToProgram(moveNum - 1, &first);
3927               }
3928             }
3929         }
3930 #endif
3931     }
3932
3933     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3934         /* If move comes from a remote source, animate it.  If it
3935            isn't remote, it will have already been animated. */
3936         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3937             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3938         }
3939         if (!pausing && appData.highlightLastMove) {
3940             SetHighlights(fromX, fromY, toX, toY);
3941         }
3942     }
3943
3944     /* Start the clocks */
3945     whiteFlag = blackFlag = FALSE;
3946     appData.clockMode = !(basetime == 0 && increment == 0);
3947     if (ticking == 0) {
3948       ics_clock_paused = TRUE;
3949       StopClocks();
3950     } else if (ticking == 1) {
3951       ics_clock_paused = FALSE;
3952     }
3953     if (gameMode == IcsIdle ||
3954         relation == RELATION_OBSERVING_STATIC ||
3955         relation == RELATION_EXAMINING ||
3956         ics_clock_paused)
3957       DisplayBothClocks();
3958     else
3959       StartClocks();
3960
3961     /* Display opponents and material strengths */
3962     if (gameInfo.variant != VariantBughouse &&
3963         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3964         if (tinyLayout || smallLayout) {
3965             if(gameInfo.variant == VariantNormal)
3966                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3967                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3968                     basetime, increment);
3969             else
3970                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3971                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3972                     basetime, increment, (int) gameInfo.variant);
3973         } else {
3974             if(gameInfo.variant == VariantNormal)
3975                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3976                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3977                     basetime, increment);
3978             else
3979                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3980                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3981                     basetime, increment, VariantName(gameInfo.variant));
3982         }
3983         DisplayTitle(str);
3984   if (appData.debugMode) {
3985     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3986   }
3987     }
3988
3989
3990     /* Display the board */
3991     if (!pausing && !appData.noGUI) {
3992       if (appData.premove)
3993           if (!gotPremove ||
3994              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3995              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3996               ClearPremoveHighlights();
3997
3998       DrawPosition(FALSE, boards[currentMove]);
3999       DisplayMove(moveNum - 1);
4000       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4001             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4002               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4003         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4004       }
4005     }
4006
4007     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4008 #if ZIPPY
4009     if(bookHit) { // [HGM] book: simulate book reply
4010         static char bookMove[MSG_SIZ]; // a bit generous?
4011
4012         programStats.nodes = programStats.depth = programStats.time =
4013         programStats.score = programStats.got_only_move = 0;
4014         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4015
4016         strcpy(bookMove, "move ");
4017         strcat(bookMove, bookHit);
4018         HandleMachineMove(bookMove, &first);
4019     }
4020 #endif
4021 }
4022
4023 void
4024 GetMoveListEvent()
4025 {
4026     char buf[MSG_SIZ];
4027     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4028         ics_getting_history = H_REQUESTED;
4029         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4030         SendToICS(buf);
4031     }
4032 }
4033
4034 void
4035 AnalysisPeriodicEvent(force)
4036      int force;
4037 {
4038     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4039          && !force) || !appData.periodicUpdates)
4040       return;
4041
4042     /* Send . command to Crafty to collect stats */
4043     SendToProgram(".\n", &first);
4044
4045     /* Don't send another until we get a response (this makes
4046        us stop sending to old Crafty's which don't understand
4047        the "." command (sending illegal cmds resets node count & time,
4048        which looks bad)) */
4049     programStats.ok_to_send = 0;
4050 }
4051
4052 void ics_update_width(new_width)
4053         int new_width;
4054 {
4055         ics_printf("set width %d\n", new_width);
4056 }
4057
4058 void
4059 SendMoveToProgram(moveNum, cps)
4060      int moveNum;
4061      ChessProgramState *cps;
4062 {
4063     char buf[MSG_SIZ];
4064
4065     if (cps->useUsermove) {
4066       SendToProgram("usermove ", cps);
4067     }
4068     if (cps->useSAN) {
4069       char *space;
4070       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4071         int len = space - parseList[moveNum];
4072         memcpy(buf, parseList[moveNum], len);
4073         buf[len++] = '\n';
4074         buf[len] = NULLCHAR;
4075       } else {
4076         sprintf(buf, "%s\n", parseList[moveNum]);
4077       }
4078       SendToProgram(buf, cps);
4079     } else {
4080       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4081         AlphaRank(moveList[moveNum], 4);
4082         SendToProgram(moveList[moveNum], cps);
4083         AlphaRank(moveList[moveNum], 4); // and back
4084       } else
4085       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4086        * the engine. It would be nice to have a better way to identify castle
4087        * moves here. */
4088       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4089                                                                          && cps->useOOCastle) {
4090         int fromX = moveList[moveNum][0] - AAA;
4091         int fromY = moveList[moveNum][1] - ONE;
4092         int toX = moveList[moveNum][2] - AAA;
4093         int toY = moveList[moveNum][3] - ONE;
4094         if((boards[moveNum][fromY][fromX] == WhiteKing
4095             && boards[moveNum][toY][toX] == WhiteRook)
4096            || (boards[moveNum][fromY][fromX] == BlackKing
4097                && boards[moveNum][toY][toX] == BlackRook)) {
4098           if(toX > fromX) SendToProgram("O-O\n", cps);
4099           else SendToProgram("O-O-O\n", cps);
4100         }
4101         else SendToProgram(moveList[moveNum], cps);
4102       }
4103       else SendToProgram(moveList[moveNum], cps);
4104       /* End of additions by Tord */
4105     }
4106
4107     /* [HGM] setting up the opening has brought engine in force mode! */
4108     /*       Send 'go' if we are in a mode where machine should play. */
4109     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4110         (gameMode == TwoMachinesPlay   ||
4111 #ifdef ZIPPY
4112          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4113 #endif
4114          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4115         SendToProgram("go\n", cps);
4116   if (appData.debugMode) {
4117     fprintf(debugFP, "(extra)\n");
4118   }
4119     }
4120     setboardSpoiledMachineBlack = 0;
4121 }
4122
4123 void
4124 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4125      ChessMove moveType;
4126      int fromX, fromY, toX, toY;
4127 {
4128     char user_move[MSG_SIZ];
4129
4130     switch (moveType) {
4131       default:
4132         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4133                 (int)moveType, fromX, fromY, toX, toY);
4134         DisplayError(user_move + strlen("say "), 0);
4135         break;
4136       case WhiteKingSideCastle:
4137       case BlackKingSideCastle:
4138       case WhiteQueenSideCastleWild:
4139       case BlackQueenSideCastleWild:
4140       /* PUSH Fabien */
4141       case WhiteHSideCastleFR:
4142       case BlackHSideCastleFR:
4143       /* POP Fabien */
4144         sprintf(user_move, "o-o\n");
4145         break;
4146       case WhiteQueenSideCastle:
4147       case BlackQueenSideCastle:
4148       case WhiteKingSideCastleWild:
4149       case BlackKingSideCastleWild:
4150       /* PUSH Fabien */
4151       case WhiteASideCastleFR:
4152       case BlackASideCastleFR:
4153       /* POP Fabien */
4154         sprintf(user_move, "o-o-o\n");
4155         break;
4156       case WhitePromotionQueen:
4157       case BlackPromotionQueen:
4158       case WhitePromotionRook:
4159       case BlackPromotionRook:
4160       case WhitePromotionBishop:
4161       case BlackPromotionBishop:
4162       case WhitePromotionKnight:
4163       case BlackPromotionKnight:
4164       case WhitePromotionKing:
4165       case BlackPromotionKing:
4166       case WhitePromotionChancellor:
4167       case BlackPromotionChancellor:
4168       case WhitePromotionArchbishop:
4169       case BlackPromotionArchbishop:
4170         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4171             sprintf(user_move, "%c%c%c%c=%c\n",
4172                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173                 PieceToChar(WhiteFerz));
4174         else if(gameInfo.variant == VariantGreat)
4175             sprintf(user_move, "%c%c%c%c=%c\n",
4176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177                 PieceToChar(WhiteMan));
4178         else
4179             sprintf(user_move, "%c%c%c%c=%c\n",
4180                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4181                 PieceToChar(PromoPiece(moveType)));
4182         break;
4183       case WhiteDrop:
4184       case BlackDrop:
4185         sprintf(user_move, "%c@%c%c\n",
4186                 ToUpper(PieceToChar((ChessSquare) fromX)),
4187                 AAA + toX, ONE + toY);
4188         break;
4189       case NormalMove:
4190       case WhiteCapturesEnPassant:
4191       case BlackCapturesEnPassant:
4192       case IllegalMove:  /* could be a variant we don't quite understand */
4193         sprintf(user_move, "%c%c%c%c\n",
4194                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4195         break;
4196     }
4197     SendToICS(user_move);
4198     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4199         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4200 }
4201
4202 void
4203 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4204      int rf, ff, rt, ft;
4205      char promoChar;
4206      char move[7];
4207 {
4208     if (rf == DROP_RANK) {
4209         sprintf(move, "%c@%c%c\n",
4210                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4211     } else {
4212         if (promoChar == 'x' || promoChar == NULLCHAR) {
4213             sprintf(move, "%c%c%c%c\n",
4214                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4215         } else {
4216             sprintf(move, "%c%c%c%c%c\n",
4217                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4218         }
4219     }
4220 }
4221
4222 void
4223 ProcessICSInitScript(f)
4224      FILE *f;
4225 {
4226     char buf[MSG_SIZ];
4227
4228     while (fgets(buf, MSG_SIZ, f)) {
4229         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4230     }
4231
4232     fclose(f);
4233 }
4234
4235
4236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4237 void
4238 AlphaRank(char *move, int n)
4239 {
4240 //    char *p = move, c; int x, y;
4241
4242     if (appData.debugMode) {
4243         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4244     }
4245
4246     if(move[1]=='*' &&
4247        move[2]>='0' && move[2]<='9' &&
4248        move[3]>='a' && move[3]<='x'    ) {
4249         move[1] = '@';
4250         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4251         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4252     } else
4253     if(move[0]>='0' && move[0]<='9' &&
4254        move[1]>='a' && move[1]<='x' &&
4255        move[2]>='0' && move[2]<='9' &&
4256        move[3]>='a' && move[3]<='x'    ) {
4257         /* input move, Shogi -> normal */
4258         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4259         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4260         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4261         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4262     } else
4263     if(move[1]=='@' &&
4264        move[3]>='0' && move[3]<='9' &&
4265        move[2]>='a' && move[2]<='x'    ) {
4266         move[1] = '*';
4267         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4268         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4269     } else
4270     if(
4271        move[0]>='a' && move[0]<='x' &&
4272        move[3]>='0' && move[3]<='9' &&
4273        move[2]>='a' && move[2]<='x'    ) {
4274          /* output move, normal -> Shogi */
4275         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4276         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4277         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4278         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4279         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4280     }
4281     if (appData.debugMode) {
4282         fprintf(debugFP, "   out = '%s'\n", move);
4283     }
4284 }
4285
4286 /* Parser for moves from gnuchess, ICS, or user typein box */
4287 Boolean
4288 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4289      char *move;
4290      int moveNum;
4291      ChessMove *moveType;
4292      int *fromX, *fromY, *toX, *toY;
4293      char *promoChar;
4294 {
4295     if (appData.debugMode) {
4296         fprintf(debugFP, "move to parse: %s\n", move);
4297     }
4298     *moveType = yylexstr(moveNum, move);
4299
4300     switch (*moveType) {
4301       case WhitePromotionChancellor:
4302       case BlackPromotionChancellor:
4303       case WhitePromotionArchbishop:
4304       case BlackPromotionArchbishop:
4305       case WhitePromotionQueen:
4306       case BlackPromotionQueen:
4307       case WhitePromotionRook:
4308       case BlackPromotionRook:
4309       case WhitePromotionBishop:
4310       case BlackPromotionBishop:
4311       case WhitePromotionKnight:
4312       case BlackPromotionKnight:
4313       case WhitePromotionKing:
4314       case BlackPromotionKing:
4315       case NormalMove:
4316       case WhiteCapturesEnPassant:
4317       case BlackCapturesEnPassant:
4318       case WhiteKingSideCastle:
4319       case WhiteQueenSideCastle:
4320       case BlackKingSideCastle:
4321       case BlackQueenSideCastle:
4322       case WhiteKingSideCastleWild:
4323       case WhiteQueenSideCastleWild:
4324       case BlackKingSideCastleWild:
4325       case BlackQueenSideCastleWild:
4326       /* Code added by Tord: */
4327       case WhiteHSideCastleFR:
4328       case WhiteASideCastleFR:
4329       case BlackHSideCastleFR:
4330       case BlackASideCastleFR:
4331       /* End of code added by Tord */
4332       case IllegalMove:         /* bug or odd chess variant */
4333         *fromX = currentMoveString[0] - AAA;
4334         *fromY = currentMoveString[1] - ONE;
4335         *toX = currentMoveString[2] - AAA;
4336         *toY = currentMoveString[3] - ONE;
4337         *promoChar = currentMoveString[4];
4338         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4339             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4340     if (appData.debugMode) {
4341         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4342     }
4343             *fromX = *fromY = *toX = *toY = 0;
4344             return FALSE;
4345         }
4346         if (appData.testLegality) {
4347           return (*moveType != IllegalMove);
4348         } else {
4349           return !(fromX == fromY && toX == toY);
4350         }
4351
4352       case WhiteDrop:
4353       case BlackDrop:
4354         *fromX = *moveType == WhiteDrop ?
4355           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4356           (int) CharToPiece(ToLower(currentMoveString[0]));
4357         *fromY = DROP_RANK;
4358         *toX = currentMoveString[2] - AAA;
4359         *toY = currentMoveString[3] - ONE;
4360         *promoChar = NULLCHAR;
4361         return TRUE;
4362
4363       case AmbiguousMove:
4364       case ImpossibleMove:
4365       case (ChessMove) 0:       /* end of file */
4366       case ElapsedTime:
4367       case Comment:
4368       case PGNTag:
4369       case NAG:
4370       case WhiteWins:
4371       case BlackWins:
4372       case GameIsDrawn:
4373       default:
4374     if (appData.debugMode) {
4375         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4376     }
4377         /* bug? */
4378         *fromX = *fromY = *toX = *toY = 0;
4379         *promoChar = NULLCHAR;
4380         return FALSE;
4381     }
4382 }
4383
4384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4385 // All positions will have equal probability, but the current method will not provide a unique
4386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4387 #define DARK 1
4388 #define LITE 2
4389 #define ANY 3
4390
4391 int squaresLeft[4];
4392 int piecesLeft[(int)BlackPawn];
4393 int seed, nrOfShuffles;
4394
4395 void GetPositionNumber()
4396 {       // sets global variable seed
4397         int i;
4398
4399         seed = appData.defaultFrcPosition;
4400         if(seed < 0) { // randomize based on time for negative FRC position numbers
4401                 for(i=0; i<50; i++) seed += random();
4402                 seed = random() ^ random() >> 8 ^ random() << 8;
4403                 if(seed<0) seed = -seed;
4404         }
4405 }
4406
4407 int put(Board board, int pieceType, int rank, int n, int shade)
4408 // put the piece on the (n-1)-th empty squares of the given shade
4409 {
4410         int i;
4411
4412         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4413                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4414                         board[rank][i] = (ChessSquare) pieceType;
4415                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4416                         squaresLeft[ANY]--;
4417                         piecesLeft[pieceType]--;
4418                         return i;
4419                 }
4420         }
4421         return -1;
4422 }
4423
4424
4425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4426 // calculate where the next piece goes, (any empty square), and put it there
4427 {
4428         int i;
4429
4430         i = seed % squaresLeft[shade];
4431         nrOfShuffles *= squaresLeft[shade];
4432         seed /= squaresLeft[shade];
4433         put(board, pieceType, rank, i, shade);
4434 }
4435
4436 void AddTwoPieces(Board board, int pieceType, int rank)
4437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4438 {
4439         int i, n=squaresLeft[ANY], j=n-1, k;
4440
4441         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4442         i = seed % k;  // pick one
4443         nrOfShuffles *= k;
4444         seed /= k;
4445         while(i >= j) i -= j--;
4446         j = n - 1 - j; i += j;
4447         put(board, pieceType, rank, j, ANY);
4448         put(board, pieceType, rank, i, ANY);
4449 }
4450
4451 void SetUpShuffle(Board board, int number)
4452 {
4453         int i, p, first=1;
4454
4455         GetPositionNumber(); nrOfShuffles = 1;
4456
4457         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4458         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4459         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4460
4461         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4462
4463         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4464             p = (int) board[0][i];
4465             if(p < (int) BlackPawn) piecesLeft[p] ++;
4466             board[0][i] = EmptySquare;
4467         }
4468
4469         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4470             // shuffles restricted to allow normal castling put KRR first
4471             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4472                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4473             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4474                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4475             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4476                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4477             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4478                 put(board, WhiteRook, 0, 0, ANY);
4479             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4480         }
4481
4482         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4483             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4484             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4485                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4486                 while(piecesLeft[p] >= 2) {
4487                     AddOnePiece(board, p, 0, LITE);
4488                     AddOnePiece(board, p, 0, DARK);
4489                 }
4490                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4491             }
4492
4493         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4494             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4495             // but we leave King and Rooks for last, to possibly obey FRC restriction
4496             if(p == (int)WhiteRook) continue;
4497             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4498             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4499         }
4500
4501         // now everything is placed, except perhaps King (Unicorn) and Rooks
4502
4503         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4504             // Last King gets castling rights
4505             while(piecesLeft[(int)WhiteUnicorn]) {
4506                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4507                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4508             }
4509
4510             while(piecesLeft[(int)WhiteKing]) {
4511                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4512                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4513             }
4514
4515
4516         } else {
4517             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4518             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4519         }
4520
4521         // Only Rooks can be left; simply place them all
4522         while(piecesLeft[(int)WhiteRook]) {
4523                 i = put(board, WhiteRook, 0, 0, ANY);
4524                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4525                         if(first) {
4526                                 first=0;
4527                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4528                         }
4529                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4530                 }
4531         }
4532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4533             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4534         }
4535
4536         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4537 }
4538
4539 int SetCharTable( char *table, const char * map )
4540 /* [HGM] moved here from winboard.c because of its general usefulness */
4541 /*       Basically a safe strcpy that uses the last character as King */
4542 {
4543     int result = FALSE; int NrPieces;
4544
4545     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4546                     && NrPieces >= 12 && !(NrPieces&1)) {
4547         int i; /* [HGM] Accept even length from 12 to 34 */
4548
4549         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4550         for( i=0; i<NrPieces/2-1; i++ ) {
4551             table[i] = map[i];
4552             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4553         }
4554         table[(int) WhiteKing]  = map[NrPieces/2-1];
4555         table[(int) BlackKing]  = map[NrPieces-1];
4556
4557         result = TRUE;
4558     }
4559
4560     return result;
4561 }
4562
4563 void Prelude(Board board)
4564 {       // [HGM] superchess: random selection of exo-pieces
4565         int i, j, k; ChessSquare p;
4566         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4567
4568         GetPositionNumber(); // use FRC position number
4569
4570         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4571             SetCharTable(pieceToChar, appData.pieceToCharTable);
4572             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4573                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4574         }
4575
4576         j = seed%4;                 seed /= 4;
4577         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4579         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4580         j = seed%3 + (seed%3 >= j); seed /= 3;
4581         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4582         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4583         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4584         j = seed%3;                 seed /= 3;
4585         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4588         j = seed%2 + (seed%2 >= j); seed /= 2;
4589         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4592         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4593         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4594         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4595         put(board, exoPieces[0],    0, 0, ANY);
4596         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4597 }
4598
4599 void
4600 InitPosition(redraw)
4601      int redraw;
4602 {
4603     ChessSquare (* pieces)[BOARD_SIZE];
4604     int i, j, pawnRow, overrule,
4605     oldx = gameInfo.boardWidth,
4606     oldy = gameInfo.boardHeight,
4607     oldh = gameInfo.holdingsWidth,
4608     oldv = gameInfo.variant;
4609
4610     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4611
4612     /* [AS] Initialize pv info list [HGM] and game status */
4613     {
4614         for( i=0; i<MAX_MOVES; i++ ) {
4615             pvInfoList[i].depth = 0;
4616             epStatus[i]=EP_NONE;
4617             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4618         }
4619
4620         initialRulePlies = 0; /* 50-move counter start */
4621
4622         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4623         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4624     }
4625
4626
4627     /* [HGM] logic here is completely changed. In stead of full positions */
4628     /* the initialized data only consist of the two backranks. The switch */
4629     /* selects which one we will use, which is than copied to the Board   */
4630     /* initialPosition, which for the rest is initialized by Pawns and    */
4631     /* empty squares. This initial position is then copied to boards[0],  */
4632     /* possibly after shuffling, so that it remains available.            */
4633
4634     gameInfo.holdingsWidth = 0; /* default board sizes */
4635     gameInfo.boardWidth    = 8;
4636     gameInfo.boardHeight   = 8;
4637     gameInfo.holdingsSize  = 0;
4638     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4639     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4640     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4641
4642     switch (gameInfo.variant) {
4643     case VariantFischeRandom:
4644       shuffleOpenings = TRUE;
4645     default:
4646       pieces = FIDEArray;
4647       break;
4648     case VariantShatranj:
4649       pieces = ShatranjArray;
4650       nrCastlingRights = 0;
4651       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4652       break;
4653     case VariantTwoKings:
4654       pieces = twoKingsArray;
4655       break;
4656     case VariantCapaRandom:
4657       shuffleOpenings = TRUE;
4658     case VariantCapablanca:
4659       pieces = CapablancaArray;
4660       gameInfo.boardWidth = 10;
4661       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4662       break;
4663     case VariantGothic:
4664       pieces = GothicArray;
4665       gameInfo.boardWidth = 10;
4666       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4667       break;
4668     case VariantJanus:
4669       pieces = JanusArray;
4670       gameInfo.boardWidth = 10;
4671       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4672       nrCastlingRights = 6;
4673         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4674         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4675         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4676         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4677         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4678         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4679       break;
4680     case VariantFalcon:
4681       pieces = FalconArray;
4682       gameInfo.boardWidth = 10;
4683       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4684       break;
4685     case VariantXiangqi:
4686       pieces = XiangqiArray;
4687       gameInfo.boardWidth  = 9;
4688       gameInfo.boardHeight = 10;
4689       nrCastlingRights = 0;
4690       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4691       break;
4692     case VariantShogi:
4693       pieces = ShogiArray;
4694       gameInfo.boardWidth  = 9;
4695       gameInfo.boardHeight = 9;
4696       gameInfo.holdingsSize = 7;
4697       nrCastlingRights = 0;
4698       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4699       break;
4700     case VariantCourier:
4701       pieces = CourierArray;
4702       gameInfo.boardWidth  = 12;
4703       nrCastlingRights = 0;
4704       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4705       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4706       break;
4707     case VariantKnightmate:
4708       pieces = KnightmateArray;
4709       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4710       break;
4711     case VariantFairy:
4712       pieces = fairyArray;
4713       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4714       break;
4715     case VariantGreat:
4716       pieces = GreatArray;
4717       gameInfo.boardWidth = 10;
4718       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4719       gameInfo.holdingsSize = 8;
4720       break;
4721     case VariantSuper:
4722       pieces = FIDEArray;
4723       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4724       gameInfo.holdingsSize = 8;
4725       startedFromSetupPosition = TRUE;
4726       break;
4727     case VariantCrazyhouse:
4728     case VariantBughouse:
4729       pieces = FIDEArray;
4730       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4731       gameInfo.holdingsSize = 5;
4732       break;
4733     case VariantWildCastle:
4734       pieces = FIDEArray;
4735       /* !!?shuffle with kings guaranteed to be on d or e file */
4736       shuffleOpenings = 1;
4737       break;
4738     case VariantNoCastle:
4739       pieces = FIDEArray;
4740       nrCastlingRights = 0;
4741       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4742       /* !!?unconstrained back-rank shuffle */
4743       shuffleOpenings = 1;
4744       break;
4745     }
4746
4747     overrule = 0;
4748     if(appData.NrFiles >= 0) {
4749         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4750         gameInfo.boardWidth = appData.NrFiles;
4751     }
4752     if(appData.NrRanks >= 0) {
4753         gameInfo.boardHeight = appData.NrRanks;
4754     }
4755     if(appData.holdingsSize >= 0) {
4756         i = appData.holdingsSize;
4757         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4758         gameInfo.holdingsSize = i;
4759     }
4760     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4761     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4762         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4763
4764     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4765     if(pawnRow < 1) pawnRow = 1;
4766
4767     /* User pieceToChar list overrules defaults */
4768     if(appData.pieceToCharTable != NULL)
4769         SetCharTable(pieceToChar, appData.pieceToCharTable);
4770
4771     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4772
4773         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4774             s = (ChessSquare) 0; /* account holding counts in guard band */
4775         for( i=0; i<BOARD_HEIGHT; i++ )
4776             initialPosition[i][j] = s;
4777
4778         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4779         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4780         initialPosition[pawnRow][j] = WhitePawn;
4781         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4782         if(gameInfo.variant == VariantXiangqi) {
4783             if(j&1) {
4784                 initialPosition[pawnRow][j] =
4785                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4786                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4787                    initialPosition[2][j] = WhiteCannon;
4788                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4789                 }
4790             }
4791         }
4792         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4793     }
4794     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4795
4796             j=BOARD_LEFT+1;
4797             initialPosition[1][j] = WhiteBishop;
4798             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4799             j=BOARD_RGHT-2;
4800             initialPosition[1][j] = WhiteRook;
4801             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4802     }
4803
4804     if( nrCastlingRights == -1) {
4805         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4806         /*       This sets default castling rights from none to normal corners   */
4807         /* Variants with other castling rights must set them themselves above    */
4808         nrCastlingRights = 6;
4809
4810         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4811         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4812         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4813         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4814         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4815         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4816      }
4817
4818      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4819      if(gameInfo.variant == VariantGreat) { // promotion commoners
4820         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4821         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4822         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4823         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4824      }
4825   if (appData.debugMode) {
4826     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4827   }
4828     if(shuffleOpenings) {
4829         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4830         startedFromSetupPosition = TRUE;
4831     }
4832     if(startedFromPositionFile) {
4833       /* [HGM] loadPos: use PositionFile for every new game */
4834       CopyBoard(initialPosition, filePosition);
4835       for(i=0; i<nrCastlingRights; i++)
4836           castlingRights[0][i] = initialRights[i] = fileRights[i];
4837       startedFromSetupPosition = TRUE;
4838     }
4839
4840     CopyBoard(boards[0], initialPosition);
4841     if(oldx != gameInfo.boardWidth ||
4842        oldy != gameInfo.boardHeight ||
4843        oldh != gameInfo.holdingsWidth
4844 #ifdef GOTHIC
4845        || oldv == VariantGothic ||        // For licensing popups
4846        gameInfo.variant == VariantGothic
4847 #endif
4848 #ifdef FALCON
4849        || oldv == VariantFalcon ||
4850        gameInfo.variant == VariantFalcon
4851 #endif
4852                                          )
4853       {
4854             InitDrawingSizes(-2 ,0);
4855       }
4856
4857     if (redraw)
4858       DrawPosition(TRUE, boards[currentMove]);
4859
4860 }
4861
4862 void
4863 SendBoard(cps, moveNum)
4864      ChessProgramState *cps;
4865      int moveNum;
4866 {
4867     char message[MSG_SIZ];
4868
4869     if (cps->useSetboard) {
4870       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4871       sprintf(message, "setboard %s\n", fen);
4872       SendToProgram(message, cps);
4873       free(fen);
4874
4875     } else {
4876       ChessSquare *bp;
4877       int i, j;
4878       /* Kludge to set black to move, avoiding the troublesome and now
4879        * deprecated "black" command.
4880        */
4881       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4882
4883       SendToProgram("edit\n", cps);
4884       SendToProgram("#\n", cps);
4885       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4886         bp = &boards[moveNum][i][BOARD_LEFT];
4887         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4888           if ((int) *bp < (int) BlackPawn) {
4889             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4890                     AAA + j, ONE + i);
4891             if(message[0] == '+' || message[0] == '~') {
4892                 sprintf(message, "%c%c%c+\n",
4893                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4894                         AAA + j, ONE + i);
4895             }
4896             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4897                 message[1] = BOARD_RGHT   - 1 - j + '1';
4898                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4899             }
4900             SendToProgram(message, cps);
4901           }
4902         }
4903       }
4904
4905       SendToProgram("c\n", cps);
4906       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4907         bp = &boards[moveNum][i][BOARD_LEFT];
4908         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4909           if (((int) *bp != (int) EmptySquare)
4910               && ((int) *bp >= (int) BlackPawn)) {
4911             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4912                     AAA + j, ONE + i);
4913             if(message[0] == '+' || message[0] == '~') {
4914                 sprintf(message, "%c%c%c+\n",
4915                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4916                         AAA + j, ONE + i);
4917             }
4918             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4919                 message[1] = BOARD_RGHT   - 1 - j + '1';
4920                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4921             }
4922             SendToProgram(message, cps);
4923           }
4924         }
4925       }
4926
4927       SendToProgram(".\n", cps);
4928     }
4929     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4930 }
4931
4932 int
4933 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4934 {
4935     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4936     /* [HGM] add Shogi promotions */
4937     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4938     ChessSquare piece;
4939     ChessMove moveType;
4940     Boolean premove;
4941
4942     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4943     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4944
4945     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4946       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4947         return FALSE;
4948
4949     piece = boards[currentMove][fromY][fromX];
4950     if(gameInfo.variant == VariantShogi) {
4951         promotionZoneSize = 3;
4952         highestPromotingPiece = (int)WhiteFerz;
4953     }
4954
4955     // next weed out all moves that do not touch the promotion zone at all
4956     if((int)piece >= BlackPawn) {
4957         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4958              return FALSE;
4959         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4960     } else {
4961         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4962            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4963     }
4964
4965     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4966
4967     // weed out mandatory Shogi promotions
4968     if(gameInfo.variant == VariantShogi) {
4969         if(piece >= BlackPawn) {
4970             if(toY == 0 && piece == BlackPawn ||
4971                toY == 0 && piece == BlackQueen ||
4972                toY <= 1 && piece == BlackKnight) {
4973                 *promoChoice = '+';
4974                 return FALSE;
4975             }
4976         } else {
4977             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4978                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4979                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4980                 *promoChoice = '+';
4981                 return FALSE;
4982             }
4983         }
4984     }
4985
4986     // weed out obviously illegal Pawn moves
4987     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4988         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4989         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4990         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4991         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4992         // note we are not allowed to test for valid (non-)capture, due to premove
4993     }
4994
4995     // we either have a choice what to promote to, or (in Shogi) whether to promote
4996     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4997         *promoChoice = PieceToChar(BlackFerz);  // no choice
4998         return FALSE;
4999     }
5000     if(appData.alwaysPromoteToQueen) { // predetermined
5001         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5002              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5003         else *promoChoice = PieceToChar(BlackQueen);
5004         return FALSE;
5005     }
5006
5007     // suppress promotion popup on illegal moves that are not premoves
5008     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5009               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5010     if(appData.testLegality && !premove) {
5011         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5012                         epStatus[currentMove], castlingRights[currentMove],
5013                         fromY, fromX, toY, toX, NULLCHAR);
5014         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5015            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5016             return FALSE;
5017     }
5018
5019     return TRUE;
5020 }
5021
5022 int
5023 InPalace(row, column)
5024      int row, column;
5025 {   /* [HGM] for Xiangqi */
5026     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5027          column < (BOARD_WIDTH + 4)/2 &&
5028          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5029     return FALSE;
5030 }
5031
5032 int
5033 PieceForSquare (x, y)
5034      int x;
5035      int y;
5036 {
5037   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5038      return -1;
5039   else
5040      return boards[currentMove][y][x];
5041 }
5042
5043 int
5044 OKToStartUserMove(x, y)
5045      int x, y;
5046 {
5047     ChessSquare from_piece;
5048     int white_piece;
5049
5050     if (matchMode) return FALSE;
5051     if (gameMode == EditPosition) return TRUE;
5052
5053     if (x >= 0 && y >= 0)
5054       from_piece = boards[currentMove][y][x];
5055     else
5056       from_piece = EmptySquare;
5057
5058     if (from_piece == EmptySquare) return FALSE;
5059
5060     white_piece = (int)from_piece >= (int)WhitePawn &&
5061       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5062
5063     switch (gameMode) {
5064       case PlayFromGameFile:
5065       case AnalyzeFile:
5066       case TwoMachinesPlay:
5067       case EndOfGame:
5068         return FALSE;
5069
5070       case IcsObserving:
5071       case IcsIdle:
5072         return FALSE;
5073
5074       case MachinePlaysWhite:
5075       case IcsPlayingBlack:
5076         if (appData.zippyPlay) return FALSE;
5077         if (white_piece) {
5078             DisplayMoveError(_("You are playing Black"));
5079             return FALSE;
5080         }
5081         break;
5082
5083       case MachinePlaysBlack:
5084       case IcsPlayingWhite:
5085         if (appData.zippyPlay) return FALSE;
5086         if (!white_piece) {
5087             DisplayMoveError(_("You are playing White"));
5088             return FALSE;
5089         }
5090         break;
5091
5092       case EditGame:
5093         if (!white_piece && WhiteOnMove(currentMove)) {
5094             DisplayMoveError(_("It is White's turn"));
5095             return FALSE;
5096         }
5097         if (white_piece && !WhiteOnMove(currentMove)) {
5098             DisplayMoveError(_("It is Black's turn"));
5099             return FALSE;
5100         }
5101         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5102             /* Editing correspondence game history */
5103             /* Could disallow this or prompt for confirmation */
5104             cmailOldMove = -1;
5105         }
5106         if (currentMove < forwardMostMove) {
5107             /* Discarding moves */
5108             /* Could prompt for confirmation here,
5109                but I don't think that's such a good idea */
5110             forwardMostMove = currentMove;
5111         }
5112         break;
5113
5114       case BeginningOfGame:
5115         if (appData.icsActive) return FALSE;
5116         if (!appData.noChessProgram) {
5117             if (!white_piece) {
5118                 DisplayMoveError(_("You are playing White"));
5119                 return FALSE;
5120             }
5121         }
5122         break;
5123
5124       case Training:
5125         if (!white_piece && WhiteOnMove(currentMove)) {
5126             DisplayMoveError(_("It is White's turn"));
5127             return FALSE;
5128         }
5129         if (white_piece && !WhiteOnMove(currentMove)) {
5130             DisplayMoveError(_("It is Black's turn"));
5131             return FALSE;
5132         }
5133         break;
5134
5135       default:
5136       case IcsExamining:
5137         break;
5138     }
5139     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5140         && gameMode != AnalyzeFile && gameMode != Training) {
5141         DisplayMoveError(_("Displayed position is not current"));
5142         return FALSE;
5143     }
5144     return TRUE;
5145 }
5146
5147 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5148 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5149 int lastLoadGameUseList = FALSE;
5150 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5151 ChessMove lastLoadGameStart = (ChessMove) 0;
5152
5153 ChessMove
5154 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5155      int fromX, fromY, toX, toY;
5156      int promoChar;
5157      Boolean captureOwn;
5158 {
5159     ChessMove moveType;
5160     ChessSquare pdown, pup;
5161
5162     /* Check if the user is playing in turn.  This is complicated because we
5163        let the user "pick up" a piece before it is his turn.  So the piece he
5164        tried to pick up may have been captured by the time he puts it down!
5165        Therefore we use the color the user is supposed to be playing in this
5166        test, not the color of the piece that is currently on the starting
5167        square---except in EditGame mode, where the user is playing both
5168        sides; fortunately there the capture race can't happen.  (It can
5169        now happen in IcsExamining mode, but that's just too bad.  The user
5170        will get a somewhat confusing message in that case.)
5171        */
5172
5173     switch (gameMode) {
5174       case PlayFromGameFile:
5175       case AnalyzeFile:
5176       case TwoMachinesPlay:
5177       case EndOfGame:
5178       case IcsObserving:
5179       case IcsIdle:
5180         /* We switched into a game mode where moves are not accepted,
5181            perhaps while the mouse button was down. */
5182         return ImpossibleMove;
5183
5184       case MachinePlaysWhite:
5185         /* User is moving for Black */
5186         if (WhiteOnMove(currentMove)) {
5187             DisplayMoveError(_("It is White's turn"));
5188             return ImpossibleMove;
5189         }
5190         break;
5191
5192       case MachinePlaysBlack:
5193         /* User is moving for White */
5194         if (!WhiteOnMove(currentMove)) {
5195             DisplayMoveError(_("It is Black's turn"));
5196             return ImpossibleMove;
5197         }
5198         break;
5199
5200       case EditGame:
5201       case IcsExamining:
5202       case BeginningOfGame:
5203       case AnalyzeMode:
5204       case Training:
5205         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5206             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5207             /* User is moving for Black */
5208             if (WhiteOnMove(currentMove)) {
5209                 DisplayMoveError(_("It is White's turn"));
5210                 return ImpossibleMove;
5211             }
5212         } else {
5213             /* User is moving for White */
5214             if (!WhiteOnMove(currentMove)) {
5215                 DisplayMoveError(_("It is Black's turn"));
5216                 return ImpossibleMove;
5217             }
5218         }
5219         break;
5220
5221       case IcsPlayingBlack:
5222         /* User is moving for Black */
5223         if (WhiteOnMove(currentMove)) {
5224             if (!appData.premove) {
5225                 DisplayMoveError(_("It is White's turn"));
5226             } else if (toX >= 0 && toY >= 0) {
5227                 premoveToX = toX;
5228                 premoveToY = toY;
5229                 premoveFromX = fromX;
5230                 premoveFromY = fromY;
5231                 premovePromoChar = promoChar;
5232                 gotPremove = 1;
5233                 if (appData.debugMode)
5234                     fprintf(debugFP, "Got premove: fromX %d,"
5235                             "fromY %d, toX %d, toY %d\n",
5236                             fromX, fromY, toX, toY);
5237             }
5238             return ImpossibleMove;
5239         }
5240         break;
5241
5242       case IcsPlayingWhite:
5243         /* User is moving for White */
5244         if (!WhiteOnMove(currentMove)) {
5245             if (!appData.premove) {
5246                 DisplayMoveError(_("It is Black's turn"));
5247             } else if (toX >= 0 && toY >= 0) {
5248                 premoveToX = toX;
5249                 premoveToY = toY;
5250                 premoveFromX = fromX;
5251                 premoveFromY = fromY;
5252                 premovePromoChar = promoChar;
5253                 gotPremove = 1;
5254                 if (appData.debugMode)
5255                     fprintf(debugFP, "Got premove: fromX %d,"
5256                             "fromY %d, toX %d, toY %d\n",
5257                             fromX, fromY, toX, toY);
5258             }
5259             return ImpossibleMove;
5260         }
5261         break;
5262
5263       default:
5264         break;
5265
5266       case EditPosition:
5267         /* EditPosition, empty square, or different color piece;
5268            click-click move is possible */
5269         if (toX == -2 || toY == -2) {
5270             boards[0][fromY][fromX] = EmptySquare;
5271             return AmbiguousMove;
5272         } else if (toX >= 0 && toY >= 0) {
5273             boards[0][toY][toX] = boards[0][fromY][fromX];
5274             boards[0][fromY][fromX] = EmptySquare;
5275             return AmbiguousMove;
5276         }
5277         return ImpossibleMove;
5278     }
5279
5280     if(toX < 0 || toY < 0) return ImpossibleMove;
5281     pdown = boards[currentMove][fromY][fromX];
5282     pup = boards[currentMove][toY][toX];
5283
5284     /* [HGM] If move started in holdings, it means a drop */
5285     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5286          if( pup != EmptySquare ) return ImpossibleMove;
5287          if(appData.testLegality) {
5288              /* it would be more logical if LegalityTest() also figured out
5289               * which drops are legal. For now we forbid pawns on back rank.
5290               * Shogi is on its own here...
5291               */
5292              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5293                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5294                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5295          }
5296          return WhiteDrop; /* Not needed to specify white or black yet */
5297     }
5298
5299     userOfferedDraw = FALSE;
5300
5301     /* [HGM] always test for legality, to get promotion info */
5302     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5303                           epStatus[currentMove], castlingRights[currentMove],
5304                                          fromY, fromX, toY, toX, promoChar);
5305     /* [HGM] but possibly ignore an IllegalMove result */
5306     if (appData.testLegality) {
5307         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5308             DisplayMoveError(_("Illegal move"));
5309             return ImpossibleMove;
5310         }
5311     }
5312     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5313     return moveType;
5314     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5315        function is made into one that returns an OK move type if FinishMove
5316        should be called. This to give the calling driver routine the
5317        opportunity to finish the userMove input with a promotion popup,
5318        without bothering the user with this for invalid or illegal moves */
5319
5320 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5321 }
5322
5323 /* Common tail of UserMoveEvent and DropMenuEvent */
5324 int
5325 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5326      ChessMove moveType;
5327      int fromX, fromY, toX, toY;
5328      /*char*/int promoChar;
5329 {
5330   char *bookHit = 0;
5331
5332   if(appData.debugMode)
5333     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5334
5335   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5336     {
5337       // [HGM] superchess: suppress promotions to non-available piece
5338       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5339       if(WhiteOnMove(currentMove))
5340         {
5341           if(!boards[currentMove][k][BOARD_WIDTH-2])
5342             return 0;
5343         }
5344       else
5345         {
5346           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5347             return 0;
5348         }
5349     }
5350   
5351   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5352      move type in caller when we know the move is a legal promotion */
5353   if(moveType == NormalMove && promoChar)
5354     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5355
5356   if(appData.debugMode) 
5357     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5358
5359   /* [HGM] convert drag-and-drop piece drops to standard form */
5360   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5361     {
5362       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5363       if(appData.debugMode) 
5364         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5365                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5366       //         fromX = boards[currentMove][fromY][fromX];
5367       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5368       if(fromX == 0) 
5369         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370
5371       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5372
5373       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5374         fromX++; 
5375
5376       fromY = DROP_RANK;
5377     }
5378
5379   /* [HGM] <popupFix> The following if has been moved here from
5380      UserMoveEvent(). Because it seemed to belon here (why not allow
5381      piece drops in training games?), and because it can only be
5382      performed after it is known to what we promote. */
5383   if (gameMode == Training)
5384     {
5385       /* compare the move played on the board to the next move in the
5386        * game. If they match, display the move and the opponent's response.
5387        * If they don't match, display an error message.
5388        */
5389       int saveAnimate;
5390       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5391       CopyBoard(testBoard, boards[currentMove]);
5392       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5393
5394       if (CompareBoards(testBoard, boards[currentMove+1]))
5395         {
5396           ForwardInner(currentMove+1);
5397
5398           /* Autoplay the opponent's response.
5399            * if appData.animate was TRUE when Training mode was entered,
5400            * the response will be animated.
5401            */
5402           saveAnimate = appData.animate;
5403           appData.animate = animateTraining;
5404           ForwardInner(currentMove+1);
5405           appData.animate = saveAnimate;
5406
5407           /* check for the end of the game */
5408           if (currentMove >= forwardMostMove)
5409             {
5410               gameMode = PlayFromGameFile;
5411               ModeHighlight();
5412               SetTrainingModeOff();
5413               DisplayInformation(_("End of game"));
5414             }
5415         }
5416       else
5417         {
5418           DisplayError(_("Incorrect move"), 0);
5419         }
5420       return 1;
5421     }
5422
5423   /* Ok, now we know that the move is good, so we can kill
5424      the previous line in Analysis Mode */
5425   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5426     {
5427       forwardMostMove = currentMove;
5428     }
5429
5430   /* If we need the chess program but it's dead, restart it */
5431   ResurrectChessProgram();
5432
5433   /* A user move restarts a paused game*/
5434   if (pausing)
5435     PauseEvent();
5436
5437   thinkOutput[0] = NULLCHAR;
5438
5439   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5440
5441   if (gameMode == BeginningOfGame)
5442     {
5443       if (appData.noChessProgram)
5444         {
5445           gameMode = EditGame;
5446           SetGameInfo();
5447         }
5448       else
5449         {
5450           char buf[MSG_SIZ];
5451           gameMode = MachinePlaysBlack;
5452           StartClocks();
5453           SetGameInfo();
5454           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5455           DisplayTitle(buf);
5456           if (first.sendName)
5457             {
5458               sprintf(buf, "name %s\n", gameInfo.white);
5459               SendToProgram(buf, &first);
5460             }
5461           StartClocks();
5462         }
5463       ModeHighlight();
5464     }
5465   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5466
5467   /* Relay move to ICS or chess engine */
5468   if (appData.icsActive)
5469     {
5470       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5471           gameMode == IcsExamining)
5472         {
5473           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5474           ics_user_moved = 1;
5475         }
5476     }
5477   else
5478     {
5479       if (first.sendTime && (gameMode == BeginningOfGame ||
5480                              gameMode == MachinePlaysWhite ||
5481                              gameMode == MachinePlaysBlack))
5482         {
5483           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5484         }
5485       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5486         {
5487           // [HGM] book: if program might be playing, let it use book
5488           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5489           first.maybeThinking = TRUE;
5490         }
5491       else
5492         SendMoveToProgram(forwardMostMove-1, &first);
5493       if (currentMove == cmailOldMove + 1)
5494         {
5495           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5496         }
5497     }
5498
5499   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5500
5501   switch (gameMode)
5502     {
5503     case EditGame:
5504       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5505                        EP_UNKNOWN, castlingRights[currentMove]) )
5506         {
5507         case MT_NONE:
5508         case MT_CHECK:
5509           break;
5510         case MT_CHECKMATE:
5511         case MT_STAINMATE:
5512           if (WhiteOnMove(currentMove))
5513             {
5514               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5515             }
5516           else
5517             {
5518               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5519             }
5520           break;
5521         case MT_STALEMATE:
5522           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5523           break;
5524     }
5525       break;
5526
5527     case MachinePlaysBlack:
5528     case MachinePlaysWhite:
5529       /* disable certain menu options while machine is thinking */
5530       SetMachineThinkingEnables();
5531       break;
5532
5533     default:
5534       break;
5535     }
5536
5537   if(bookHit)
5538     { // [HGM] book: simulate book reply
5539       static char bookMove[MSG_SIZ]; // a bit generous?
5540
5541       programStats.nodes = programStats.depth = programStats.time =
5542         programStats.score = programStats.got_only_move = 0;
5543       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5544
5545       strcpy(bookMove, "move ");
5546       strcat(bookMove, bookHit);
5547       HandleMachineMove(bookMove, &first);
5548     }
5549
5550   return 1;
5551 }
5552
5553 void
5554 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5555      int fromX, fromY, toX, toY;
5556      int promoChar;
5557 {
5558     /* [HGM] This routine was added to allow calling of its two logical
5559        parts from other modules in the old way. Before, UserMoveEvent()
5560        automatically called FinishMove() if the move was OK, and returned
5561        otherwise. I separated the two, in order to make it possible to
5562        slip a promotion popup in between. But that it always needs two
5563        calls, to the first part, (now called UserMoveTest() ), and to
5564        FinishMove if the first part succeeded. Calls that do not need
5565        to do anything in between, can call this routine the old way.
5566     */
5567     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5568 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5569     if(moveType == AmbiguousMove)
5570         DrawPosition(FALSE, boards[currentMove]);
5571     else if(moveType != ImpossibleMove && moveType != Comment)
5572         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5573 }
5574
5575 void LeftClick(ClickType clickType, int xPix, int yPix)
5576 {
5577     int x, y;
5578     Boolean saveAnimate;
5579     static int second = 0, promotionChoice = 0;
5580     char promoChoice = NULLCHAR;
5581
5582     if (clickType == Press) ErrorPopDown();
5583
5584     x = EventToSquare(xPix, BOARD_WIDTH);
5585     y = EventToSquare(yPix, BOARD_HEIGHT);
5586     if (!flipView && y >= 0) {
5587         y = BOARD_HEIGHT - 1 - y;
5588     }
5589     if (flipView && x >= 0) {
5590         x = BOARD_WIDTH - 1 - x;
5591     }
5592
5593     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5594         if(clickType == Release) return; // ignore upclick of click-click destination
5595         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5596         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5597         if(gameInfo.holdingsWidth && 
5598                 (WhiteOnMove(currentMove) 
5599                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5600                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5601             // click in right holdings, for determining promotion piece
5602             ChessSquare p = boards[currentMove][y][x];
5603             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5604             if(p != EmptySquare) {
5605                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5606                 fromX = fromY = -1;
5607                 return;
5608             }
5609         }
5610         DrawPosition(FALSE, boards[currentMove]);
5611         return;
5612     }
5613
5614     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5615     if(clickType == Press
5616             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5617               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5618               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5619         return;
5620
5621     if (fromX == -1) {
5622         if (clickType == Press) {
5623             /* First square */
5624             if (OKToStartUserMove(x, y)) {
5625                 fromX = x;
5626                 fromY = y;
5627                 second = 0;
5628                 DragPieceBegin(xPix, yPix);
5629                 if (appData.highlightDragging) {
5630                     SetHighlights(x, y, -1, -1);
5631                 }
5632             }
5633         }
5634         return;
5635     }
5636
5637     /* fromX != -1 */
5638     if (clickType == Press && gameMode != EditPosition) {
5639         ChessSquare fromP;
5640         ChessSquare toP;
5641         int frc;
5642
5643         // ignore off-board to clicks
5644         if(y < 0 || x < 0) return;
5645
5646         /* Check if clicking again on the same color piece */
5647         fromP = boards[currentMove][fromY][fromX];
5648         toP = boards[currentMove][y][x];
5649         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5650         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5651              WhitePawn <= toP && toP <= WhiteKing &&
5652              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5653              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5654             (BlackPawn <= fromP && fromP <= BlackKing && 
5655              BlackPawn <= toP && toP <= BlackKing &&
5656              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5657              !(fromP == BlackKing && toP == BlackRook && frc))) {
5658             /* Clicked again on same color piece -- changed his mind */
5659             second = (x == fromX && y == fromY);
5660             if (appData.highlightDragging) {
5661                 SetHighlights(x, y, -1, -1);
5662             } else {
5663                 ClearHighlights();
5664             }
5665             if (OKToStartUserMove(x, y)) {
5666                 fromX = x;
5667                 fromY = y;
5668                 DragPieceBegin(xPix, yPix);
5669             }
5670             return;
5671         }
5672         // ignore clicks on holdings
5673         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5674     }
5675
5676     if (clickType == Release && x == fromX && y == fromY) {
5677         DragPieceEnd(xPix, yPix);
5678         if (appData.animateDragging) {
5679             /* Undo animation damage if any */
5680             DrawPosition(FALSE, NULL);
5681         }
5682         if (second) {
5683             /* Second up/down in same square; just abort move */
5684             second = 0;
5685             fromX = fromY = -1;
5686             ClearHighlights();
5687             gotPremove = 0;
5688             ClearPremoveHighlights();
5689         } else {
5690             /* First upclick in same square; start click-click mode */
5691             SetHighlights(x, y, -1, -1);
5692         }
5693         return;
5694     }
5695
5696     /* we now have a different from- and (possibly off-board) to-square */
5697     /* Completed move */
5698     toX = x;
5699     toY = y;
5700     saveAnimate = appData.animate;
5701     if (clickType == Press) {
5702         /* Finish clickclick move */
5703         if (appData.animate || appData.highlightLastMove) {
5704             SetHighlights(fromX, fromY, toX, toY);
5705         } else {
5706             ClearHighlights();
5707         }
5708     } else {
5709         /* Finish drag move */
5710         if (appData.highlightLastMove) {
5711             SetHighlights(fromX, fromY, toX, toY);
5712         } else {
5713             ClearHighlights();
5714         }
5715         DragPieceEnd(xPix, yPix);
5716         /* Don't animate move and drag both */
5717         appData.animate = FALSE;
5718     }
5719
5720     // moves into holding are invalid for now (later perhaps allow in EditPosition)
5721     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5722         ClearHighlights();
5723         fromX = fromY = -1;
5724         DrawPosition(TRUE, NULL);
5725         return;
5726     }
5727
5728     // off-board moves should not be highlighted
5729     if(x < 0 || x < 0) ClearHighlights();
5730
5731     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5732         SetHighlights(fromX, fromY, toX, toY);
5733         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5734             // [HGM] super: promotion to captured piece selected from holdings
5735             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5736             promotionChoice = TRUE;
5737             // kludge follows to temporarily execute move on display, without promoting yet
5738             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5739             boards[currentMove][toY][toX] = p;
5740             DrawPosition(FALSE, boards[currentMove]);
5741             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5742             boards[currentMove][toY][toX] = q;
5743             DisplayMessage("Click in holdings to choose piece", "");
5744             return;
5745         }
5746         PromotionPopUp();
5747     } else {
5748         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5749         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5750         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5751         fromX = fromY = -1;
5752     }
5753     appData.animate = saveAnimate;
5754     if (appData.animate || appData.animateDragging) {
5755         /* Undo animation damage if needed */
5756         DrawPosition(FALSE, NULL);
5757     }
5758 }
5759
5760 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5761 {
5762 //    char * hint = lastHint;
5763     FrontEndProgramStats stats;
5764
5765     stats.which = cps == &first ? 0 : 1;
5766     stats.depth = cpstats->depth;
5767     stats.nodes = cpstats->nodes;
5768     stats.score = cpstats->score;
5769     stats.time = cpstats->time;
5770     stats.pv = cpstats->movelist;
5771     stats.hint = lastHint;
5772     stats.an_move_index = 0;
5773     stats.an_move_count = 0;
5774
5775     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5776         stats.hint = cpstats->move_name;
5777         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5778         stats.an_move_count = cpstats->nr_moves;
5779     }
5780
5781     SetProgramStats( &stats );
5782 }
5783
5784 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5785 {   // [HGM] book: this routine intercepts moves to simulate book replies
5786     char *bookHit = NULL;
5787
5788     //first determine if the incoming move brings opponent into his book
5789     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5790         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5791     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5792     if(bookHit != NULL && !cps->bookSuspend) {
5793         // make sure opponent is not going to reply after receiving move to book position
5794         SendToProgram("force\n", cps);
5795         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5796     }
5797     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5798     // now arrange restart after book miss
5799     if(bookHit) {
5800         // after a book hit we never send 'go', and the code after the call to this routine
5801         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5802         char buf[MSG_SIZ];
5803         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5804         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5805         SendToProgram(buf, cps);
5806         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5807     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5808         SendToProgram("go\n", cps);
5809         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5810     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5811         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5812             SendToProgram("go\n", cps);
5813         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5814     }
5815     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5816 }
5817
5818 char *savedMessage;
5819 ChessProgramState *savedState;
5820 void DeferredBookMove(void)
5821 {
5822         if(savedState->lastPing != savedState->lastPong)
5823                     ScheduleDelayedEvent(DeferredBookMove, 10);
5824         else
5825         HandleMachineMove(savedMessage, savedState);
5826 }
5827
5828 void
5829 HandleMachineMove(message, cps)
5830      char *message;
5831      ChessProgramState *cps;
5832 {
5833     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5834     char realname[MSG_SIZ];
5835     int fromX, fromY, toX, toY;
5836     ChessMove moveType;
5837     char promoChar;
5838     char *p;
5839     int machineWhite;
5840     char *bookHit;
5841
5842 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5843     /*
5844      * Kludge to ignore BEL characters
5845      */
5846     while (*message == '\007') message++;
5847
5848     /*
5849      * [HGM] engine debug message: ignore lines starting with '#' character
5850      */
5851     if(cps->debug && *message == '#') return;
5852
5853     /*
5854      * Look for book output
5855      */
5856     if (cps == &first && bookRequested) {
5857         if (message[0] == '\t' || message[0] == ' ') {
5858             /* Part of the book output is here; append it */
5859             strcat(bookOutput, message);
5860             strcat(bookOutput, "  \n");
5861             return;
5862         } else if (bookOutput[0] != NULLCHAR) {
5863             /* All of book output has arrived; display it */
5864             char *p = bookOutput;
5865             while (*p != NULLCHAR) {
5866                 if (*p == '\t') *p = ' ';
5867                 p++;
5868             }
5869             DisplayInformation(bookOutput);
5870             bookRequested = FALSE;
5871             /* Fall through to parse the current output */
5872         }
5873     }
5874
5875     /*
5876      * Look for machine move.
5877      */
5878     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5879         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5880     {
5881         /* This method is only useful on engines that support ping */
5882         if (cps->lastPing != cps->lastPong) {
5883           if (gameMode == BeginningOfGame) {
5884             /* Extra move from before last new; ignore */
5885             if (appData.debugMode) {
5886                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5887             }
5888           } else {
5889             if (appData.debugMode) {
5890                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5891                         cps->which, gameMode);
5892             }
5893
5894             SendToProgram("undo\n", cps);
5895           }
5896           return;
5897         }
5898
5899         switch (gameMode) {
5900           case BeginningOfGame:
5901             /* Extra move from before last reset; ignore */
5902             if (appData.debugMode) {
5903                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5904             }
5905             return;
5906
5907           case EndOfGame:
5908           case IcsIdle:
5909           default:
5910             /* Extra move after we tried to stop.  The mode test is
5911                not a reliable way of detecting this problem, but it's
5912                the best we can do on engines that don't support ping.
5913             */
5914             if (appData.debugMode) {
5915                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5916                         cps->which, gameMode);
5917             }
5918             SendToProgram("undo\n", cps);
5919             return;
5920
5921           case MachinePlaysWhite:
5922           case IcsPlayingWhite:
5923             machineWhite = TRUE;
5924             break;
5925
5926           case MachinePlaysBlack:
5927           case IcsPlayingBlack:
5928             machineWhite = FALSE;
5929             break;
5930
5931           case TwoMachinesPlay:
5932             machineWhite = (cps->twoMachinesColor[0] == 'w');
5933             break;
5934         }
5935         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5936             if (appData.debugMode) {
5937                 fprintf(debugFP,
5938                         "Ignoring move out of turn by %s, gameMode %d"
5939                         ", forwardMost %d\n",
5940                         cps->which, gameMode, forwardMostMove);
5941             }
5942             return;
5943         }
5944
5945     if (appData.debugMode) { int f = forwardMostMove;
5946         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5947                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5948     }
5949         if(cps->alphaRank) AlphaRank(machineMove, 4);
5950         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5951                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5952             /* Machine move could not be parsed; ignore it. */
5953             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5954                     machineMove, cps->which);
5955             DisplayError(buf1, 0);
5956             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5957                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5958             if (gameMode == TwoMachinesPlay) {
5959               GameEnds(machineWhite ? BlackWins : WhiteWins,
5960                        buf1, GE_XBOARD);
5961             }
5962             return;
5963         }
5964
5965         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5966         /* So we have to redo legality test with true e.p. status here,  */
5967         /* to make sure an illegal e.p. capture does not slip through,   */
5968         /* to cause a forfeit on a justified illegal-move complaint      */
5969         /* of the opponent.                                              */
5970         if( gameMode==TwoMachinesPlay && appData.testLegality
5971             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5972                                                               ) {
5973            ChessMove moveType;
5974            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5975                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5976                              fromY, fromX, toY, toX, promoChar);
5977             if (appData.debugMode) {
5978                 int i;
5979                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5980                     castlingRights[forwardMostMove][i], castlingRank[i]);
5981                 fprintf(debugFP, "castling rights\n");
5982             }
5983             if(moveType == IllegalMove) {
5984                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5985                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5986                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5987                            buf1, GE_XBOARD);
5988                 return;
5989            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5990            /* [HGM] Kludge to handle engines that send FRC-style castling
5991               when they shouldn't (like TSCP-Gothic) */
5992            switch(moveType) {
5993              case WhiteASideCastleFR:
5994              case BlackASideCastleFR:
5995                toX+=2;
5996                currentMoveString[2]++;
5997                break;
5998              case WhiteHSideCastleFR:
5999              case BlackHSideCastleFR:
6000                toX--;
6001                currentMoveString[2]--;
6002                break;
6003              default: ; // nothing to do, but suppresses warning of pedantic compilers
6004            }
6005         }
6006         hintRequested = FALSE;
6007         lastHint[0] = NULLCHAR;
6008         bookRequested = FALSE;
6009         /* Program may be pondering now */
6010         cps->maybeThinking = TRUE;
6011         if (cps->sendTime == 2) cps->sendTime = 1;
6012         if (cps->offeredDraw) cps->offeredDraw--;
6013
6014 #if ZIPPY
6015         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6016             first.initDone) {
6017           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6018           ics_user_moved = 1;
6019           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6020                 char buf[3*MSG_SIZ];
6021
6022                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6023                         programStats.score / 100.,
6024                         programStats.depth,
6025                         programStats.time / 100.,
6026                         (unsigned int)programStats.nodes,
6027                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6028                         programStats.movelist);
6029                 SendToICS(buf);
6030 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6031           }
6032         }
6033 #endif
6034         /* currentMoveString is set as a side-effect of ParseOneMove */
6035         strcpy(machineMove, currentMoveString);
6036         strcat(machineMove, "\n");
6037         strcpy(moveList[forwardMostMove], machineMove);
6038
6039         /* [AS] Save move info and clear stats for next move */
6040         pvInfoList[ forwardMostMove ].score = programStats.score;
6041         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6042         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6043         ClearProgramStats();
6044         thinkOutput[0] = NULLCHAR;
6045         hiddenThinkOutputState = 0;
6046
6047         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6048
6049         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6050         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6051             int count = 0;
6052
6053             while( count < adjudicateLossPlies ) {
6054                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6055
6056                 if( count & 1 ) {
6057                     score = -score; /* Flip score for winning side */
6058                 }
6059
6060                 if( score > adjudicateLossThreshold ) {
6061                     break;
6062                 }
6063
6064                 count++;
6065             }
6066
6067             if( count >= adjudicateLossPlies ) {
6068                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069
6070                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6071                     "Xboard adjudication",
6072                     GE_XBOARD );
6073
6074                 return;
6075             }
6076         }
6077
6078         if( gameMode == TwoMachinesPlay ) {
6079           // [HGM] some adjudications useful with buggy engines
6080             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6081           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6082
6083
6084             if( appData.testLegality )
6085             {   /* [HGM] Some more adjudications for obstinate engines */
6086                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6087                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6088                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6089                 static int moveCount = 6;
6090                 ChessMove result;
6091                 char *reason = NULL;
6092
6093                 /* Count what is on board. */
6094                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6095                 {   ChessSquare p = boards[forwardMostMove][i][j];
6096                     int m=i;
6097
6098                     switch((int) p)
6099                     {   /* count B,N,R and other of each side */
6100                         case WhiteKing:
6101                         case BlackKing:
6102                              NrK++; break; // [HGM] atomic: count Kings
6103                         case WhiteKnight:
6104                              NrWN++; break;
6105                         case WhiteBishop:
6106                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6107                              bishopsColor |= 1 << ((i^j)&1);
6108                              NrWB++; break;
6109                         case BlackKnight:
6110                              NrBN++; break;
6111                         case BlackBishop:
6112                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6113                              bishopsColor |= 1 << ((i^j)&1);
6114                              NrBB++; break;
6115                         case WhiteRook:
6116                              NrWR++; break;
6117                         case BlackRook:
6118                              NrBR++; break;
6119                         case WhiteQueen:
6120                              NrWQ++; break;
6121                         case BlackQueen:
6122                              NrBQ++; break;
6123                         case EmptySquare:
6124                              break;
6125                         case BlackPawn:
6126                              m = 7-i;
6127                         case WhitePawn:
6128                              PawnAdvance += m; NrPawns++;
6129                     }
6130                     NrPieces += (p != EmptySquare);
6131                     NrW += ((int)p < (int)BlackPawn);
6132                     if(gameInfo.variant == VariantXiangqi &&
6133                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6134                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6135                         NrW -= ((int)p < (int)BlackPawn);
6136                     }
6137                 }
6138
6139                 /* Some material-based adjudications that have to be made before stalemate test */
6140                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6141                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6142                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6143                      if(appData.checkMates) {
6144                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6145                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6147                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6148                          return;
6149                      }
6150                 }
6151
6152                 /* Bare King in Shatranj (loses) or Losers (wins) */
6153                 if( NrW == 1 || NrPieces - NrW == 1) {
6154                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6155                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6156                      if(appData.checkMates) {
6157                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6158                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6159                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6160                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6161                          return;
6162                      }
6163                   } else
6164                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6165                   {    /* bare King */
6166                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6167                         if(appData.checkMates) {
6168                             /* but only adjudicate if adjudication enabled */
6169                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6170                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6171                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6172                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6173                             return;
6174                         }
6175                   }
6176                 } else bare = 1;
6177
6178
6179             // don't wait for engine to announce game end if we can judge ourselves
6180             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6181                                        castlingRights[forwardMostMove]) ) {
6182               case MT_CHECK:
6183                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6184                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6185                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6186                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6187                             checkCnt++;
6188                         if(checkCnt >= 2) {
6189                             reason = "Xboard adjudication: 3rd check";
6190                             epStatus[forwardMostMove] = EP_CHECKMATE;
6191                             break;
6192                         }
6193                     }
6194                 }
6195               case MT_NONE:
6196               default:
6197                 break;
6198               case MT_STALEMATE:
6199               case MT_STAINMATE:
6200                 reason = "Xboard adjudication: Stalemate";
6201                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6202                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6203                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6204                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6205                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6206                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6207                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6208                                                                         EP_CHECKMATE : EP_WINS);
6209                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6210                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6211                 }
6212                 break;
6213               case MT_CHECKMATE:
6214                 reason = "Xboard adjudication: Checkmate";
6215                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6216                 break;
6217             }
6218
6219                 switch(i = epStatus[forwardMostMove]) {
6220                     case EP_STALEMATE:
6221                         result = GameIsDrawn; break;
6222                     case EP_CHECKMATE:
6223                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6224                     case EP_WINS:
6225                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6226                     default:
6227                         result = (ChessMove) 0;
6228                 }
6229                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6230                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6231                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6232                     GameEnds( result, reason, GE_XBOARD );
6233                     return;
6234                 }
6235
6236                 /* Next absolutely insufficient mating material. */
6237                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6238                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6239                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6240                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6241                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6242
6243                      /* always flag draws, for judging claims */
6244                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6245
6246                      if(appData.materialDraws) {
6247                          /* but only adjudicate them if adjudication enabled */
6248                          SendToProgram("force\n", cps->other); // suppress reply
6249                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6250                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6251                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6252                          return;
6253                      }
6254                 }
6255
6256                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6257                 if(NrPieces == 4 &&
6258                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6259                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6260                    || NrWN==2 || NrBN==2     /* KNNK */
6261                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6262                   ) ) {
6263                      if(--moveCount < 0 && appData.trivialDraws)
6264                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6265                           SendToProgram("force\n", cps->other); // suppress reply
6266                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6267                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6268                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6269                           return;
6270                      }
6271                 } else moveCount = 6;
6272             }
6273           }
6274           
6275           if (appData.debugMode) { int i;
6276             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6277                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6278                     appData.drawRepeats);
6279             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6280               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6281             
6282           }
6283
6284                 /* Check for rep-draws */
6285                 count = 0;
6286                 for(k = forwardMostMove-2;
6287                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6288                         epStatus[k] < EP_UNKNOWN &&
6289                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6290                     k-=2)
6291                 {   int rights=0;
6292                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6293                         /* compare castling rights */
6294                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6295                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6296                                 rights++; /* King lost rights, while rook still had them */
6297                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6298                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6299                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6300                                    rights++; /* but at least one rook lost them */
6301                         }
6302                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6303                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6304                                 rights++;
6305                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6306                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6307                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6308                                    rights++;
6309                         }
6310                         if( rights == 0 && ++count > appData.drawRepeats-2
6311                             && appData.drawRepeats > 1) {
6312                              /* adjudicate after user-specified nr of repeats */
6313                              SendToProgram("force\n", cps->other); // suppress reply
6314                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6315                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6316                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6317                                 // [HGM] xiangqi: check for forbidden perpetuals
6318                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6319                                 for(m=forwardMostMove; m>k; m-=2) {
6320                                     if(MateTest(boards[m], PosFlags(m),
6321                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6322                                         ourPerpetual = 0; // the current mover did not always check
6323                                     if(MateTest(boards[m-1], PosFlags(m-1),
6324                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6325                                         hisPerpetual = 0; // the opponent did not always check
6326                                 }
6327                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6328                                                                         ourPerpetual, hisPerpetual);
6329                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6330                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6331                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6332                                     return;
6333                                 }
6334                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6335                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6336                                 // Now check for perpetual chases
6337                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6338                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6339                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6340                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6341                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6342                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6343                                         return;
6344                                     }
6345                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6346                                         break; // Abort repetition-checking loop.
6347                                 }
6348                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6349                              }
6350                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6351                              return;
6352                         }
6353                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6354                              epStatus[forwardMostMove] = EP_REP_DRAW;
6355                     }
6356                 }
6357
6358                 /* Now we test for 50-move draws. Determine ply count */
6359                 count = forwardMostMove;
6360                 /* look for last irreversble move */
6361                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6362                     count--;
6363                 /* if we hit starting position, add initial plies */
6364                 if( count == backwardMostMove )
6365                     count -= initialRulePlies;
6366                 count = forwardMostMove - count;
6367                 if( count >= 100)
6368                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6369                          /* this is used to judge if draw claims are legal */
6370                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6371                          SendToProgram("force\n", cps->other); // suppress reply
6372                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6373                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6374                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6375                          return;
6376                 }
6377
6378                 /* if draw offer is pending, treat it as a draw claim
6379                  * when draw condition present, to allow engines a way to
6380                  * claim draws before making their move to avoid a race
6381                  * condition occurring after their move
6382                  */
6383                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6384                          char *p = NULL;
6385                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6386                              p = "Draw claim: 50-move rule";
6387                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6388                              p = "Draw claim: 3-fold repetition";
6389                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6390                              p = "Draw claim: insufficient mating material";
6391                          if( p != NULL ) {
6392                              SendToProgram("force\n", cps->other); // suppress reply
6393                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6394                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6395                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6396                              return;
6397                          }
6398                 }
6399
6400
6401                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6402                     SendToProgram("force\n", cps->other); // suppress reply
6403                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6404                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6405
6406                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6407
6408                     return;
6409                 }
6410         }
6411
6412         bookHit = NULL;
6413         if (gameMode == TwoMachinesPlay) {
6414             /* [HGM] relaying draw offers moved to after reception of move */
6415             /* and interpreting offer as claim if it brings draw condition */
6416             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6417                 SendToProgram("draw\n", cps->other);
6418             }
6419             if (cps->other->sendTime) {
6420                 SendTimeRemaining(cps->other,
6421                                   cps->other->twoMachinesColor[0] == 'w');
6422             }
6423             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6424             if (firstMove && !bookHit) {
6425                 firstMove = FALSE;
6426                 if (cps->other->useColors) {
6427                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6428                 }
6429                 SendToProgram("go\n", cps->other);
6430             }
6431             cps->other->maybeThinking = TRUE;
6432         }
6433
6434         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6435
6436         if (!pausing && appData.ringBellAfterMoves) {
6437             RingBell();
6438         }
6439
6440         /*
6441          * Reenable menu items that were disabled while
6442          * machine was thinking
6443          */
6444         if (gameMode != TwoMachinesPlay)
6445             SetUserThinkingEnables();
6446
6447         // [HGM] book: after book hit opponent has received move and is now in force mode
6448         // force the book reply into it, and then fake that it outputted this move by jumping
6449         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6450         if(bookHit) {
6451                 static char bookMove[MSG_SIZ]; // a bit generous?
6452
6453                 strcpy(bookMove, "move ");
6454                 strcat(bookMove, bookHit);
6455                 message = bookMove;
6456                 cps = cps->other;
6457                 programStats.nodes = programStats.depth = programStats.time =
6458                 programStats.score = programStats.got_only_move = 0;
6459                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6460
6461                 if(cps->lastPing != cps->lastPong) {
6462                     savedMessage = message; // args for deferred call
6463                     savedState = cps;
6464                     ScheduleDelayedEvent(DeferredBookMove, 10);
6465                     return;
6466                 }
6467                 goto FakeBookMove;
6468         }
6469
6470         return;
6471     }
6472
6473     /* Set special modes for chess engines.  Later something general
6474      *  could be added here; for now there is just one kludge feature,
6475      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6476      *  when "xboard" is given as an interactive command.
6477      */
6478     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6479         cps->useSigint = FALSE;
6480         cps->useSigterm = FALSE;
6481     }
6482     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6483       ParseFeatures(message+8, cps);
6484       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6485     }
6486
6487     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6488      * want this, I was asked to put it in, and obliged.
6489      */
6490     if (!strncmp(message, "setboard ", 9)) {
6491         Board initial_position; int i;
6492
6493         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6494
6495         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6496             DisplayError(_("Bad FEN received from engine"), 0);
6497             return ;
6498         } else {
6499            Reset(TRUE, FALSE);
6500            CopyBoard(boards[0], initial_position);
6501            initialRulePlies = FENrulePlies;
6502            epStatus[0] = FENepStatus;
6503            for( i=0; i<nrCastlingRights; i++ )
6504                 castlingRights[0][i] = FENcastlingRights[i];
6505            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6506            else gameMode = MachinePlaysBlack;
6507            DrawPosition(FALSE, boards[currentMove]);
6508         }
6509         return;
6510     }
6511
6512     /*
6513      * Look for communication commands
6514      */
6515     if (!strncmp(message, "telluser ", 9)) {
6516         DisplayNote(message + 9);
6517         return;
6518     }
6519     if (!strncmp(message, "tellusererror ", 14)) {
6520         DisplayError(message + 14, 0);
6521         return;
6522     }
6523     if (!strncmp(message, "tellopponent ", 13)) {
6524       if (appData.icsActive) {
6525         if (loggedOn) {
6526           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6527           SendToICS(buf1);
6528         }
6529       } else {
6530         DisplayNote(message + 13);
6531       }
6532       return;
6533     }
6534     if (!strncmp(message, "tellothers ", 11)) {
6535       if (appData.icsActive) {
6536         if (loggedOn) {
6537           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6538           SendToICS(buf1);
6539         }
6540       }
6541       return;
6542     }
6543     if (!strncmp(message, "tellall ", 8)) {
6544       if (appData.icsActive) {
6545         if (loggedOn) {
6546           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6547           SendToICS(buf1);
6548         }
6549       } else {
6550         DisplayNote(message + 8);
6551       }
6552       return;
6553     }
6554     if (strncmp(message, "warning", 7) == 0) {
6555         /* Undocumented feature, use tellusererror in new code */
6556         DisplayError(message, 0);
6557         return;
6558     }
6559     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6560         strcpy(realname, cps->tidy);
6561         strcat(realname, " query");
6562         AskQuestion(realname, buf2, buf1, cps->pr);
6563         return;
6564     }
6565     /* Commands from the engine directly to ICS.  We don't allow these to be
6566      *  sent until we are logged on. Crafty kibitzes have been known to
6567      *  interfere with the login process.
6568      */
6569     if (loggedOn) {
6570         if (!strncmp(message, "tellics ", 8)) {
6571             SendToICS(message + 8);
6572             SendToICS("\n");
6573             return;
6574         }
6575         if (!strncmp(message, "tellicsnoalias ", 15)) {
6576             SendToICS(ics_prefix);
6577             SendToICS(message + 15);
6578             SendToICS("\n");
6579             return;
6580         }
6581         /* The following are for backward compatibility only */
6582         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6583             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6584             SendToICS(ics_prefix);
6585             SendToICS(message);
6586             SendToICS("\n");
6587             return;
6588         }
6589     }
6590     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6591         return;
6592     }
6593     /*
6594      * If the move is illegal, cancel it and redraw the board.
6595      * Also deal with other error cases.  Matching is rather loose
6596      * here to accommodate engines written before the spec.
6597      */
6598     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6599         strncmp(message, "Error", 5) == 0) {
6600         if (StrStr(message, "name") ||
6601             StrStr(message, "rating") || StrStr(message, "?") ||
6602             StrStr(message, "result") || StrStr(message, "board") ||
6603             StrStr(message, "bk") || StrStr(message, "computer") ||
6604             StrStr(message, "variant") || StrStr(message, "hint") ||
6605             StrStr(message, "random") || StrStr(message, "depth") ||
6606             StrStr(message, "accepted")) {
6607             return;
6608         }
6609         if (StrStr(message, "protover")) {
6610           /* Program is responding to input, so it's apparently done
6611              initializing, and this error message indicates it is
6612              protocol version 1.  So we don't need to wait any longer
6613              for it to initialize and send feature commands. */
6614           FeatureDone(cps, 1);
6615           cps->protocolVersion = 1;
6616           return;
6617         }
6618         cps->maybeThinking = FALSE;
6619
6620         if (StrStr(message, "draw")) {
6621             /* Program doesn't have "draw" command */
6622             cps->sendDrawOffers = 0;
6623             return;
6624         }
6625         if (cps->sendTime != 1 &&
6626             (StrStr(message, "time") || StrStr(message, "otim"))) {
6627           /* Program apparently doesn't have "time" or "otim" command */
6628           cps->sendTime = 0;
6629           return;
6630         }
6631         if (StrStr(message, "analyze")) {
6632             cps->analysisSupport = FALSE;
6633             cps->analyzing = FALSE;
6634             Reset(FALSE, TRUE);
6635             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6636             DisplayError(buf2, 0);
6637             return;
6638         }
6639         if (StrStr(message, "(no matching move)st")) {
6640           /* Special kludge for GNU Chess 4 only */
6641           cps->stKludge = TRUE;
6642           SendTimeControl(cps, movesPerSession, timeControl,
6643                           timeIncrement, appData.searchDepth,
6644                           searchTime);
6645           return;
6646         }
6647         if (StrStr(message, "(no matching move)sd")) {
6648           /* Special kludge for GNU Chess 4 only */
6649           cps->sdKludge = TRUE;
6650           SendTimeControl(cps, movesPerSession, timeControl,
6651                           timeIncrement, appData.searchDepth,
6652                           searchTime);
6653           return;
6654         }
6655         if (!StrStr(message, "llegal")) {
6656             return;
6657         }
6658         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6659             gameMode == IcsIdle) return;
6660         if (forwardMostMove <= backwardMostMove) return;
6661         if (pausing) PauseEvent();
6662       if(appData.forceIllegal) {
6663             // [HGM] illegal: machine refused move; force position after move into it
6664           SendToProgram("force\n", cps);
6665           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6666                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6667                 // when black is to move, while there might be nothing on a2 or black
6668                 // might already have the move. So send the board as if white has the move.
6669                 // But first we must change the stm of the engine, as it refused the last move
6670                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6671                 if(WhiteOnMove(forwardMostMove)) {
6672                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6673                     SendBoard(cps, forwardMostMove); // kludgeless board
6674                 } else {
6675                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6676                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6677                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6678                 }
6679           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6680             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6681                  gameMode == TwoMachinesPlay)
6682               SendToProgram("go\n", cps);
6683             return;
6684       } else
6685         if (gameMode == PlayFromGameFile) {
6686             /* Stop reading this game file */
6687             gameMode = EditGame;
6688             ModeHighlight();
6689         }
6690         currentMove = --forwardMostMove;
6691         DisplayMove(currentMove-1); /* before DisplayMoveError */
6692         SwitchClocks();
6693         DisplayBothClocks();
6694         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6695                 parseList[currentMove], cps->which);
6696         DisplayMoveError(buf1);
6697         DrawPosition(FALSE, boards[currentMove]);
6698
6699         /* [HGM] illegal-move claim should forfeit game when Xboard */
6700         /* only passes fully legal moves                            */
6701         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6702             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6703                                 "False illegal-move claim", GE_XBOARD );
6704         }
6705         return;
6706     }
6707     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6708         /* Program has a broken "time" command that
6709            outputs a string not ending in newline.
6710            Don't use it. */
6711         cps->sendTime = 0;
6712     }
6713
6714     /*
6715      * If chess program startup fails, exit with an error message.
6716      * Attempts to recover here are futile.
6717      */
6718     if ((StrStr(message, "unknown host") != NULL)
6719         || (StrStr(message, "No remote directory") != NULL)
6720         || (StrStr(message, "not found") != NULL)
6721         || (StrStr(message, "No such file") != NULL)
6722         || (StrStr(message, "can't alloc") != NULL)
6723         || (StrStr(message, "Permission denied") != NULL)) {
6724
6725         cps->maybeThinking = FALSE;
6726         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6727                 cps->which, cps->program, cps->host, message);
6728         RemoveInputSource(cps->isr);
6729         DisplayFatalError(buf1, 0, 1);
6730         return;
6731     }
6732
6733     /*
6734      * Look for hint output
6735      */
6736     if (sscanf(message, "Hint: %s", buf1) == 1) {
6737         if (cps == &first && hintRequested) {
6738             hintRequested = FALSE;
6739             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6740                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6741                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6742                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6743                                     fromY, fromX, toY, toX, promoChar, buf1);
6744                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6745                 DisplayInformation(buf2);
6746             } else {
6747                 /* Hint move could not be parsed!? */
6748               snprintf(buf2, sizeof(buf2),
6749                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6750                         buf1, cps->which);
6751                 DisplayError(buf2, 0);
6752             }
6753         } else {
6754             strcpy(lastHint, buf1);
6755         }
6756         return;
6757     }
6758
6759     /*
6760      * Ignore other messages if game is not in progress
6761      */
6762     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6763         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6764
6765     /*
6766      * look for win, lose, draw, or draw offer
6767      */
6768     if (strncmp(message, "1-0", 3) == 0) {
6769         char *p, *q, *r = "";
6770         p = strchr(message, '{');
6771         if (p) {
6772             q = strchr(p, '}');
6773             if (q) {
6774                 *q = NULLCHAR;
6775                 r = p + 1;
6776             }
6777         }
6778         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6779         return;
6780     } else if (strncmp(message, "0-1", 3) == 0) {
6781         char *p, *q, *r = "";
6782         p = strchr(message, '{');
6783         if (p) {
6784             q = strchr(p, '}');
6785             if (q) {
6786                 *q = NULLCHAR;
6787                 r = p + 1;
6788             }
6789         }
6790         /* Kludge for Arasan 4.1 bug */
6791         if (strcmp(r, "Black resigns") == 0) {
6792             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6793             return;
6794         }
6795         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6796         return;
6797     } else if (strncmp(message, "1/2", 3) == 0) {
6798         char *p, *q, *r = "";
6799         p = strchr(message, '{');
6800         if (p) {
6801             q = strchr(p, '}');
6802             if (q) {
6803                 *q = NULLCHAR;
6804                 r = p + 1;
6805             }
6806         }
6807
6808         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6809         return;
6810
6811     } else if (strncmp(message, "White resign", 12) == 0) {
6812         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6813         return;
6814     } else if (strncmp(message, "Black resign", 12) == 0) {
6815         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6816         return;
6817     } else if (strncmp(message, "White matches", 13) == 0 ||
6818                strncmp(message, "Black matches", 13) == 0   ) {
6819         /* [HGM] ignore GNUShogi noises */
6820         return;
6821     } else if (strncmp(message, "White", 5) == 0 &&
6822                message[5] != '(' &&
6823                StrStr(message, "Black") == NULL) {
6824         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6825         return;
6826     } else if (strncmp(message, "Black", 5) == 0 &&
6827                message[5] != '(') {
6828         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6829         return;
6830     } else if (strcmp(message, "resign") == 0 ||
6831                strcmp(message, "computer resigns") == 0) {
6832         switch (gameMode) {
6833           case MachinePlaysBlack:
6834           case IcsPlayingBlack:
6835             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6836             break;
6837           case MachinePlaysWhite:
6838           case IcsPlayingWhite:
6839             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6840             break;
6841           case TwoMachinesPlay:
6842             if (cps->twoMachinesColor[0] == 'w')
6843               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6844             else
6845               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6846             break;
6847           default:
6848             /* can't happen */
6849             break;
6850         }
6851         return;
6852     } else if (strncmp(message, "opponent mates", 14) == 0) {
6853         switch (gameMode) {
6854           case MachinePlaysBlack:
6855           case IcsPlayingBlack:
6856             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6857             break;
6858           case MachinePlaysWhite:
6859           case IcsPlayingWhite:
6860             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6861             break;
6862           case TwoMachinesPlay:
6863             if (cps->twoMachinesColor[0] == 'w')
6864               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6865             else
6866               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6867             break;
6868           default:
6869             /* can't happen */
6870             break;
6871         }
6872         return;
6873     } else if (strncmp(message, "computer mates", 14) == 0) {
6874         switch (gameMode) {
6875           case MachinePlaysBlack:
6876           case IcsPlayingBlack:
6877             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6878             break;
6879           case MachinePlaysWhite:
6880           case IcsPlayingWhite:
6881             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6882             break;
6883           case TwoMachinesPlay:
6884             if (cps->twoMachinesColor[0] == 'w')
6885               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6886             else
6887               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6888             break;
6889           default:
6890             /* can't happen */
6891             break;
6892         }
6893         return;
6894     } else if (strncmp(message, "checkmate", 9) == 0) {
6895         if (WhiteOnMove(forwardMostMove)) {
6896             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6897         } else {
6898             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6899         }
6900         return;
6901     } else if (strstr(message, "Draw") != NULL ||
6902                strstr(message, "game is a draw") != NULL) {
6903         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6904         return;
6905     } else if (strstr(message, "offer") != NULL &&
6906                strstr(message, "draw") != NULL) {
6907 #if ZIPPY
6908         if (appData.zippyPlay && first.initDone) {
6909             /* Relay offer to ICS */
6910             SendToICS(ics_prefix);
6911             SendToICS("draw\n");
6912         }
6913 #endif
6914         cps->offeredDraw = 2; /* valid until this engine moves twice */
6915         if (gameMode == TwoMachinesPlay) {
6916             if (cps->other->offeredDraw) {
6917                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6918             /* [HGM] in two-machine mode we delay relaying draw offer      */
6919             /* until after we also have move, to see if it is really claim */
6920             }
6921         } else if (gameMode == MachinePlaysWhite ||
6922                    gameMode == MachinePlaysBlack) {
6923           if (userOfferedDraw) {
6924             DisplayInformation(_("Machine accepts your draw offer"));
6925             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6926           } else {
6927             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6928           }
6929         }
6930     }
6931
6932
6933     /*
6934      * Look for thinking output
6935      */
6936     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6937           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6938                                 ) {
6939         int plylev, mvleft, mvtot, curscore, time;
6940         char mvname[MOVE_LEN];
6941         u64 nodes; // [DM]
6942         char plyext;
6943         int ignore = FALSE;
6944         int prefixHint = FALSE;
6945         mvname[0] = NULLCHAR;
6946
6947         switch (gameMode) {
6948           case MachinePlaysBlack:
6949           case IcsPlayingBlack:
6950             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6951             break;
6952           case MachinePlaysWhite:
6953           case IcsPlayingWhite:
6954             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6955             break;
6956           case AnalyzeMode:
6957           case AnalyzeFile:
6958             break;
6959           case IcsObserving: /* [DM] icsEngineAnalyze */
6960             if (!appData.icsEngineAnalyze) ignore = TRUE;
6961             break;
6962           case TwoMachinesPlay:
6963             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6964                 ignore = TRUE;
6965             }
6966             break;
6967           default:
6968             ignore = TRUE;
6969             break;
6970         }
6971
6972         if (!ignore) {
6973             buf1[0] = NULLCHAR;
6974             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6975                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6976
6977                 if (plyext != ' ' && plyext != '\t') {
6978                     time *= 100;
6979                 }
6980
6981                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6982                 if( cps->scoreIsAbsolute &&
6983                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6984                 {
6985                     curscore = -curscore;
6986                 }
6987
6988
6989                 programStats.depth = plylev;
6990                 programStats.nodes = nodes;
6991                 programStats.time = time;
6992                 programStats.score = curscore;
6993                 programStats.got_only_move = 0;
6994
6995                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6996                         int ticklen;
6997
6998                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6999                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7000                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7001                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7002                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7003                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7004                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7005                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7006                 }
7007
7008                 /* Buffer overflow protection */
7009                 if (buf1[0] != NULLCHAR) {
7010                     if (strlen(buf1) >= sizeof(programStats.movelist)
7011                         && appData.debugMode) {
7012                         fprintf(debugFP,
7013                                 "PV is too long; using the first %u bytes.\n",
7014                                 (unsigned) sizeof(programStats.movelist) - 1);
7015                     }
7016
7017                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7018                 } else {
7019                     sprintf(programStats.movelist, " no PV\n");
7020                 }
7021
7022                 if (programStats.seen_stat) {
7023                     programStats.ok_to_send = 1;
7024                 }
7025
7026                 if (strchr(programStats.movelist, '(') != NULL) {
7027                     programStats.line_is_book = 1;
7028                     programStats.nr_moves = 0;
7029                     programStats.moves_left = 0;
7030                 } else {
7031                     programStats.line_is_book = 0;
7032                 }
7033
7034                 SendProgramStatsToFrontend( cps, &programStats );
7035
7036                 /*
7037                     [AS] Protect the thinkOutput buffer from overflow... this
7038                     is only useful if buf1 hasn't overflowed first!
7039                 */
7040                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7041                         plylev,
7042                         (gameMode == TwoMachinesPlay ?
7043                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7044                         ((double) curscore) / 100.0,
7045                         prefixHint ? lastHint : "",
7046                         prefixHint ? " " : "" );
7047
7048                 if( buf1[0] != NULLCHAR ) {
7049                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7050
7051                     if( strlen(buf1) > max_len ) {
7052                         if( appData.debugMode) {
7053                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7054                         }
7055                         buf1[max_len+1] = '\0';
7056                     }
7057
7058                     strcat( thinkOutput, buf1 );
7059                 }
7060
7061                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7062                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7063                     DisplayMove(currentMove - 1);
7064                 }
7065                 return;
7066
7067             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7068                 /* crafty (9.25+) says "(only move) <move>"
7069                  * if there is only 1 legal move
7070                  */
7071                 sscanf(p, "(only move) %s", buf1);
7072                 sprintf(thinkOutput, "%s (only move)", buf1);
7073                 sprintf(programStats.movelist, "%s (only move)", buf1);
7074                 programStats.depth = 1;
7075                 programStats.nr_moves = 1;
7076                 programStats.moves_left = 1;
7077                 programStats.nodes = 1;
7078                 programStats.time = 1;
7079                 programStats.got_only_move = 1;
7080
7081                 /* Not really, but we also use this member to
7082                    mean "line isn't going to change" (Crafty
7083                    isn't searching, so stats won't change) */
7084                 programStats.line_is_book = 1;
7085
7086                 SendProgramStatsToFrontend( cps, &programStats );
7087
7088                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7089                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7090                     DisplayMove(currentMove - 1);
7091                 }
7092                 return;
7093             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7094                               &time, &nodes, &plylev, &mvleft,
7095                               &mvtot, mvname) >= 5) {
7096                 /* The stat01: line is from Crafty (9.29+) in response
7097                    to the "." command */
7098                 programStats.seen_stat = 1;
7099                 cps->maybeThinking = TRUE;
7100
7101                 if (programStats.got_only_move || !appData.periodicUpdates)
7102                   return;
7103
7104                 programStats.depth = plylev;
7105                 programStats.time = time;
7106                 programStats.nodes = nodes;
7107                 programStats.moves_left = mvleft;
7108                 programStats.nr_moves = mvtot;
7109                 strcpy(programStats.move_name, mvname);
7110                 programStats.ok_to_send = 1;
7111                 programStats.movelist[0] = '\0';
7112
7113                 SendProgramStatsToFrontend( cps, &programStats );
7114
7115                 return;
7116
7117             } else if (strncmp(message,"++",2) == 0) {
7118                 /* Crafty 9.29+ outputs this */
7119                 programStats.got_fail = 2;
7120                 return;
7121
7122             } else if (strncmp(message,"--",2) == 0) {
7123                 /* Crafty 9.29+ outputs this */
7124                 programStats.got_fail = 1;
7125                 return;
7126
7127             } else if (thinkOutput[0] != NULLCHAR &&
7128                        strncmp(message, "    ", 4) == 0) {
7129                 unsigned message_len;
7130
7131                 p = message;
7132                 while (*p && *p == ' ') p++;
7133
7134                 message_len = strlen( p );
7135
7136                 /* [AS] Avoid buffer overflow */
7137                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7138                     strcat(thinkOutput, " ");
7139                     strcat(thinkOutput, p);
7140                 }
7141
7142                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7143                     strcat(programStats.movelist, " ");
7144                     strcat(programStats.movelist, p);
7145                 }
7146
7147                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7148                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7149                     DisplayMove(currentMove - 1);
7150                 }
7151                 return;
7152             }
7153         }
7154         else {
7155             buf1[0] = NULLCHAR;
7156
7157             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7158                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7159             {
7160                 ChessProgramStats cpstats;
7161
7162                 if (plyext != ' ' && plyext != '\t') {
7163                     time *= 100;
7164                 }
7165
7166                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7167                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7168                     curscore = -curscore;
7169                 }
7170
7171                 cpstats.depth = plylev;
7172                 cpstats.nodes = nodes;
7173                 cpstats.time = time;
7174                 cpstats.score = curscore;
7175                 cpstats.got_only_move = 0;
7176                 cpstats.movelist[0] = '\0';
7177
7178                 if (buf1[0] != NULLCHAR) {
7179                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7180                 }
7181
7182                 cpstats.ok_to_send = 0;
7183                 cpstats.line_is_book = 0;
7184                 cpstats.nr_moves = 0;
7185                 cpstats.moves_left = 0;
7186
7187                 SendProgramStatsToFrontend( cps, &cpstats );
7188             }
7189         }
7190     }
7191 }
7192
7193
7194 /* Parse a game score from the character string "game", and
7195    record it as the history of the current game.  The game
7196    score is NOT assumed to start from the standard position.
7197    The display is not updated in any way.
7198    */
7199 void
7200 ParseGameHistory(game)
7201      char *game;
7202 {
7203     ChessMove moveType;
7204     int fromX, fromY, toX, toY, boardIndex;
7205     char promoChar;
7206     char *p, *q;
7207     char buf[MSG_SIZ];
7208
7209     if (appData.debugMode)
7210       fprintf(debugFP, "Parsing game history: %s\n", game);
7211
7212     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7213     gameInfo.site = StrSave(appData.icsHost);
7214     gameInfo.date = PGNDate();
7215     gameInfo.round = StrSave("-");
7216
7217     /* Parse out names of players */
7218     while (*game == ' ') game++;
7219     p = buf;
7220     while (*game != ' ') *p++ = *game++;
7221     *p = NULLCHAR;
7222     gameInfo.white = StrSave(buf);
7223     while (*game == ' ') game++;
7224     p = buf;
7225     while (*game != ' ' && *game != '\n') *p++ = *game++;
7226     *p = NULLCHAR;
7227     gameInfo.black = StrSave(buf);
7228
7229     /* Parse moves */
7230     boardIndex = blackPlaysFirst ? 1 : 0;
7231     yynewstr(game);
7232     for (;;) {
7233         yyboardindex = boardIndex;
7234         moveType = (ChessMove) yylex();
7235         switch (moveType) {
7236           case IllegalMove:             /* maybe suicide chess, etc. */
7237   if (appData.debugMode) {
7238     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7239     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7240     setbuf(debugFP, NULL);
7241   }
7242           case WhitePromotionChancellor:
7243           case BlackPromotionChancellor:
7244           case WhitePromotionArchbishop:
7245           case BlackPromotionArchbishop:
7246           case WhitePromotionQueen:
7247           case BlackPromotionQueen:
7248           case WhitePromotionRook:
7249           case BlackPromotionRook:
7250           case WhitePromotionBishop:
7251           case BlackPromotionBishop:
7252           case WhitePromotionKnight:
7253           case BlackPromotionKnight:
7254           case WhitePromotionKing:
7255           case BlackPromotionKing:
7256           case NormalMove:
7257           case WhiteCapturesEnPassant:
7258           case BlackCapturesEnPassant:
7259           case WhiteKingSideCastle:
7260           case WhiteQueenSideCastle:
7261           case BlackKingSideCastle:
7262           case BlackQueenSideCastle:
7263           case WhiteKingSideCastleWild:
7264           case WhiteQueenSideCastleWild:
7265           case BlackKingSideCastleWild:
7266           case BlackQueenSideCastleWild:
7267           /* PUSH Fabien */
7268           case WhiteHSideCastleFR:
7269           case WhiteASideCastleFR:
7270           case BlackHSideCastleFR:
7271           case BlackASideCastleFR:
7272           /* POP Fabien */
7273             fromX = currentMoveString[0] - AAA;
7274             fromY = currentMoveString[1] - ONE;
7275             toX = currentMoveString[2] - AAA;
7276             toY = currentMoveString[3] - ONE;
7277             promoChar = currentMoveString[4];
7278             break;
7279           case WhiteDrop:
7280           case BlackDrop:
7281             fromX = moveType == WhiteDrop ?
7282               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7283             (int) CharToPiece(ToLower(currentMoveString[0]));
7284             fromY = DROP_RANK;
7285             toX = currentMoveString[2] - AAA;
7286             toY = currentMoveString[3] - ONE;
7287             promoChar = NULLCHAR;
7288             break;
7289           case AmbiguousMove:
7290             /* bug? */
7291             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7292   if (appData.debugMode) {
7293     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7294     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7295     setbuf(debugFP, NULL);
7296   }
7297             DisplayError(buf, 0);
7298             return;
7299           case ImpossibleMove:
7300             /* bug? */
7301             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7302   if (appData.debugMode) {
7303     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7304     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7305     setbuf(debugFP, NULL);
7306   }
7307             DisplayError(buf, 0);
7308             return;
7309           case (ChessMove) 0:   /* end of file */
7310             if (boardIndex < backwardMostMove) {
7311                 /* Oops, gap.  How did that happen? */
7312                 DisplayError(_("Gap in move list"), 0);
7313                 return;
7314             }
7315             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7316             if (boardIndex > forwardMostMove) {
7317                 forwardMostMove = boardIndex;
7318             }
7319             return;
7320           case ElapsedTime:
7321             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7322                 strcat(parseList[boardIndex-1], " ");
7323                 strcat(parseList[boardIndex-1], yy_text);
7324             }
7325             continue;
7326           case Comment:
7327           case PGNTag:
7328           case NAG:
7329           default:
7330             /* ignore */
7331             continue;
7332           case WhiteWins:
7333           case BlackWins:
7334           case GameIsDrawn:
7335           case GameUnfinished:
7336             if (gameMode == IcsExamining) {
7337                 if (boardIndex < backwardMostMove) {
7338                     /* Oops, gap.  How did that happen? */
7339                     return;
7340                 }
7341                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7342                 return;
7343             }
7344             gameInfo.result = moveType;
7345             p = strchr(yy_text, '{');
7346             if (p == NULL) p = strchr(yy_text, '(');
7347             if (p == NULL) {
7348                 p = yy_text;
7349                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7350             } else {
7351                 q = strchr(p, *p == '{' ? '}' : ')');
7352                 if (q != NULL) *q = NULLCHAR;
7353                 p++;
7354             }
7355             gameInfo.resultDetails = StrSave(p);
7356             continue;
7357         }
7358         if (boardIndex >= forwardMostMove &&
7359             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7360             backwardMostMove = blackPlaysFirst ? 1 : 0;
7361             return;
7362         }
7363         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7364                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7365                                  parseList[boardIndex]);
7366         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7367         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7368         /* currentMoveString is set as a side-effect of yylex */
7369         strcpy(moveList[boardIndex], currentMoveString);
7370         strcat(moveList[boardIndex], "\n");
7371         boardIndex++;
7372         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7373                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7374         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7375                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7376           case MT_NONE:
7377           case MT_STALEMATE:
7378           default:
7379             break;
7380           case MT_CHECK:
7381             if(gameInfo.variant != VariantShogi)
7382                 strcat(parseList[boardIndex - 1], "+");
7383             break;
7384           case MT_CHECKMATE:
7385           case MT_STAINMATE:
7386             strcat(parseList[boardIndex - 1], "#");
7387             break;
7388         }
7389     }
7390 }
7391
7392
7393 /* Apply a move to the given board  */
7394 void
7395 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7396      int fromX, fromY, toX, toY;
7397      int promoChar;
7398      Board board;
7399      char *castling;
7400      char *ep;
7401 {
7402   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7403
7404     /* [HGM] compute & store e.p. status and castling rights for new position */
7405     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7406     { int i;
7407
7408       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7409       oldEP = *ep;
7410       *ep = EP_NONE;
7411
7412       if( board[toY][toX] != EmptySquare )
7413            *ep = EP_CAPTURE;
7414
7415       if( board[fromY][fromX] == WhitePawn ) {
7416            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7417                *ep = EP_PAWN_MOVE;
7418            if( toY-fromY==2) {
7419                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7420                         gameInfo.variant != VariantBerolina || toX < fromX)
7421                       *ep = toX | berolina;
7422                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7423                         gameInfo.variant != VariantBerolina || toX > fromX)
7424                       *ep = toX;
7425            }
7426       } else
7427       if( board[fromY][fromX] == BlackPawn ) {
7428            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7429                *ep = EP_PAWN_MOVE;
7430            if( toY-fromY== -2) {
7431                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7432                         gameInfo.variant != VariantBerolina || toX < fromX)
7433                       *ep = toX | berolina;
7434                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7435                         gameInfo.variant != VariantBerolina || toX > fromX)
7436                       *ep = toX;
7437            }
7438        }
7439
7440        for(i=0; i<nrCastlingRights; i++) {
7441            if(castling[i] == fromX && castlingRank[i] == fromY ||
7442               castling[i] == toX   && castlingRank[i] == toY
7443              ) castling[i] = -1; // revoke for moved or captured piece
7444        }
7445
7446     }
7447
7448   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7449   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7450        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7451
7452   if (fromX == toX && fromY == toY) return;
7453
7454   if (fromY == DROP_RANK) {
7455         /* must be first */
7456         piece = board[toY][toX] = (ChessSquare) fromX;
7457   } else {
7458      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7459      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7460      if(gameInfo.variant == VariantKnightmate)
7461          king += (int) WhiteUnicorn - (int) WhiteKing;
7462
7463     /* Code added by Tord: */
7464     /* FRC castling assumed when king captures friendly rook. */
7465     if (board[fromY][fromX] == WhiteKing &&
7466              board[toY][toX] == WhiteRook) {
7467       board[fromY][fromX] = EmptySquare;
7468       board[toY][toX] = EmptySquare;
7469       if(toX > fromX) {
7470         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7471       } else {
7472         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7473       }
7474     } else if (board[fromY][fromX] == BlackKing &&
7475                board[toY][toX] == BlackRook) {
7476       board[fromY][fromX] = EmptySquare;
7477       board[toY][toX] = EmptySquare;
7478       if(toX > fromX) {
7479         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7480       } else {
7481         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7482       }
7483     /* End of code added by Tord */
7484
7485     } else if (board[fromY][fromX] == king
7486         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7487         && toY == fromY && toX > fromX+1) {
7488         board[fromY][fromX] = EmptySquare;
7489         board[toY][toX] = king;
7490         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7491         board[fromY][BOARD_RGHT-1] = EmptySquare;
7492     } else if (board[fromY][fromX] == king
7493         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7494                && toY == fromY && toX < fromX-1) {
7495         board[fromY][fromX] = EmptySquare;
7496         board[toY][toX] = king;
7497         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7498         board[fromY][BOARD_LEFT] = EmptySquare;
7499     } else if (board[fromY][fromX] == WhitePawn
7500                && toY == BOARD_HEIGHT-1
7501                && gameInfo.variant != VariantXiangqi
7502                ) {
7503         /* white pawn promotion */
7504         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7505         if (board[toY][toX] == EmptySquare) {
7506             board[toY][toX] = WhiteQueen;
7507         }
7508         if(gameInfo.variant==VariantBughouse ||
7509            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7510             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7511         board[fromY][fromX] = EmptySquare;
7512     } else if ((fromY == BOARD_HEIGHT-4)
7513                && (toX != fromX)
7514                && gameInfo.variant != VariantXiangqi
7515                && gameInfo.variant != VariantBerolina
7516                && (board[fromY][fromX] == WhitePawn)
7517                && (board[toY][toX] == EmptySquare)) {
7518         board[fromY][fromX] = EmptySquare;
7519         board[toY][toX] = WhitePawn;
7520         captured = board[toY - 1][toX];
7521         board[toY - 1][toX] = EmptySquare;
7522     } else if ((fromY == BOARD_HEIGHT-4)
7523                && (toX == fromX)
7524                && gameInfo.variant == VariantBerolina
7525                && (board[fromY][fromX] == WhitePawn)
7526                && (board[toY][toX] == EmptySquare)) {
7527         board[fromY][fromX] = EmptySquare;
7528         board[toY][toX] = WhitePawn;
7529         if(oldEP & EP_BEROLIN_A) {
7530                 captured = board[fromY][fromX-1];
7531                 board[fromY][fromX-1] = EmptySquare;
7532         }else{  captured = board[fromY][fromX+1];
7533                 board[fromY][fromX+1] = EmptySquare;
7534         }
7535     } else if (board[fromY][fromX] == king
7536         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7537                && toY == fromY && toX > fromX+1) {
7538         board[fromY][fromX] = EmptySquare;
7539         board[toY][toX] = king;
7540         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7541         board[fromY][BOARD_RGHT-1] = EmptySquare;
7542     } else if (board[fromY][fromX] == king
7543         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7544                && toY == fromY && toX < fromX-1) {
7545         board[fromY][fromX] = EmptySquare;
7546         board[toY][toX] = king;
7547         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7548         board[fromY][BOARD_LEFT] = EmptySquare;
7549     } else if (fromY == 7 && fromX == 3
7550                && board[fromY][fromX] == BlackKing
7551                && toY == 7 && toX == 5) {
7552         board[fromY][fromX] = EmptySquare;
7553         board[toY][toX] = BlackKing;
7554         board[fromY][7] = EmptySquare;
7555         board[toY][4] = BlackRook;
7556     } else if (fromY == 7 && fromX == 3
7557                && board[fromY][fromX] == BlackKing
7558                && toY == 7 && toX == 1) {
7559         board[fromY][fromX] = EmptySquare;
7560         board[toY][toX] = BlackKing;
7561         board[fromY][0] = EmptySquare;
7562         board[toY][2] = BlackRook;
7563     } else if (board[fromY][fromX] == BlackPawn
7564                && toY == 0
7565                && gameInfo.variant != VariantXiangqi
7566                ) {
7567         /* black pawn promotion */
7568         board[0][toX] = CharToPiece(ToLower(promoChar));
7569         if (board[0][toX] == EmptySquare) {
7570             board[0][toX] = BlackQueen;
7571         }
7572         if(gameInfo.variant==VariantBughouse ||
7573            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7574             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7575         board[fromY][fromX] = EmptySquare;
7576     } else if ((fromY == 3)
7577                && (toX != fromX)
7578                && gameInfo.variant != VariantXiangqi
7579                && gameInfo.variant != VariantBerolina
7580                && (board[fromY][fromX] == BlackPawn)
7581                && (board[toY][toX] == EmptySquare)) {
7582         board[fromY][fromX] = EmptySquare;
7583         board[toY][toX] = BlackPawn;
7584         captured = board[toY + 1][toX];
7585         board[toY + 1][toX] = EmptySquare;
7586     } else if ((fromY == 3)
7587                && (toX == fromX)
7588                && gameInfo.variant == VariantBerolina
7589                && (board[fromY][fromX] == BlackPawn)
7590                && (board[toY][toX] == EmptySquare)) {
7591         board[fromY][fromX] = EmptySquare;
7592         board[toY][toX] = BlackPawn;
7593         if(oldEP & EP_BEROLIN_A) {
7594                 captured = board[fromY][fromX-1];
7595                 board[fromY][fromX-1] = EmptySquare;
7596         }else{  captured = board[fromY][fromX+1];
7597                 board[fromY][fromX+1] = EmptySquare;
7598         }
7599     } else {
7600         board[toY][toX] = board[fromY][fromX];
7601         board[fromY][fromX] = EmptySquare;
7602     }
7603
7604     /* [HGM] now we promote for Shogi, if needed */
7605     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7606         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7607   }
7608
7609     if (gameInfo.holdingsWidth != 0) {
7610
7611       /* !!A lot more code needs to be written to support holdings  */
7612       /* [HGM] OK, so I have written it. Holdings are stored in the */
7613       /* penultimate board files, so they are automaticlly stored   */
7614       /* in the game history.                                       */
7615       if (fromY == DROP_RANK) {
7616         /* Delete from holdings, by decreasing count */
7617         /* and erasing image if necessary            */
7618         p = (int) fromX;
7619         if(p < (int) BlackPawn) { /* white drop */
7620              p -= (int)WhitePawn;
7621                  p = PieceToNumber((ChessSquare)p);
7622              if(p >= gameInfo.holdingsSize) p = 0;
7623              if(--board[p][BOARD_WIDTH-2] <= 0)
7624                   board[p][BOARD_WIDTH-1] = EmptySquare;
7625              if((int)board[p][BOARD_WIDTH-2] < 0)
7626                         board[p][BOARD_WIDTH-2] = 0;
7627         } else {                  /* black drop */
7628              p -= (int)BlackPawn;
7629                  p = PieceToNumber((ChessSquare)p);
7630              if(p >= gameInfo.holdingsSize) p = 0;
7631              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7632                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7633              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7634                         board[BOARD_HEIGHT-1-p][1] = 0;
7635         }
7636       }
7637       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7638           && gameInfo.variant != VariantBughouse        ) {
7639         /* [HGM] holdings: Add to holdings, if holdings exist */
7640         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7641                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7642                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7643         }
7644         p = (int) captured;
7645         if (p >= (int) BlackPawn) {
7646           p -= (int)BlackPawn;
7647           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7648                   /* in Shogi restore piece to its original  first */
7649                   captured = (ChessSquare) (DEMOTED captured);
7650                   p = DEMOTED p;
7651           }
7652           p = PieceToNumber((ChessSquare)p);
7653           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7654           board[p][BOARD_WIDTH-2]++;
7655           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7656         } else {
7657           p -= (int)WhitePawn;
7658           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7659                   captured = (ChessSquare) (DEMOTED captured);
7660                   p = DEMOTED p;
7661           }
7662           p = PieceToNumber((ChessSquare)p);
7663           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7664           board[BOARD_HEIGHT-1-p][1]++;
7665           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7666         }
7667       }
7668     } else if (gameInfo.variant == VariantAtomic) {
7669       if (captured != EmptySquare) {
7670         int y, x;
7671         for (y = toY-1; y <= toY+1; y++) {
7672           for (x = toX-1; x <= toX+1; x++) {
7673             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7674                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7675               board[y][x] = EmptySquare;
7676             }
7677           }
7678         }
7679         board[toY][toX] = EmptySquare;
7680       }
7681     }
7682     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7683         /* [HGM] Shogi promotions */
7684         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7685     }
7686
7687     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7688                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7689         // [HGM] superchess: take promotion piece out of holdings
7690         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7691         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7692             if(!--board[k][BOARD_WIDTH-2])
7693                 board[k][BOARD_WIDTH-1] = EmptySquare;
7694         } else {
7695             if(!--board[BOARD_HEIGHT-1-k][1])
7696                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7697         }
7698     }
7699
7700 }
7701
7702 /* Updates forwardMostMove */
7703 void
7704 MakeMove(fromX, fromY, toX, toY, promoChar)
7705      int fromX, fromY, toX, toY;
7706      int promoChar;
7707 {
7708 //    forwardMostMove++; // [HGM] bare: moved downstream
7709
7710     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7711         int timeLeft; static int lastLoadFlag=0; int king, piece;
7712         piece = boards[forwardMostMove][fromY][fromX];
7713         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7714         if(gameInfo.variant == VariantKnightmate)
7715             king += (int) WhiteUnicorn - (int) WhiteKing;
7716         if(forwardMostMove == 0) {
7717             if(blackPlaysFirst)
7718                 fprintf(serverMoves, "%s;", second.tidy);
7719             fprintf(serverMoves, "%s;", first.tidy);
7720             if(!blackPlaysFirst)
7721                 fprintf(serverMoves, "%s;", second.tidy);
7722         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7723         lastLoadFlag = loadFlag;
7724         // print base move
7725         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7726         // print castling suffix
7727         if( toY == fromY && piece == king ) {
7728             if(toX-fromX > 1)
7729                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7730             if(fromX-toX >1)
7731                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7732         }
7733         // e.p. suffix
7734         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7735              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7736              boards[forwardMostMove][toY][toX] == EmptySquare
7737              && fromX != toX )
7738                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7739         // promotion suffix
7740         if(promoChar != NULLCHAR)
7741                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7742         if(!loadFlag) {
7743             fprintf(serverMoves, "/%d/%d",
7744                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7745             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7746             else                      timeLeft = blackTimeRemaining/1000;
7747             fprintf(serverMoves, "/%d", timeLeft);
7748         }
7749         fflush(serverMoves);
7750     }
7751
7752     if (forwardMostMove+1 >= MAX_MOVES) {
7753       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7754                         0, 1);
7755       return;
7756     }
7757     if (commentList[forwardMostMove+1] != NULL) {
7758         free(commentList[forwardMostMove+1]);
7759         commentList[forwardMostMove+1] = NULL;
7760     }
7761     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7762     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7763     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7764                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7765     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7766     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7767     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7768     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7769     gameInfo.result = GameUnfinished;
7770     if (gameInfo.resultDetails != NULL) {
7771         free(gameInfo.resultDetails);
7772         gameInfo.resultDetails = NULL;
7773     }
7774     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7775                               moveList[forwardMostMove - 1]);
7776     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7777                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7778                              fromY, fromX, toY, toX, promoChar,
7779                              parseList[forwardMostMove - 1]);
7780     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7781                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7782                             castlingRights[forwardMostMove]) ) {
7783       case MT_NONE:
7784       case MT_STALEMATE:
7785       default:
7786         break;
7787       case MT_CHECK:
7788         if(gameInfo.variant != VariantShogi)
7789             strcat(parseList[forwardMostMove - 1], "+");
7790         break;
7791       case MT_CHECKMATE:
7792       case MT_STAINMATE:
7793         strcat(parseList[forwardMostMove - 1], "#");
7794         break;
7795     }
7796     if (appData.debugMode) {
7797         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7798     }
7799
7800 }
7801
7802 /* Updates currentMove if not pausing */
7803 void
7804 ShowMove(fromX, fromY, toX, toY)
7805 {
7806     int instant = (gameMode == PlayFromGameFile) ?
7807         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7808
7809     if(appData.noGUI) return;
7810
7811     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7812       {
7813         if (!instant)
7814           {
7815             if (forwardMostMove == currentMove + 1)
7816               {
7817 //TODO
7818 //              AnimateMove(boards[forwardMostMove - 1],
7819 //                          fromX, fromY, toX, toY);
7820               }
7821             if (appData.highlightLastMove)
7822               {
7823                 SetHighlights(fromX, fromY, toX, toY);
7824               }
7825           }
7826         currentMove = forwardMostMove;
7827     }
7828
7829     if (instant) return;
7830
7831     DisplayMove(currentMove - 1);
7832     DrawPosition(FALSE, boards[currentMove]);
7833     DisplayBothClocks();
7834     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7835
7836     return;
7837 }
7838
7839 void SendEgtPath(ChessProgramState *cps)
7840 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7841         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7842
7843         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7844
7845         while(*p) {
7846             char c, *q = name+1, *r, *s;
7847
7848             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7849             while(*p && *p != ',') *q++ = *p++;
7850             *q++ = ':'; *q = 0;
7851             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7852                 strcmp(name, ",nalimov:") == 0 ) {
7853                 // take nalimov path from the menu-changeable option first, if it is defined
7854                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7855                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7856             } else
7857             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7858                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7859                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7860                 s = r = StrStr(s, ":") + 1; // beginning of path info
7861                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7862                 c = *r; *r = 0;             // temporarily null-terminate path info
7863                     *--q = 0;               // strip of trailig ':' from name
7864                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7865                 *r = c;
7866                 SendToProgram(buf,cps);     // send egtbpath command for this format
7867             }
7868             if(*p == ',') p++; // read away comma to position for next format name
7869         }
7870 }
7871
7872 void
7873 InitChessProgram(cps, setup)
7874      ChessProgramState *cps;
7875      int setup; /* [HGM] needed to setup FRC opening position */
7876 {
7877     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7878     if (appData.noChessProgram) return;
7879     hintRequested = FALSE;
7880     bookRequested = FALSE;
7881
7882     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7883     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7884     if(cps->memSize) { /* [HGM] memory */
7885         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7886         SendToProgram(buf, cps);
7887     }
7888     SendEgtPath(cps); /* [HGM] EGT */
7889     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7890         sprintf(buf, "cores %d\n", appData.smpCores);
7891         SendToProgram(buf, cps);
7892     }
7893
7894     SendToProgram(cps->initString, cps);
7895     if (gameInfo.variant != VariantNormal &&
7896         gameInfo.variant != VariantLoadable
7897         /* [HGM] also send variant if board size non-standard */
7898         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7899                                             ) {
7900       char *v = VariantName(gameInfo.variant);
7901       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7902         /* [HGM] in protocol 1 we have to assume all variants valid */
7903         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7904         DisplayFatalError(buf, 0, 1);
7905         return;
7906       }
7907
7908       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7909       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7910       if( gameInfo.variant == VariantXiangqi )
7911            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7912       if( gameInfo.variant == VariantShogi )
7913            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7914       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7915            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7916       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7917                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7918            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7919       if( gameInfo.variant == VariantCourier )
7920            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7921       if( gameInfo.variant == VariantSuper )
7922            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7923       if( gameInfo.variant == VariantGreat )
7924            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7925
7926       if(overruled) {
7927            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7928                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7929            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7930            if(StrStr(cps->variants, b) == NULL) {
7931                // specific sized variant not known, check if general sizing allowed
7932                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7933                    if(StrStr(cps->variants, "boardsize") == NULL) {
7934                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7935                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7936                        DisplayFatalError(buf, 0, 1);
7937                        return;
7938                    }
7939                    /* [HGM] here we really should compare with the maximum supported board size */
7940                }
7941            }
7942       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7943       sprintf(buf, "variant %s\n", b);
7944       SendToProgram(buf, cps);
7945     }
7946     currentlyInitializedVariant = gameInfo.variant;
7947
7948     /* [HGM] send opening position in FRC to first engine */
7949     if(setup) {
7950           SendToProgram("force\n", cps);
7951           SendBoard(cps, 0);
7952           /* engine is now in force mode! Set flag to wake it up after first move. */
7953           setboardSpoiledMachineBlack = 1;
7954     }
7955
7956     if (cps->sendICS) {
7957       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7958       SendToProgram(buf, cps);
7959     }
7960     cps->maybeThinking = FALSE;
7961     cps->offeredDraw = 0;
7962     if (!appData.icsActive) {
7963         SendTimeControl(cps, movesPerSession, timeControl,
7964                         timeIncrement, appData.searchDepth,
7965                         searchTime);
7966     }
7967     if (appData.showThinking
7968         // [HGM] thinking: four options require thinking output to be sent
7969         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7970                                 ) {
7971         SendToProgram("post\n", cps);
7972     }
7973     SendToProgram("hard\n", cps);
7974     if (!appData.ponderNextMove) {
7975         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7976            it without being sure what state we are in first.  "hard"
7977            is not a toggle, so that one is OK.
7978          */
7979         SendToProgram("easy\n", cps);
7980     }
7981     if (cps->usePing) {
7982       sprintf(buf, "ping %d\n", ++cps->lastPing);
7983       SendToProgram(buf, cps);
7984     }
7985     cps->initDone = TRUE;
7986 }
7987
7988
7989 void
7990 StartChessProgram(cps)
7991      ChessProgramState *cps;
7992 {
7993     char buf[MSG_SIZ];
7994     int err;
7995
7996     if (appData.noChessProgram) return;
7997     cps->initDone = FALSE;
7998
7999     if (strcmp(cps->host, "localhost") == 0) {
8000         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8001     } else if (*appData.remoteShell == NULLCHAR) {
8002         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8003     } else {
8004         if (*appData.remoteUser == NULLCHAR) {
8005           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8006                     cps->program);
8007         } else {
8008           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8009                     cps->host, appData.remoteUser, cps->program);
8010         }
8011         err = StartChildProcess(buf, "", &cps->pr);
8012     }
8013
8014     if (err != 0) {
8015         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8016         DisplayFatalError(buf, err, 1);
8017         cps->pr = NoProc;
8018         cps->isr = NULL;
8019         return;
8020     }
8021
8022     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8023     if (cps->protocolVersion > 1) {
8024       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8025       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8026       cps->comboCnt = 0;  //                and values of combo boxes
8027       SendToProgram(buf, cps);
8028     } else {
8029       SendToProgram("xboard\n", cps);
8030     }
8031 }
8032
8033
8034 void
8035 TwoMachinesEventIfReady P((void))
8036 {
8037   if (first.lastPing != first.lastPong) {
8038     DisplayMessage("", _("Waiting for first chess program"));
8039     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8040     return;
8041   }
8042   if (second.lastPing != second.lastPong) {
8043     DisplayMessage("", _("Waiting for second chess program"));
8044     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8045     return;
8046   }
8047   ThawUI();
8048   TwoMachinesEvent();
8049 }
8050
8051 void
8052 NextMatchGame P((void))
8053 {
8054     int index; /* [HGM] autoinc: step load index during match */
8055     Reset(FALSE, TRUE);
8056     if (*appData.loadGameFile != NULLCHAR) {
8057         index = appData.loadGameIndex;
8058         if(index < 0) { // [HGM] autoinc
8059             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8060             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8061         }
8062         LoadGameFromFile(appData.loadGameFile,
8063                          index,
8064                          appData.loadGameFile, FALSE);
8065     } else if (*appData.loadPositionFile != NULLCHAR) {
8066         index = appData.loadPositionIndex;
8067         if(index < 0) { // [HGM] autoinc
8068             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8069             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8070         }
8071         LoadPositionFromFile(appData.loadPositionFile,
8072                              index,
8073                              appData.loadPositionFile);
8074     }
8075     TwoMachinesEventIfReady();
8076 }
8077
8078 void UserAdjudicationEvent( int result )
8079 {
8080     ChessMove gameResult = GameIsDrawn;
8081
8082     if( result > 0 ) {
8083         gameResult = WhiteWins;
8084     }
8085     else if( result < 0 ) {
8086         gameResult = BlackWins;
8087     }
8088
8089     if( gameMode == TwoMachinesPlay ) {
8090         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8091     }
8092 }
8093
8094
8095 // [HGM] save: calculate checksum of game to make games easily identifiable
8096 int StringCheckSum(char *s)
8097 {
8098         int i = 0;
8099         if(s==NULL) return 0;
8100         while(*s) i = i*259 + *s++;
8101         return i;
8102 }
8103
8104 int GameCheckSum()
8105 {
8106         int i, sum=0;
8107         for(i=backwardMostMove; i<forwardMostMove; i++) {
8108                 sum += pvInfoList[i].depth;
8109                 sum += StringCheckSum(parseList[i]);
8110                 sum += StringCheckSum(commentList[i]);
8111                 sum *= 261;
8112         }
8113         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8114         return sum + StringCheckSum(commentList[i]);
8115 } // end of save patch
8116
8117 void
8118 GameEnds(result, resultDetails, whosays)
8119      ChessMove result;
8120      char *resultDetails;
8121      int whosays;
8122 {
8123     GameMode nextGameMode;
8124     int isIcsGame;
8125     char buf[MSG_SIZ];
8126
8127     if(endingGame) return; /* [HGM] crash: forbid recursion */
8128     endingGame = 1;
8129
8130     if (appData.debugMode) {
8131       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8132               result, resultDetails ? resultDetails : "(null)", whosays);
8133     }
8134
8135     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8136         /* If we are playing on ICS, the server decides when the
8137            game is over, but the engine can offer to draw, claim
8138            a draw, or resign.
8139          */
8140 #if ZIPPY
8141         if (appData.zippyPlay && first.initDone) {
8142             if (result == GameIsDrawn) {
8143                 /* In case draw still needs to be claimed */
8144                 SendToICS(ics_prefix);
8145                 SendToICS("draw\n");
8146             } else if (StrCaseStr(resultDetails, "resign")) {
8147                 SendToICS(ics_prefix);
8148                 SendToICS("resign\n");
8149             }
8150         }
8151 #endif
8152         endingGame = 0; /* [HGM] crash */
8153         return;
8154     }
8155
8156     /* If we're loading the game from a file, stop */
8157     if (whosays == GE_FILE) {
8158       (void) StopLoadGameTimer();
8159       gameFileFP = NULL;
8160     }
8161
8162     /* Cancel draw offers */
8163     first.offeredDraw = second.offeredDraw = 0;
8164
8165     /* If this is an ICS game, only ICS can really say it's done;
8166        if not, anyone can. */
8167     isIcsGame = (gameMode == IcsPlayingWhite ||
8168                  gameMode == IcsPlayingBlack ||
8169                  gameMode == IcsObserving    ||
8170                  gameMode == IcsExamining);
8171
8172     if (!isIcsGame || whosays == GE_ICS) {
8173         /* OK -- not an ICS game, or ICS said it was done */
8174         StopClocks();
8175         if (!isIcsGame && !appData.noChessProgram)
8176           SetUserThinkingEnables();
8177
8178         /* [HGM] if a machine claims the game end we verify this claim */
8179         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8180             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8181                 char claimer;
8182                 ChessMove trueResult = (ChessMove) -1;
8183
8184                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8185                                             first.twoMachinesColor[0] :
8186                                             second.twoMachinesColor[0] ;
8187
8188                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8189                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8190                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8191                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8192                 } else
8193                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8194                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8195                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8196                 } else
8197                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8198                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8199                 }
8200
8201                 // now verify win claims, but not in drop games, as we don't understand those yet
8202                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8203                                                  || gameInfo.variant == VariantGreat) &&
8204                     (result == WhiteWins && claimer == 'w' ||
8205                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8206                       if (appData.debugMode) {
8207                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8208                                 result, epStatus[forwardMostMove], forwardMostMove);
8209                       }
8210                       if(result != trueResult) {
8211                               sprintf(buf, "False win claim: '%s'", resultDetails);
8212                               result = claimer == 'w' ? BlackWins : WhiteWins;
8213                               resultDetails = buf;
8214                       }
8215                 } else
8216                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8217                     && (forwardMostMove <= backwardMostMove ||
8218                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8219                         (claimer=='b')==(forwardMostMove&1))
8220                                                                                   ) {
8221                       /* [HGM] verify: draws that were not flagged are false claims */
8222                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8223                       result = claimer == 'w' ? BlackWins : WhiteWins;
8224                       resultDetails = buf;
8225                 }
8226                 /* (Claiming a loss is accepted no questions asked!) */
8227             }
8228
8229             /* [HGM] bare: don't allow bare King to win */
8230             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8231                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8232                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8233                && result != GameIsDrawn)
8234             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8235                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8236                         int p = (int)boards[forwardMostMove][i][j] - color;
8237                         if(p >= 0 && p <= (int)WhiteKing) k++;
8238                 }
8239                 if (appData.debugMode) {
8240                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8241                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8242                 }
8243                 if(k <= 1) {
8244                         result = GameIsDrawn;
8245                         sprintf(buf, "%s but bare king", resultDetails);
8246                         resultDetails = buf;
8247                 }
8248             }
8249         }
8250
8251         if(serverMoves != NULL && !loadFlag) { char c = '=';
8252             if(result==WhiteWins) c = '+';
8253             if(result==BlackWins) c = '-';
8254             if(resultDetails != NULL)
8255                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8256         }
8257         if (resultDetails != NULL) {
8258             gameInfo.result = result;
8259             gameInfo.resultDetails = StrSave(resultDetails);
8260
8261             /* display last move only if game was not loaded from file */
8262             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8263                 DisplayMove(currentMove - 1);
8264
8265             if (forwardMostMove != 0) {
8266                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8267                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8268                                                                 ) {
8269                     if (*appData.saveGameFile != NULLCHAR) {
8270                         SaveGameToFile(appData.saveGameFile, TRUE);
8271                     } else if (appData.autoSaveGames) {
8272                         AutoSaveGame();
8273                     }
8274                     if (*appData.savePositionFile != NULLCHAR) {
8275                         SavePositionToFile(appData.savePositionFile);
8276                     }
8277                 }
8278             }
8279
8280             /* Tell program how game ended in case it is learning */
8281             /* [HGM] Moved this to after saving the PGN, just in case */
8282             /* engine died and we got here through time loss. In that */
8283             /* case we will get a fatal error writing the pipe, which */
8284             /* would otherwise lose us the PGN.                       */
8285             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8286             /* output during GameEnds should never be fatal anymore   */
8287             if (gameMode == MachinePlaysWhite ||
8288                 gameMode == MachinePlaysBlack ||
8289                 gameMode == TwoMachinesPlay ||
8290                 gameMode == IcsPlayingWhite ||
8291                 gameMode == IcsPlayingBlack ||
8292                 gameMode == BeginningOfGame) {
8293                 char buf[MSG_SIZ];
8294                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8295                         resultDetails);
8296                 if (first.pr != NoProc) {
8297                     SendToProgram(buf, &first);
8298                 }
8299                 if (second.pr != NoProc &&
8300                     gameMode == TwoMachinesPlay) {
8301                     SendToProgram(buf, &second);
8302                 }
8303             }
8304         }
8305
8306         if (appData.icsActive) {
8307             if (appData.quietPlay &&
8308                 (gameMode == IcsPlayingWhite ||
8309                  gameMode == IcsPlayingBlack)) {
8310                 SendToICS(ics_prefix);
8311                 SendToICS("set shout 1\n");
8312             }
8313             nextGameMode = IcsIdle;
8314             ics_user_moved = FALSE;
8315             /* clean up premove.  It's ugly when the game has ended and the
8316              * premove highlights are still on the board.
8317              */
8318             if (gotPremove) {
8319               gotPremove = FALSE;
8320               ClearPremoveHighlights();
8321               DrawPosition(FALSE, boards[currentMove]);
8322             }
8323             if (whosays == GE_ICS) {
8324                 switch (result) {
8325                 case WhiteWins:
8326                     if (gameMode == IcsPlayingWhite)
8327                         PlayIcsWinSound();
8328                     else if(gameMode == IcsPlayingBlack)
8329                         PlayIcsLossSound();
8330                     break;
8331                 case BlackWins:
8332                     if (gameMode == IcsPlayingBlack)
8333                         PlayIcsWinSound();
8334                     else if(gameMode == IcsPlayingWhite)
8335                         PlayIcsLossSound();
8336                     break;
8337                 case GameIsDrawn:
8338                     PlayIcsDrawSound();
8339                     break;
8340                 default:
8341                     PlayIcsUnfinishedSound();
8342                 }
8343             }
8344         } else if (gameMode == EditGame ||
8345                    gameMode == PlayFromGameFile ||
8346                    gameMode == AnalyzeMode ||
8347                    gameMode == AnalyzeFile) {
8348             nextGameMode = gameMode;
8349         } else {
8350             nextGameMode = EndOfGame;
8351         }
8352         pausing = FALSE;
8353         ModeHighlight();
8354     } else {
8355         nextGameMode = gameMode;
8356     }
8357
8358     if (appData.noChessProgram) {
8359         gameMode = nextGameMode;
8360         ModeHighlight();
8361         endingGame = 0; /* [HGM] crash */
8362         return;
8363     }
8364
8365     if (first.reuse) {
8366         /* Put first chess program into idle state */
8367         if (first.pr != NoProc &&
8368             (gameMode == MachinePlaysWhite ||
8369              gameMode == MachinePlaysBlack ||
8370              gameMode == TwoMachinesPlay ||
8371              gameMode == IcsPlayingWhite ||
8372              gameMode == IcsPlayingBlack ||
8373              gameMode == BeginningOfGame)) {
8374             SendToProgram("force\n", &first);
8375             if (first.usePing) {
8376               char buf[MSG_SIZ];
8377               sprintf(buf, "ping %d\n", ++first.lastPing);
8378               SendToProgram(buf, &first);
8379             }
8380         }
8381     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8382         /* Kill off first chess program */
8383         if (first.isr != NULL)
8384           RemoveInputSource(first.isr);
8385         first.isr = NULL;
8386
8387         if (first.pr != NoProc) {
8388             ExitAnalyzeMode();
8389             DoSleep( appData.delayBeforeQuit );
8390             SendToProgram("quit\n", &first);
8391             DoSleep( appData.delayAfterQuit );
8392             DestroyChildProcess(first.pr, first.useSigterm);
8393         }
8394         first.pr = NoProc;
8395     }
8396     if (second.reuse) {
8397         /* Put second chess program into idle state */
8398         if (second.pr != NoProc &&
8399             gameMode == TwoMachinesPlay) {
8400             SendToProgram("force\n", &second);
8401             if (second.usePing) {
8402               char buf[MSG_SIZ];
8403               sprintf(buf, "ping %d\n", ++second.lastPing);
8404               SendToProgram(buf, &second);
8405             }
8406         }
8407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8408         /* Kill off second chess program */
8409         if (second.isr != NULL)
8410           RemoveInputSource(second.isr);
8411         second.isr = NULL;
8412
8413         if (second.pr != NoProc) {
8414             DoSleep( appData.delayBeforeQuit );
8415             SendToProgram("quit\n", &second);
8416             DoSleep( appData.delayAfterQuit );
8417             DestroyChildProcess(second.pr, second.useSigterm);
8418         }
8419         second.pr = NoProc;
8420     }
8421
8422     if (matchMode && gameMode == TwoMachinesPlay) {
8423         switch (result) {
8424         case WhiteWins:
8425           if (first.twoMachinesColor[0] == 'w') {
8426             first.matchWins++;
8427           } else {
8428             second.matchWins++;
8429           }
8430           break;
8431         case BlackWins:
8432           if (first.twoMachinesColor[0] == 'b') {
8433             first.matchWins++;
8434           } else {
8435             second.matchWins++;
8436           }
8437           break;
8438         default:
8439           break;
8440         }
8441         if (matchGame < appData.matchGames) {
8442             char *tmp;
8443             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8444                 tmp = first.twoMachinesColor;
8445                 first.twoMachinesColor = second.twoMachinesColor;
8446                 second.twoMachinesColor = tmp;
8447             }
8448             gameMode = nextGameMode;
8449             matchGame++;
8450             if(appData.matchPause>10000 || appData.matchPause<10)
8451                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8452             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8453             endingGame = 0; /* [HGM] crash */
8454             return;
8455         } else {
8456             char buf[MSG_SIZ];
8457             gameMode = nextGameMode;
8458             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8459                     first.tidy, second.tidy,
8460                     first.matchWins, second.matchWins,
8461                     appData.matchGames - (first.matchWins + second.matchWins));
8462             DisplayFatalError(buf, 0, 0);
8463         }
8464     }
8465     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8466         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8467       ExitAnalyzeMode();
8468     gameMode = nextGameMode;
8469     ModeHighlight();
8470     endingGame = 0;  /* [HGM] crash */
8471 }
8472
8473 /* Assumes program was just initialized (initString sent).
8474    Leaves program in force mode. */
8475 void
8476 FeedMovesToProgram(cps, upto)
8477      ChessProgramState *cps;
8478      int upto;
8479 {
8480     int i;
8481
8482     if (appData.debugMode)
8483       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8484               startedFromSetupPosition ? "position and " : "",
8485               backwardMostMove, upto, cps->which);
8486     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8487         // [HGM] variantswitch: make engine aware of new variant
8488         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8489                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8490         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8491         SendToProgram(buf, cps);
8492         currentlyInitializedVariant = gameInfo.variant;
8493     }
8494     SendToProgram("force\n", cps);
8495     if (startedFromSetupPosition) {
8496         SendBoard(cps, backwardMostMove);
8497     if (appData.debugMode) {
8498         fprintf(debugFP, "feedMoves\n");
8499     }
8500     }
8501     for (i = backwardMostMove; i < upto; i++) {
8502         SendMoveToProgram(i, cps);
8503     }
8504 }
8505
8506
8507 void
8508 ResurrectChessProgram()
8509 {
8510      /* The chess program may have exited.
8511         If so, restart it and feed it all the moves made so far. */
8512
8513     if (appData.noChessProgram || first.pr != NoProc) return;
8514
8515     StartChessProgram(&first);
8516     InitChessProgram(&first, FALSE);
8517     FeedMovesToProgram(&first, currentMove);
8518
8519     if (!first.sendTime) {
8520         /* can't tell gnuchess what its clock should read,
8521            so we bow to its notion. */
8522         ResetClocks();
8523         timeRemaining[0][currentMove] = whiteTimeRemaining;
8524         timeRemaining[1][currentMove] = blackTimeRemaining;
8525     }
8526
8527     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8528                 appData.icsEngineAnalyze) && first.analysisSupport) {
8529       SendToProgram("analyze\n", &first);
8530       first.analyzing = TRUE;
8531     }
8532 }
8533
8534 /*
8535  * Button procedures
8536  */
8537 void
8538 Reset(redraw, init)
8539      int redraw, init;
8540 {
8541     int i;
8542
8543     if (appData.debugMode) {
8544         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8545                 redraw, init, gameMode);
8546     }
8547     pausing = pauseExamInvalid = FALSE;
8548     startedFromSetupPosition = blackPlaysFirst = FALSE;
8549     firstMove = TRUE;
8550     whiteFlag = blackFlag = FALSE;
8551     userOfferedDraw = FALSE;
8552     hintRequested = bookRequested = FALSE;
8553     first.maybeThinking = FALSE;
8554     second.maybeThinking = FALSE;
8555     first.bookSuspend = FALSE; // [HGM] book
8556     second.bookSuspend = FALSE;
8557     thinkOutput[0] = NULLCHAR;
8558     lastHint[0] = NULLCHAR;
8559     ClearGameInfo(&gameInfo);
8560     gameInfo.variant = StringToVariant(appData.variant);
8561     ics_user_moved = ics_clock_paused = FALSE;
8562     ics_getting_history = H_FALSE;
8563     ics_gamenum = -1;
8564     white_holding[0] = black_holding[0] = NULLCHAR;
8565     ClearProgramStats();
8566     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8567
8568     ResetFrontEnd();
8569     ClearHighlights();
8570     flipView = appData.flipView;
8571     ClearPremoveHighlights();
8572     gotPremove = FALSE;
8573     alarmSounded = FALSE;
8574
8575     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8576     if(appData.serverMovesName != NULL) {
8577         /* [HGM] prepare to make moves file for broadcasting */
8578         clock_t t = clock();
8579         if(serverMoves != NULL) fclose(serverMoves);
8580         serverMoves = fopen(appData.serverMovesName, "r");
8581         if(serverMoves != NULL) {
8582             fclose(serverMoves);
8583             /* delay 15 sec before overwriting, so all clients can see end */
8584             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8585         }
8586         serverMoves = fopen(appData.serverMovesName, "w");
8587     }
8588
8589     ExitAnalyzeMode();
8590     gameMode = BeginningOfGame;
8591     ModeHighlight();
8592
8593     if(appData.icsActive) gameInfo.variant = VariantNormal;
8594     currentMove = forwardMostMove = backwardMostMove = 0;
8595     InitPosition(redraw);
8596     for (i = 0; i < MAX_MOVES; i++) {
8597         if (commentList[i] != NULL) {
8598             free(commentList[i]);
8599             commentList[i] = NULL;
8600         }
8601     }
8602
8603     ResetClocks();
8604     timeRemaining[0][0] = whiteTimeRemaining;
8605     timeRemaining[1][0] = blackTimeRemaining;
8606     if (first.pr == NULL) {
8607         StartChessProgram(&first);
8608     }
8609     if (init) {
8610             InitChessProgram(&first, startedFromSetupPosition);
8611     }
8612
8613     DisplayTitle("");
8614     DisplayMessage("", "");
8615     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8616     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8617     return;
8618 }
8619
8620 void
8621 AutoPlayGameLoop()
8622 {
8623     for (;;) {
8624         if (!AutoPlayOneMove())
8625           return;
8626         if (matchMode || appData.timeDelay == 0)
8627           continue;
8628         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8629           return;
8630         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8631         break;
8632     }
8633 }
8634
8635
8636 int
8637 AutoPlayOneMove()
8638 {
8639     int fromX, fromY, toX, toY;
8640
8641     if (appData.debugMode) {
8642       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8643     }
8644
8645     if (gameMode != PlayFromGameFile)
8646       return FALSE;
8647
8648     if (currentMove >= forwardMostMove) {
8649       gameMode = EditGame;
8650       ModeHighlight();
8651
8652       /* [AS] Clear current move marker at the end of a game */
8653       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8654
8655       return FALSE;
8656     }
8657
8658     toX = moveList[currentMove][2] - AAA;
8659     toY = moveList[currentMove][3] - ONE;
8660
8661     if (moveList[currentMove][1] == '@') {
8662         if (appData.highlightLastMove) {
8663             SetHighlights(-1, -1, toX, toY);
8664         }
8665     } else {
8666         fromX = moveList[currentMove][0] - AAA;
8667         fromY = moveList[currentMove][1] - ONE;
8668
8669         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8670
8671         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8672
8673         if (appData.highlightLastMove) {
8674             SetHighlights(fromX, fromY, toX, toY);
8675         }
8676     }
8677     DisplayMove(currentMove);
8678     SendMoveToProgram(currentMove++, &first);
8679     DisplayBothClocks();
8680     DrawPosition(FALSE, boards[currentMove]);
8681     // [HGM] PV info: always display, routine tests if empty
8682     DisplayComment(currentMove - 1, commentList[currentMove]);
8683     return TRUE;
8684 }
8685
8686
8687 int
8688 LoadGameOneMove(readAhead)
8689      ChessMove readAhead;
8690 {
8691     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8692     char promoChar = NULLCHAR;
8693     ChessMove moveType;
8694     char move[MSG_SIZ];
8695     char *p, *q;
8696
8697     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8698         gameMode != AnalyzeMode && gameMode != Training) {
8699         gameFileFP = NULL;
8700         return FALSE;
8701     }
8702
8703     yyboardindex = forwardMostMove;
8704     if (readAhead != (ChessMove)0) {
8705       moveType = readAhead;
8706     } else {
8707       if (gameFileFP == NULL)
8708           return FALSE;
8709       moveType = (ChessMove) yylex();
8710     }
8711
8712     done = FALSE;
8713     switch (moveType) {
8714       case Comment:
8715         if (appData.debugMode)
8716           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8717         p = yy_text;
8718         if (*p == '{' || *p == '[' || *p == '(') {
8719             p[strlen(p) - 1] = NULLCHAR;
8720             p++;
8721         }
8722
8723         /* append the comment but don't display it */
8724         while (*p == '\n') p++;
8725         AppendComment(currentMove, p);
8726         return TRUE;
8727
8728       case WhiteCapturesEnPassant:
8729       case BlackCapturesEnPassant:
8730       case WhitePromotionChancellor:
8731       case BlackPromotionChancellor:
8732       case WhitePromotionArchbishop:
8733       case BlackPromotionArchbishop:
8734       case WhitePromotionCentaur:
8735       case BlackPromotionCentaur:
8736       case WhitePromotionQueen:
8737       case BlackPromotionQueen:
8738       case WhitePromotionRook:
8739       case BlackPromotionRook:
8740       case WhitePromotionBishop:
8741       case BlackPromotionBishop:
8742       case WhitePromotionKnight:
8743       case BlackPromotionKnight:
8744       case WhitePromotionKing:
8745       case BlackPromotionKing:
8746       case NormalMove:
8747       case WhiteKingSideCastle:
8748       case WhiteQueenSideCastle:
8749       case BlackKingSideCastle:
8750       case BlackQueenSideCastle:
8751       case WhiteKingSideCastleWild:
8752       case WhiteQueenSideCastleWild:
8753       case BlackKingSideCastleWild:
8754       case BlackQueenSideCastleWild:
8755       /* PUSH Fabien */
8756       case WhiteHSideCastleFR:
8757       case WhiteASideCastleFR:
8758       case BlackHSideCastleFR:
8759       case BlackASideCastleFR:
8760       /* POP Fabien */
8761         if (appData.debugMode)
8762           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8763         fromX = currentMoveString[0] - AAA;
8764         fromY = currentMoveString[1] - ONE;
8765         toX = currentMoveString[2] - AAA;
8766         toY = currentMoveString[3] - ONE;
8767         promoChar = currentMoveString[4];
8768         break;
8769
8770       case WhiteDrop:
8771       case BlackDrop:
8772         if (appData.debugMode)
8773           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8774         fromX = moveType == WhiteDrop ?
8775           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8776         (int) CharToPiece(ToLower(currentMoveString[0]));
8777         fromY = DROP_RANK;
8778         toX = currentMoveString[2] - AAA;
8779         toY = currentMoveString[3] - ONE;
8780         break;
8781
8782       case WhiteWins:
8783       case BlackWins:
8784       case GameIsDrawn:
8785       case GameUnfinished:
8786         if (appData.debugMode)
8787           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8788         p = strchr(yy_text, '{');
8789         if (p == NULL) p = strchr(yy_text, '(');
8790         if (p == NULL) {
8791             p = yy_text;
8792             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8793         } else {
8794             q = strchr(p, *p == '{' ? '}' : ')');
8795             if (q != NULL) *q = NULLCHAR;
8796             p++;
8797         }
8798         GameEnds(moveType, p, GE_FILE);
8799         done = TRUE;
8800         if (cmailMsgLoaded) {
8801             ClearHighlights();
8802             flipView = WhiteOnMove(currentMove);
8803             if (moveType == GameUnfinished) flipView = !flipView;
8804             if (appData.debugMode)
8805               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8806         }
8807         break;
8808
8809       case (ChessMove) 0:       /* end of file */
8810         if (appData.debugMode)
8811           fprintf(debugFP, "Parser hit end of file\n");
8812         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8813                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8814           case MT_NONE:
8815           case MT_CHECK:
8816             break;
8817           case MT_CHECKMATE:
8818           case MT_STAINMATE:
8819             if (WhiteOnMove(currentMove)) {
8820                 GameEnds(BlackWins, "Black mates", GE_FILE);
8821             } else {
8822                 GameEnds(WhiteWins, "White mates", GE_FILE);
8823             }
8824             break;
8825           case MT_STALEMATE:
8826             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8827             break;
8828         }
8829         done = TRUE;
8830         break;
8831
8832       case MoveNumberOne:
8833         if (lastLoadGameStart == GNUChessGame) {
8834             /* GNUChessGames have numbers, but they aren't move numbers */
8835             if (appData.debugMode)
8836               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8837                       yy_text, (int) moveType);
8838             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8839         }
8840         /* else fall thru */
8841
8842       case XBoardGame:
8843       case GNUChessGame:
8844       case PGNTag:
8845         /* Reached start of next game in file */
8846         if (appData.debugMode)
8847           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8848         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8849                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8850           case MT_NONE:
8851           case MT_CHECK:
8852             break;
8853           case MT_CHECKMATE:
8854           case MT_STAINMATE:
8855             if (WhiteOnMove(currentMove)) {
8856                 GameEnds(BlackWins, "Black mates", GE_FILE);
8857             } else {
8858                 GameEnds(WhiteWins, "White mates", GE_FILE);
8859             }
8860             break;
8861           case MT_STALEMATE:
8862             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8863             break;
8864         }
8865         done = TRUE;
8866         break;
8867
8868       case PositionDiagram:     /* should not happen; ignore */
8869       case ElapsedTime:         /* ignore */
8870       case NAG:                 /* ignore */
8871         if (appData.debugMode)
8872           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8873                   yy_text, (int) moveType);
8874         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8875
8876       case IllegalMove:
8877         if (appData.testLegality) {
8878             if (appData.debugMode)
8879               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8880             sprintf(move, _("Illegal move: %d.%s%s"),
8881                     (forwardMostMove / 2) + 1,
8882                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8883             DisplayError(move, 0);
8884             done = TRUE;
8885         } else {
8886             if (appData.debugMode)
8887               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8888                       yy_text, currentMoveString);
8889             fromX = currentMoveString[0] - AAA;
8890             fromY = currentMoveString[1] - ONE;
8891             toX = currentMoveString[2] - AAA;
8892             toY = currentMoveString[3] - ONE;
8893             promoChar = currentMoveString[4];
8894         }
8895         break;
8896
8897       case AmbiguousMove:
8898         if (appData.debugMode)
8899           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8900         sprintf(move, _("Ambiguous move: %d.%s%s"),
8901                 (forwardMostMove / 2) + 1,
8902                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8903         DisplayError(move, 0);
8904         done = TRUE;
8905         break;
8906
8907       default:
8908       case ImpossibleMove:
8909         if (appData.debugMode)
8910           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8911         sprintf(move, _("Illegal move: %d.%s%s"),
8912                 (forwardMostMove / 2) + 1,
8913                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8914         DisplayError(move, 0);
8915         done = TRUE;
8916         break;
8917     }
8918
8919     if (done) {
8920         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8921             DrawPosition(FALSE, boards[currentMove]);
8922             DisplayBothClocks();
8923             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8924               DisplayComment(currentMove - 1, commentList[currentMove]);
8925         }
8926         (void) StopLoadGameTimer();
8927         gameFileFP = NULL;
8928         cmailOldMove = forwardMostMove;
8929         return FALSE;
8930     } else {
8931         /* currentMoveString is set as a side-effect of yylex */
8932         strcat(currentMoveString, "\n");
8933         strcpy(moveList[forwardMostMove], currentMoveString);
8934
8935         thinkOutput[0] = NULLCHAR;
8936         MakeMove(fromX, fromY, toX, toY, promoChar);
8937         currentMove = forwardMostMove;
8938         return TRUE;
8939     }
8940 }
8941
8942 /* Load the nth game from the given file */
8943 int
8944 LoadGameFromFile(filename, n, title, useList)
8945      char *filename;
8946      int n;
8947      char *title;
8948      /*Boolean*/ int useList;
8949 {
8950     FILE *f;
8951     char buf[MSG_SIZ];
8952
8953     if (strcmp(filename, "-") == 0) {
8954         f = stdin;
8955         title = "stdin";
8956     } else {
8957         f = fopen(filename, "rb");
8958         if (f == NULL) {
8959           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8960             DisplayError(buf, errno);
8961             return FALSE;
8962         }
8963     }
8964     if (fseek(f, 0, 0) == -1) {
8965         /* f is not seekable; probably a pipe */
8966         useList = FALSE;
8967     }
8968     if (useList && n == 0) {
8969         int error = GameListBuild(f);
8970         if (error) {
8971             DisplayError(_("Cannot build game list"), error);
8972         } else if (!ListEmpty(&gameList) &&
8973                    ((ListGame *) gameList.tailPred)->number > 1) {
8974           // TODO convert to GTK
8975           //        GameListPopUp(f, title);
8976             return TRUE;
8977         }
8978         GameListDestroy();
8979         n = 1;
8980     }
8981     if (n == 0) n = 1;
8982     return LoadGame(f, n, title, FALSE);
8983 }
8984
8985
8986 void
8987 MakeRegisteredMove()
8988 {
8989     int fromX, fromY, toX, toY;
8990     char promoChar;
8991     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8992         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8993           case CMAIL_MOVE:
8994           case CMAIL_DRAW:
8995             if (appData.debugMode)
8996               fprintf(debugFP, "Restoring %s for game %d\n",
8997                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8998
8999             thinkOutput[0] = NULLCHAR;
9000             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9001             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9002             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9003             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9004             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9005             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9006             MakeMove(fromX, fromY, toX, toY, promoChar);
9007             ShowMove(fromX, fromY, toX, toY);
9008
9009             switch (MateTest(boards[currentMove], PosFlags(currentMove),
9010                              EP_UNKNOWN, castlingRights[currentMove]) ) {
9011               case MT_NONE:
9012               case MT_CHECK:
9013                 break;
9014
9015               case MT_CHECKMATE:
9016               case MT_STAINMATE:
9017                 if (WhiteOnMove(currentMove)) {
9018                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9019                 } else {
9020                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9021                 }
9022                 break;
9023
9024               case MT_STALEMATE:
9025                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9026                 break;
9027             }
9028
9029             break;
9030
9031           case CMAIL_RESIGN:
9032             if (WhiteOnMove(currentMove)) {
9033                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9034             } else {
9035                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9036             }
9037             break;
9038
9039           case CMAIL_ACCEPT:
9040             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9041             break;
9042
9043           default:
9044             break;
9045         }
9046     }
9047
9048     return;
9049 }
9050
9051 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9052 int
9053 CmailLoadGame(f, gameNumber, title, useList)
9054      FILE *f;
9055      int gameNumber;
9056      char *title;
9057      int useList;
9058 {
9059     int retVal;
9060
9061     if (gameNumber > nCmailGames) {
9062         DisplayError(_("No more games in this message"), 0);
9063         return FALSE;
9064     }
9065     if (f == lastLoadGameFP) {
9066         int offset = gameNumber - lastLoadGameNumber;
9067         if (offset == 0) {
9068             cmailMsg[0] = NULLCHAR;
9069             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9070                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9071                 nCmailMovesRegistered--;
9072             }
9073             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9074             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9075                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9076             }
9077         } else {
9078             if (! RegisterMove()) return FALSE;
9079         }
9080     }
9081
9082     retVal = LoadGame(f, gameNumber, title, useList);
9083
9084     /* Make move registered during previous look at this game, if any */
9085     MakeRegisteredMove();
9086
9087     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9088         commentList[currentMove]
9089           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9090         DisplayComment(currentMove - 1, commentList[currentMove]);
9091     }
9092
9093     return retVal;
9094 }
9095
9096 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9097 int
9098 ReloadGame(offset)
9099      int offset;
9100 {
9101     int gameNumber = lastLoadGameNumber + offset;
9102     if (lastLoadGameFP == NULL) {
9103         DisplayError(_("No game has been loaded yet"), 0);
9104         return FALSE;
9105     }
9106     if (gameNumber <= 0) {
9107         DisplayError(_("Can't back up any further"), 0);
9108         return FALSE;
9109     }
9110     if (cmailMsgLoaded) {
9111         return CmailLoadGame(lastLoadGameFP, gameNumber,
9112                              lastLoadGameTitle, lastLoadGameUseList);
9113     } else {
9114         return LoadGame(lastLoadGameFP, gameNumber,
9115                         lastLoadGameTitle, lastLoadGameUseList);
9116     }
9117 }
9118
9119
9120
9121 /* Load the nth game from open file f */
9122 int
9123 LoadGame(f, gameNumber, title, useList)
9124      FILE *f;
9125      int gameNumber;
9126      char *title;
9127      int useList;
9128 {
9129     ChessMove cm;
9130     char buf[MSG_SIZ];
9131     int gn = gameNumber;
9132     ListGame *lg = NULL;
9133     int numPGNTags = 0;
9134     int err;
9135     GameMode oldGameMode;
9136     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9137
9138     if (appData.debugMode)
9139         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9140
9141     if (gameMode == Training )
9142         SetTrainingModeOff();
9143
9144     oldGameMode = gameMode;
9145     if (gameMode != BeginningOfGame) 
9146       {
9147         Reset(FALSE, TRUE);
9148       };
9149
9150     gameFileFP = f;
9151     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9152       {
9153         fclose(lastLoadGameFP);
9154       };
9155
9156     if (useList) 
9157       {
9158         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9159         
9160         if (lg) 
9161           {
9162             fseek(f, lg->offset, 0);
9163             GameListHighlight(gameNumber);
9164             gn = 1;
9165           }
9166         else 
9167           {
9168             DisplayError(_("Game number out of range"), 0);
9169             return FALSE;
9170           };
9171       } 
9172     else 
9173       {
9174         GameListDestroy();
9175         if (fseek(f, 0, 0) == -1) 
9176           {
9177             if (f == lastLoadGameFP ?
9178                 gameNumber == lastLoadGameNumber + 1 :
9179                 gameNumber == 1) 
9180               {
9181                 gn = 1;
9182               } 
9183             else 
9184               {
9185                 DisplayError(_("Can't seek on game file"), 0);
9186                 return FALSE;
9187               };
9188           };
9189       };
9190
9191     lastLoadGameFP      = f;
9192     lastLoadGameNumber  = gameNumber;
9193     strcpy(lastLoadGameTitle, title);
9194     lastLoadGameUseList = useList;
9195
9196     yynewfile(f);
9197
9198     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9199       {
9200         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9201                  lg->gameInfo.black);
9202         DisplayTitle(buf);
9203       } 
9204     else if (*title != NULLCHAR) 
9205       {
9206         if (gameNumber > 1) 
9207           {
9208             sprintf(buf, "%s %d", title, gameNumber);
9209             DisplayTitle(buf);
9210           } 
9211         else 
9212           {
9213             DisplayTitle(title);
9214           };
9215       };
9216
9217     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9218       {
9219         gameMode = PlayFromGameFile;
9220         ModeHighlight();
9221       };
9222
9223     currentMove = forwardMostMove = backwardMostMove = 0;
9224     CopyBoard(boards[0], initialPosition);
9225     StopClocks();
9226
9227     /*
9228      * Skip the first gn-1 games in the file.
9229      * Also skip over anything that precedes an identifiable
9230      * start of game marker, to avoid being confused by
9231      * garbage at the start of the file.  Currently
9232      * recognized start of game markers are the move number "1",
9233      * the pattern "gnuchess .* game", the pattern
9234      * "^[#;%] [^ ]* game file", and a PGN tag block.
9235      * A game that starts with one of the latter two patterns
9236      * will also have a move number 1, possibly
9237      * following a position diagram.
9238      * 5-4-02: Let's try being more lenient and allowing a game to
9239      * start with an unnumbered move.  Does that break anything?
9240      */
9241     cm = lastLoadGameStart = (ChessMove) 0;
9242     while (gn > 0) {
9243         yyboardindex = forwardMostMove;
9244         cm = (ChessMove) yylex();
9245         switch (cm) {
9246           case (ChessMove) 0:
9247             if (cmailMsgLoaded) {
9248                 nCmailGames = CMAIL_MAX_GAMES - gn;
9249             } else {
9250                 Reset(TRUE, TRUE);
9251                 DisplayError(_("Game not found in file"), 0);
9252             }
9253             return FALSE;
9254
9255           case GNUChessGame:
9256           case XBoardGame:
9257             gn--;
9258             lastLoadGameStart = cm;
9259             break;
9260
9261           case MoveNumberOne:
9262             switch (lastLoadGameStart) {
9263               case GNUChessGame:
9264               case XBoardGame:
9265               case PGNTag:
9266                 break;
9267               case MoveNumberOne:
9268               case (ChessMove) 0:
9269                 gn--;           /* count this game */
9270                 lastLoadGameStart = cm;
9271                 break;
9272               default:
9273                 /* impossible */
9274                 break;
9275             }
9276             break;
9277
9278           case PGNTag:
9279             switch (lastLoadGameStart) {
9280               case GNUChessGame:
9281               case PGNTag:
9282               case MoveNumberOne:
9283               case (ChessMove) 0:
9284                 gn--;           /* count this game */
9285                 lastLoadGameStart = cm;
9286                 break;
9287               case XBoardGame:
9288                 lastLoadGameStart = cm; /* game counted already */
9289                 break;
9290               default:
9291                 /* impossible */
9292                 break;
9293             }
9294             if (gn > 0) {
9295                 do {
9296                     yyboardindex = forwardMostMove;
9297                     cm = (ChessMove) yylex();
9298                 } while (cm == PGNTag || cm == Comment);
9299             }
9300             break;
9301
9302           case WhiteWins:
9303           case BlackWins:
9304           case GameIsDrawn:
9305             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9306                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9307                     != CMAIL_OLD_RESULT) {
9308                     nCmailResults ++ ;
9309                     cmailResult[  CMAIL_MAX_GAMES
9310                                 - gn - 1] = CMAIL_OLD_RESULT;
9311                 }
9312             }
9313             break;
9314
9315           case NormalMove:
9316             /* Only a NormalMove can be at the start of a game
9317              * without a position diagram. */
9318             if (lastLoadGameStart == (ChessMove) 0) {
9319               gn--;
9320               lastLoadGameStart = MoveNumberOne;
9321             }
9322             break;
9323
9324           default:
9325             break;
9326         }
9327     }
9328
9329     if (appData.debugMode)
9330       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9331
9332     if (cm == XBoardGame) {
9333         /* Skip any header junk before position diagram and/or move 1 */
9334         for (;;) {
9335             yyboardindex = forwardMostMove;
9336             cm = (ChessMove) yylex();
9337
9338             if (cm == (ChessMove) 0 ||
9339                 cm == GNUChessGame || cm == XBoardGame) {
9340                 /* Empty game; pretend end-of-file and handle later */
9341                 cm = (ChessMove) 0;
9342                 break;
9343             }
9344
9345             if (cm == MoveNumberOne || cm == PositionDiagram ||
9346                 cm == PGNTag || cm == Comment)
9347               break;
9348         }
9349     } else if (cm == GNUChessGame) {
9350         if (gameInfo.event != NULL) {
9351             free(gameInfo.event);
9352         }
9353         gameInfo.event = StrSave(yy_text);
9354     }
9355
9356     startedFromSetupPosition = FALSE;
9357     while (cm == PGNTag) {
9358         if (appData.debugMode)
9359           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9360         err = ParsePGNTag(yy_text, &gameInfo);
9361         if (!err) numPGNTags++;
9362
9363         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9364         if(gameInfo.variant != oldVariant) {
9365             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9366             InitPosition(TRUE);
9367             oldVariant = gameInfo.variant;
9368             if (appData.debugMode)
9369               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9370         }
9371
9372
9373         if (gameInfo.fen != NULL) {
9374           Board initial_position;
9375           startedFromSetupPosition = TRUE;
9376           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9377             Reset(TRUE, TRUE);
9378             DisplayError(_("Bad FEN position in file"), 0);
9379             return FALSE;
9380           }
9381           CopyBoard(boards[0], initial_position);
9382           if (blackPlaysFirst) {
9383             currentMove = forwardMostMove = backwardMostMove = 1;
9384             CopyBoard(boards[1], initial_position);
9385             strcpy(moveList[0], "");
9386             strcpy(parseList[0], "");
9387             timeRemaining[0][1] = whiteTimeRemaining;
9388             timeRemaining[1][1] = blackTimeRemaining;
9389             if (commentList[0] != NULL) {
9390               commentList[1] = commentList[0];
9391               commentList[0] = NULL;
9392             }
9393           } else {
9394             currentMove = forwardMostMove = backwardMostMove = 0;
9395           }
9396           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9397           {   int i;
9398               initialRulePlies = FENrulePlies;
9399               epStatus[forwardMostMove] = FENepStatus;
9400               for( i=0; i< nrCastlingRights; i++ )
9401                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9402           }
9403           yyboardindex = forwardMostMove;
9404           free(gameInfo.fen);
9405           gameInfo.fen = NULL;
9406         }
9407
9408         yyboardindex = forwardMostMove;
9409         cm = (ChessMove) yylex();
9410
9411         /* Handle comments interspersed among the tags */
9412         while (cm == Comment) {
9413             char *p;
9414             if (appData.debugMode)
9415               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9416             p = yy_text;
9417             if (*p == '{' || *p == '[' || *p == '(') {
9418                 p[strlen(p) - 1] = NULLCHAR;
9419                 p++;
9420             }
9421             while (*p == '\n') p++;
9422             AppendComment(currentMove, p);
9423             yyboardindex = forwardMostMove;
9424             cm = (ChessMove) yylex();
9425         }
9426     }
9427
9428     /* don't rely on existence of Event tag since if game was
9429      * pasted from clipboard the Event tag may not exist
9430      */
9431     if (numPGNTags > 0){
9432         char *tags;
9433         if (gameInfo.variant == VariantNormal) {
9434           gameInfo.variant = StringToVariant(gameInfo.event);
9435         }
9436         if (!matchMode) {
9437           if( appData.autoDisplayTags ) {
9438             tags = PGNTags(&gameInfo);
9439             TagsPopUp(tags, CmailMsg());
9440             free(tags);
9441           }
9442         }
9443     } else {
9444         /* Make something up, but don't display it now */
9445         SetGameInfo();
9446         TagsPopDown();
9447     }
9448
9449     if (cm == PositionDiagram) {
9450         int i, j;
9451         char *p;
9452         Board initial_position;
9453
9454         if (appData.debugMode)
9455           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9456
9457         if (!startedFromSetupPosition) {
9458             p = yy_text;
9459             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9460               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9461                 switch (*p) {
9462                   case '[':
9463                   case '-':
9464                   case ' ':
9465                   case '\t':
9466                   case '\n':
9467                   case '\r':
9468                     break;
9469                   default:
9470                     initial_position[i][j++] = CharToPiece(*p);
9471                     break;
9472                 }
9473             while (*p == ' ' || *p == '\t' ||
9474                    *p == '\n' || *p == '\r') p++;
9475
9476             if (strncmp(p, "black", strlen("black"))==0)
9477               blackPlaysFirst = TRUE;
9478             else
9479               blackPlaysFirst = FALSE;
9480             startedFromSetupPosition = TRUE;
9481
9482             CopyBoard(boards[0], initial_position);
9483             if (blackPlaysFirst) {
9484                 currentMove = forwardMostMove = backwardMostMove = 1;
9485                 CopyBoard(boards[1], initial_position);
9486                 strcpy(moveList[0], "");
9487                 strcpy(parseList[0], "");
9488                 timeRemaining[0][1] = whiteTimeRemaining;
9489                 timeRemaining[1][1] = blackTimeRemaining;
9490                 if (commentList[0] != NULL) {
9491                     commentList[1] = commentList[0];
9492                     commentList[0] = NULL;
9493                 }
9494             } else {
9495                 currentMove = forwardMostMove = backwardMostMove = 0;
9496             }
9497         }
9498         yyboardindex = forwardMostMove;
9499         cm = (ChessMove) yylex();
9500     }
9501
9502     if (first.pr == NoProc) {
9503         StartChessProgram(&first);
9504     }
9505     InitChessProgram(&first, FALSE);
9506     SendToProgram("force\n", &first);
9507     if (startedFromSetupPosition) {
9508         SendBoard(&first, forwardMostMove);
9509     if (appData.debugMode) {
9510         fprintf(debugFP, "Load Game\n");
9511     }
9512         DisplayBothClocks();
9513     }
9514
9515     /* [HGM] server: flag to write setup moves in broadcast file as one */
9516     loadFlag = appData.suppressLoadMoves;
9517
9518     while (cm == Comment) {
9519         char *p;
9520         if (appData.debugMode)
9521           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9522         p = yy_text;
9523         if (*p == '{' || *p == '[' || *p == '(') {
9524             p[strlen(p) - 1] = NULLCHAR;
9525             p++;
9526         }
9527         while (*p == '\n') p++;
9528         AppendComment(currentMove, p);
9529         yyboardindex = forwardMostMove;
9530         cm = (ChessMove) yylex();
9531     }
9532
9533     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9534         cm == WhiteWins || cm == BlackWins ||
9535         cm == GameIsDrawn || cm == GameUnfinished) {
9536         DisplayMessage("", _("No moves in game"));
9537         if (cmailMsgLoaded) {
9538             if (appData.debugMode)
9539               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9540             ClearHighlights();
9541             flipView = FALSE;
9542         }
9543         DrawPosition(FALSE, boards[currentMove]);
9544         DisplayBothClocks();
9545         gameMode = EditGame;
9546         ModeHighlight();
9547         gameFileFP = NULL;
9548         cmailOldMove = 0;
9549         return TRUE;
9550     }
9551
9552     // [HGM] PV info: routine tests if comment empty
9553     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9554         DisplayComment(currentMove - 1, commentList[currentMove]);
9555     }
9556     if (!matchMode && appData.timeDelay != 0)
9557       DrawPosition(FALSE, boards[currentMove]);
9558
9559     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9560       programStats.ok_to_send = 1;
9561     }
9562
9563     /* if the first token after the PGN tags is a move
9564      * and not move number 1, retrieve it from the parser
9565      */
9566     if (cm != MoveNumberOne)
9567         LoadGameOneMove(cm);
9568
9569     /* load the remaining moves from the file */
9570     while (LoadGameOneMove((ChessMove)0)) {
9571       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9572       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9573     }
9574
9575     /* rewind to the start of the game */
9576     currentMove = backwardMostMove;
9577
9578     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9579
9580     if (oldGameMode == AnalyzeFile ||
9581         oldGameMode == AnalyzeMode) {
9582       AnalyzeFileEvent();
9583     }
9584
9585     if (matchMode || appData.timeDelay == 0) {
9586       ToEndEvent();
9587       gameMode = EditGame;
9588       ModeHighlight();
9589     } else if (appData.timeDelay > 0) {
9590       AutoPlayGameLoop();
9591     }
9592
9593     if (appData.debugMode)
9594         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9595
9596     loadFlag = 0; /* [HGM] true game starts */
9597     return TRUE;
9598 }
9599
9600 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9601 int
9602 ReloadPosition(offset)
9603      int offset;
9604 {
9605     int positionNumber = lastLoadPositionNumber + offset;
9606     if (lastLoadPositionFP == NULL) {
9607         DisplayError(_("No position has been loaded yet"), 0);
9608         return FALSE;
9609     }
9610     if (positionNumber <= 0) {
9611         DisplayError(_("Can't back up any further"), 0);
9612         return FALSE;
9613     }
9614     return LoadPosition(lastLoadPositionFP, positionNumber,
9615                         lastLoadPositionTitle);
9616 }
9617
9618 /* Load the nth position from the given file */
9619 int
9620 LoadPositionFromFile(filename, n, title)
9621      char *filename;
9622      int n;
9623      char *title;
9624 {
9625     FILE *f;
9626     char buf[MSG_SIZ];
9627
9628     if (strcmp(filename, "-") == 0) {
9629         return LoadPosition(stdin, n, "stdin");
9630     } else {
9631         f = fopen(filename, "rb");
9632         if (f == NULL) {
9633             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9634             DisplayError(buf, errno);
9635             return FALSE;
9636         } else {
9637             return LoadPosition(f, n, title);
9638         }
9639     }
9640 }
9641
9642 /* Load the nth position from the given open file, and close it */
9643 int
9644 LoadPosition(f, positionNumber, title)
9645      FILE *f;
9646      int positionNumber;
9647      char *title;
9648 {
9649     char *p, line[MSG_SIZ];
9650     Board initial_position;
9651     int i, j, fenMode, pn;
9652
9653     if (gameMode == Training )
9654         SetTrainingModeOff();
9655
9656     if (gameMode != BeginningOfGame) {
9657         Reset(FALSE, TRUE);
9658     }
9659     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9660         fclose(lastLoadPositionFP);
9661     }
9662     if (positionNumber == 0) positionNumber = 1;
9663     lastLoadPositionFP = f;
9664     lastLoadPositionNumber = positionNumber;
9665     strcpy(lastLoadPositionTitle, title);
9666     if (first.pr == NoProc) {
9667       StartChessProgram(&first);
9668       InitChessProgram(&first, FALSE);
9669     }
9670     pn = positionNumber;
9671     if (positionNumber < 0) {
9672         /* Negative position number means to seek to that byte offset */
9673         if (fseek(f, -positionNumber, 0) == -1) {
9674             DisplayError(_("Can't seek on position file"), 0);
9675             return FALSE;
9676         };
9677         pn = 1;
9678     } else {
9679         if (fseek(f, 0, 0) == -1) {
9680             if (f == lastLoadPositionFP ?
9681                 positionNumber == lastLoadPositionNumber + 1 :
9682                 positionNumber == 1) {
9683                 pn = 1;
9684             } else {
9685                 DisplayError(_("Can't seek on position file"), 0);
9686                 return FALSE;
9687             }
9688         }
9689     }
9690     /* See if this file is FEN or old-style xboard */
9691     if (fgets(line, MSG_SIZ, f) == NULL) {
9692         DisplayError(_("Position not found in file"), 0);
9693         return FALSE;
9694     }
9695     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9696     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9697
9698     if (pn >= 2) {
9699         if (fenMode || line[0] == '#') pn--;
9700         while (pn > 0) {
9701             /* skip positions before number pn */
9702             if (fgets(line, MSG_SIZ, f) == NULL) {
9703                 Reset(TRUE, TRUE);
9704                 DisplayError(_("Position not found in file"), 0);
9705                 return FALSE;
9706             }
9707             if (fenMode || line[0] == '#') pn--;
9708         }
9709     }
9710
9711     if (fenMode) {
9712         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9713             DisplayError(_("Bad FEN position in file"), 0);
9714             return FALSE;
9715         }
9716     } else {
9717         (void) fgets(line, MSG_SIZ, f);
9718         (void) fgets(line, MSG_SIZ, f);
9719
9720         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9721             (void) fgets(line, MSG_SIZ, f);
9722             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9723                 if (*p == ' ')
9724                   continue;
9725                 initial_position[i][j++] = CharToPiece(*p);
9726             }
9727         }
9728
9729         blackPlaysFirst = FALSE;
9730         if (!feof(f)) {
9731             (void) fgets(line, MSG_SIZ, f);
9732             if (strncmp(line, "black", strlen("black"))==0)
9733               blackPlaysFirst = TRUE;
9734         }
9735     }
9736     startedFromSetupPosition = TRUE;
9737
9738     SendToProgram("force\n", &first);
9739     CopyBoard(boards[0], initial_position);
9740     if (blackPlaysFirst) {
9741         currentMove = forwardMostMove = backwardMostMove = 1;
9742         strcpy(moveList[0], "");
9743         strcpy(parseList[0], "");
9744         CopyBoard(boards[1], initial_position);
9745         DisplayMessage("", _("Black to play"));
9746     } else {
9747         currentMove = forwardMostMove = backwardMostMove = 0;
9748         DisplayMessage("", _("White to play"));
9749     }
9750           /* [HGM] copy FEN attributes as well */
9751           {   int i;
9752               initialRulePlies = FENrulePlies;
9753               epStatus[forwardMostMove] = FENepStatus;
9754               for( i=0; i< nrCastlingRights; i++ )
9755                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9756           }
9757     SendBoard(&first, forwardMostMove);
9758     if (appData.debugMode) {
9759 int i, j;
9760   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9761   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9762         fprintf(debugFP, "Load Position\n");
9763     }
9764
9765     if (positionNumber > 1) {
9766         sprintf(line, "%s %d", title, positionNumber);
9767         DisplayTitle(line);
9768     } else {
9769         DisplayTitle(title);
9770     }
9771     gameMode = EditGame;
9772     ModeHighlight();
9773     ResetClocks();
9774     timeRemaining[0][1] = whiteTimeRemaining;
9775     timeRemaining[1][1] = blackTimeRemaining;
9776     DrawPosition(FALSE, boards[currentMove]);
9777
9778     return TRUE;
9779 }
9780
9781
9782 void
9783 CopyPlayerNameIntoFileName(dest, src)
9784      char **dest, *src;
9785 {
9786     while (*src != NULLCHAR && *src != ',') {
9787         if (*src == ' ') {
9788             *(*dest)++ = '_';
9789             src++;
9790         } else {
9791             *(*dest)++ = *src++;
9792         }
9793     }
9794 }
9795
9796 char *DefaultFileName(ext)
9797      char *ext;
9798 {
9799     static char def[MSG_SIZ];
9800     char *p;
9801
9802     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9803         p = def;
9804         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9805         *p++ = '-';
9806         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9807         *p++ = '.';
9808         strcpy(p, ext);
9809     } else {
9810         def[0] = NULLCHAR;
9811     }
9812     return def;
9813 }
9814
9815 /* Save the current game to the given file */
9816 int
9817 SaveGameToFile(filename, append)
9818      char *filename;
9819      int append;
9820 {
9821     FILE *f;
9822     char buf[MSG_SIZ];
9823
9824     if (strcmp(filename, "-") == 0) {
9825         return SaveGame(stdout, 0, NULL);
9826     } else {
9827         f = fopen(filename, append ? "a" : "w");
9828         if (f == NULL) {
9829             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9830             DisplayError(buf, errno);
9831             return FALSE;
9832         } else {
9833             return SaveGame(f, 0, NULL);
9834         }
9835     }
9836 }
9837
9838 char *
9839 SavePart(str)
9840      char *str;
9841 {
9842     static char buf[MSG_SIZ];
9843     char *p;
9844
9845     p = strchr(str, ' ');
9846     if (p == NULL) return str;
9847     strncpy(buf, str, p - str);
9848     buf[p - str] = NULLCHAR;
9849     return buf;
9850 }
9851
9852 #define PGN_MAX_LINE 75
9853
9854 #define PGN_SIDE_WHITE  0
9855 #define PGN_SIDE_BLACK  1
9856
9857 /* [AS] */
9858 static int FindFirstMoveOutOfBook( int side )
9859 {
9860     int result = -1;
9861
9862     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9863         int index = backwardMostMove;
9864         int has_book_hit = 0;
9865
9866         if( (index % 2) != side ) {
9867             index++;
9868         }
9869
9870         while( index < forwardMostMove ) {
9871             /* Check to see if engine is in book */
9872             int depth = pvInfoList[index].depth;
9873             int score = pvInfoList[index].score;
9874             int in_book = 0;
9875
9876             if( depth <= 2 ) {
9877                 in_book = 1;
9878             }
9879             else if( score == 0 && depth == 63 ) {
9880                 in_book = 1; /* Zappa */
9881             }
9882             else if( score == 2 && depth == 99 ) {
9883                 in_book = 1; /* Abrok */
9884             }
9885
9886             has_book_hit += in_book;
9887
9888             if( ! in_book ) {
9889                 result = index;
9890
9891                 break;
9892             }
9893
9894             index += 2;
9895         }
9896     }
9897
9898     return result;
9899 }
9900
9901 /* [AS] */
9902 void GetOutOfBookInfo( char * buf )
9903 {
9904     int oob[2];
9905     int i;
9906     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9907
9908     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9909     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9910
9911     *buf = '\0';
9912
9913     if( oob[0] >= 0 || oob[1] >= 0 ) {
9914         for( i=0; i<2; i++ ) {
9915             int idx = oob[i];
9916
9917             if( idx >= 0 ) {
9918                 if( i > 0 && oob[0] >= 0 ) {
9919                     strcat( buf, "   " );
9920                 }
9921
9922                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9923                 sprintf( buf+strlen(buf), "%s%.2f",
9924                     pvInfoList[idx].score >= 0 ? "+" : "",
9925                     pvInfoList[idx].score / 100.0 );
9926             }
9927         }
9928     }
9929 }
9930
9931 /* Save game in PGN style and close the file */
9932 int
9933 SaveGamePGN(f)
9934      FILE *f;
9935 {
9936     int i, offset, linelen, newblock;
9937     time_t tm;
9938 //    char *movetext;
9939     char numtext[32];
9940     int movelen, numlen, blank;
9941     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9942
9943     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9944
9945     tm = time((time_t *) NULL);
9946
9947     PrintPGNTags(f, &gameInfo);
9948
9949     if (backwardMostMove > 0 || startedFromSetupPosition) {
9950         char *fen = PositionToFEN(backwardMostMove, NULL);
9951         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9952         fprintf(f, "\n{--------------\n");
9953         PrintPosition(f, backwardMostMove);
9954         fprintf(f, "--------------}\n");
9955         free(fen);
9956     }
9957     else {
9958         /* [AS] Out of book annotation */
9959         if( appData.saveOutOfBookInfo ) {
9960             char buf[64];
9961
9962             GetOutOfBookInfo( buf );
9963
9964             if( buf[0] != '\0' ) {
9965                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9966             }
9967         }
9968
9969         fprintf(f, "\n");
9970     }
9971
9972     i = backwardMostMove;
9973     linelen = 0;
9974     newblock = TRUE;
9975
9976     while (i < forwardMostMove) {
9977         /* Print comments preceding this move */
9978         if (commentList[i] != NULL) {
9979             if (linelen > 0) fprintf(f, "\n");
9980             fprintf(f, "{\n%s}\n", commentList[i]);
9981             linelen = 0;
9982             newblock = TRUE;
9983         }
9984
9985         /* Format move number */
9986         if ((i % 2) == 0) {
9987             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9988         } else {
9989             if (newblock) {
9990                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9991             } else {
9992                 numtext[0] = NULLCHAR;
9993             }
9994         }
9995         numlen = strlen(numtext);
9996         newblock = FALSE;
9997
9998         /* Print move number */
9999         blank = linelen > 0 && numlen > 0;
10000         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10001             fprintf(f, "\n");
10002             linelen = 0;
10003             blank = 0;
10004         }
10005         if (blank) {
10006             fprintf(f, " ");
10007             linelen++;
10008         }
10009         fprintf(f, "%s", numtext);
10010         linelen += numlen;
10011
10012         /* Get move */
10013         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10014         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10015
10016         /* Print move */
10017         blank = linelen > 0 && movelen > 0;
10018         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10019             fprintf(f, "\n");
10020             linelen = 0;
10021             blank = 0;
10022         }
10023         if (blank) {
10024             fprintf(f, " ");
10025             linelen++;
10026         }
10027         fprintf(f, "%s", move_buffer);
10028         linelen += movelen;
10029
10030         /* [AS] Add PV info if present */
10031         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10032             /* [HGM] add time */
10033             char buf[MSG_SIZ]; int seconds = 0;
10034
10035             if(i >= backwardMostMove) {
10036                 if(WhiteOnMove(i))
10037                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
10038                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
10039                 else
10040                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
10041                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
10042             }
10043             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
10044
10045             if( seconds <= 0) buf[0] = 0; else
10046             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10047                 seconds = (seconds + 4)/10; // round to full seconds
10048                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10049                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10050             }
10051
10052             sprintf( move_buffer, "{%s%.2f/%d%s}",
10053                 pvInfoList[i].score >= 0 ? "+" : "",
10054                 pvInfoList[i].score / 100.0,
10055                 pvInfoList[i].depth,
10056                 buf );
10057
10058             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10059
10060             /* Print score/depth */
10061             blank = linelen > 0 && movelen > 0;
10062             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10063                 fprintf(f, "\n");
10064                 linelen = 0;
10065                 blank = 0;
10066             }
10067             if (blank) {
10068                 fprintf(f, " ");
10069                 linelen++;
10070             }
10071             fprintf(f, "%s", move_buffer);
10072             linelen += movelen;
10073         }
10074
10075         i++;
10076     }
10077
10078     /* Start a new line */
10079     if (linelen > 0) fprintf(f, "\n");
10080
10081     /* Print comments after last move */
10082     if (commentList[i] != NULL) {
10083         fprintf(f, "{\n%s}\n", commentList[i]);
10084     }
10085
10086     /* Print result */
10087     if (gameInfo.resultDetails != NULL &&
10088         gameInfo.resultDetails[0] != NULLCHAR) {
10089         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10090                 PGNResult(gameInfo.result));
10091     } else {
10092         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10093     }
10094
10095     fclose(f);
10096     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10097     return TRUE;
10098 }
10099
10100 /* Save game in old style and close the file */
10101 int
10102 SaveGameOldStyle(f)
10103      FILE *f;
10104 {
10105     int i, offset;
10106     time_t tm;
10107
10108     tm = time((time_t *) NULL);
10109
10110     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10111     PrintOpponents(f);
10112
10113     if (backwardMostMove > 0 || startedFromSetupPosition) {
10114         fprintf(f, "\n[--------------\n");
10115         PrintPosition(f, backwardMostMove);
10116         fprintf(f, "--------------]\n");
10117     } else {
10118         fprintf(f, "\n");
10119     }
10120
10121     i = backwardMostMove;
10122     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10123
10124     while (i < forwardMostMove) {
10125         if (commentList[i] != NULL) {
10126             fprintf(f, "[%s]\n", commentList[i]);
10127         }
10128
10129         if ((i % 2) == 1) {
10130             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10131             i++;
10132         } else {
10133             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10134             i++;
10135             if (commentList[i] != NULL) {
10136                 fprintf(f, "\n");
10137                 continue;
10138             }
10139             if (i >= forwardMostMove) {
10140                 fprintf(f, "\n");
10141                 break;
10142             }
10143             fprintf(f, "%s\n", parseList[i]);
10144             i++;
10145         }
10146     }
10147
10148     if (commentList[i] != NULL) {
10149         fprintf(f, "[%s]\n", commentList[i]);
10150     }
10151
10152     /* This isn't really the old style, but it's close enough */
10153     if (gameInfo.resultDetails != NULL &&
10154         gameInfo.resultDetails[0] != NULLCHAR) {
10155         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10156                 gameInfo.resultDetails);
10157     } else {
10158         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10159     }
10160
10161     fclose(f);
10162     return TRUE;
10163 }
10164
10165 /* Save the current game to open file f and close the file */
10166 int
10167 SaveGame(f, dummy, dummy2)
10168      FILE *f;
10169      int dummy;
10170      char *dummy2;
10171 {
10172     if (gameMode == EditPosition) EditPositionDone();
10173     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10174     if (appData.oldSaveStyle)
10175       return SaveGameOldStyle(f);
10176     else
10177       return SaveGamePGN(f);
10178 }
10179
10180 /* Save the current position to the given file */
10181 int
10182 SavePositionToFile(filename)
10183      char *filename;
10184 {
10185     FILE *f;
10186     char buf[MSG_SIZ];
10187
10188     if (strcmp(filename, "-") == 0) {
10189         return SavePosition(stdout, 0, NULL);
10190     } else {
10191         f = fopen(filename, "a");
10192         if (f == NULL) {
10193             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10194             DisplayError(buf, errno);
10195             return FALSE;
10196         } else {
10197             SavePosition(f, 0, NULL);
10198             return TRUE;
10199         }
10200     }
10201 }
10202
10203 /* Save the current position to the given open file and close the file */
10204 int
10205 SavePosition(f, dummy, dummy2)
10206      FILE *f;
10207      int dummy;
10208      char *dummy2;
10209 {
10210     time_t tm;
10211     char *fen;
10212
10213     if (appData.oldSaveStyle) {
10214         tm = time((time_t *) NULL);
10215
10216         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10217         PrintOpponents(f);
10218         fprintf(f, "[--------------\n");
10219         PrintPosition(f, currentMove);
10220         fprintf(f, "--------------]\n");
10221     } else {
10222         fen = PositionToFEN(currentMove, NULL);
10223         fprintf(f, "%s\n", fen);
10224         free(fen);
10225     }
10226     fclose(f);
10227     return TRUE;
10228 }
10229
10230 void
10231 ReloadCmailMsgEvent(unregister)
10232      int unregister;
10233 {
10234 #if !WIN32
10235     static char *inFilename = NULL;
10236     static char *outFilename;
10237     int i;
10238     struct stat inbuf, outbuf;
10239     int status;
10240
10241     /* Any registered moves are unregistered if unregister is set, */
10242     /* i.e. invoked by the signal handler */
10243     if (unregister) {
10244         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10245             cmailMoveRegistered[i] = FALSE;
10246             if (cmailCommentList[i] != NULL) {
10247                 free(cmailCommentList[i]);
10248                 cmailCommentList[i] = NULL;
10249             }
10250         }
10251         nCmailMovesRegistered = 0;
10252     }
10253
10254     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10255         cmailResult[i] = CMAIL_NOT_RESULT;
10256     }
10257     nCmailResults = 0;
10258
10259     if (inFilename == NULL) {
10260         /* Because the filenames are static they only get malloced once  */
10261         /* and they never get freed                                      */
10262         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10263         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10264
10265         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10266         sprintf(outFilename, "%s.out", appData.cmailGameName);
10267     }
10268
10269     status = stat(outFilename, &outbuf);
10270     if (status < 0) {
10271         cmailMailedMove = FALSE;
10272     } else {
10273         status = stat(inFilename, &inbuf);
10274         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10275     }
10276
10277     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10278        counts the games, notes how each one terminated, etc.
10279
10280        It would be nice to remove this kludge and instead gather all
10281        the information while building the game list.  (And to keep it
10282        in the game list nodes instead of having a bunch of fixed-size
10283        parallel arrays.)  Note this will require getting each game's
10284        termination from the PGN tags, as the game list builder does
10285        not process the game moves.  --mann
10286        */
10287     cmailMsgLoaded = TRUE;
10288     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10289
10290     /* Load first game in the file or popup game menu */
10291     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10292
10293 #endif /* !WIN32 */
10294     return;
10295 }
10296
10297 int
10298 RegisterMove()
10299 {
10300     FILE *f;
10301     char string[MSG_SIZ];
10302
10303     if (   cmailMailedMove
10304         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10305         return TRUE;            /* Allow free viewing  */
10306     }
10307
10308     /* Unregister move to ensure that we don't leave RegisterMove        */
10309     /* with the move registered when the conditions for registering no   */
10310     /* longer hold                                                       */
10311     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10312         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10313         nCmailMovesRegistered --;
10314
10315         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10316           {
10317               free(cmailCommentList[lastLoadGameNumber - 1]);
10318               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10319           }
10320     }
10321
10322     if (cmailOldMove == -1) {
10323         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10324         return FALSE;
10325     }
10326
10327     if (currentMove > cmailOldMove + 1) {
10328         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10329         return FALSE;
10330     }
10331
10332     if (currentMove < cmailOldMove) {
10333         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10334         return FALSE;
10335     }
10336
10337     if (forwardMostMove > currentMove) {
10338         /* Silently truncate extra moves */
10339         TruncateGame();
10340     }
10341
10342     if (   (currentMove == cmailOldMove + 1)
10343         || (   (currentMove == cmailOldMove)
10344             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10345                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10346         if (gameInfo.result != GameUnfinished) {
10347             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10348         }
10349
10350         if (commentList[currentMove] != NULL) {
10351             cmailCommentList[lastLoadGameNumber - 1]
10352               = StrSave(commentList[currentMove]);
10353         }
10354         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10355
10356         if (appData.debugMode)
10357           fprintf(debugFP, "Saving %s for game %d\n",
10358                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10359
10360         sprintf(string,
10361                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10362
10363         f = fopen(string, "w");
10364         if (appData.oldSaveStyle) {
10365             SaveGameOldStyle(f); /* also closes the file */
10366
10367             sprintf(string, "%s.pos.out", appData.cmailGameName);
10368             f = fopen(string, "w");
10369             SavePosition(f, 0, NULL); /* also closes the file */
10370         } else {
10371             fprintf(f, "{--------------\n");
10372             PrintPosition(f, currentMove);
10373             fprintf(f, "--------------}\n\n");
10374
10375             SaveGame(f, 0, NULL); /* also closes the file*/
10376         }
10377
10378         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10379         nCmailMovesRegistered ++;
10380     } else if (nCmailGames == 1) {
10381         DisplayError(_("You have not made a move yet"), 0);
10382         return FALSE;
10383     }
10384
10385     return TRUE;
10386 }
10387
10388 void
10389 MailMoveEvent()
10390 {
10391 #if !WIN32
10392     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10393     FILE *commandOutput;
10394     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10395     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10396     int nBuffers;
10397     int i;
10398     int archived;
10399     char *arcDir;
10400
10401     if (! cmailMsgLoaded) {
10402         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10403         return;
10404     }
10405
10406     if (nCmailGames == nCmailResults) {
10407         DisplayError(_("No unfinished games"), 0);
10408         return;
10409     }
10410
10411 #if CMAIL_PROHIBIT_REMAIL
10412     if (cmailMailedMove) {
10413         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);
10414         DisplayError(msg, 0);
10415         return;
10416     }
10417 #endif
10418
10419     if (! (cmailMailedMove || RegisterMove())) return;
10420
10421     if (   cmailMailedMove
10422         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10423         sprintf(string, partCommandString,
10424                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10425         commandOutput = popen(string, "r");
10426
10427         if (commandOutput == NULL) {
10428             DisplayError(_("Failed to invoke cmail"), 0);
10429         } else {
10430             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10431                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10432             }
10433             if (nBuffers > 1) {
10434                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10435                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10436                 nBytes = MSG_SIZ - 1;
10437             } else {
10438                 (void) memcpy(msg, buffer, nBytes);
10439             }
10440             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10441
10442             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10443                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10444
10445                 archived = TRUE;
10446                 for (i = 0; i < nCmailGames; i ++) {
10447                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10448                         archived = FALSE;
10449                     }
10450                 }
10451                 if (   archived
10452                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10453                         != NULL)) {
10454                     sprintf(buffer, "%s/%s.%s.archive",
10455                             arcDir,
10456                             appData.cmailGameName,
10457                             gameInfo.date);
10458                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10459                     cmailMsgLoaded = FALSE;
10460                 }
10461             }
10462
10463             DisplayInformation(msg);
10464             pclose(commandOutput);
10465         }
10466     } else {
10467         if ((*cmailMsg) != '\0') {
10468             DisplayInformation(cmailMsg);
10469         }
10470     }
10471
10472     return;
10473 #endif /* !WIN32 */
10474 }
10475
10476 char *
10477 CmailMsg()
10478 {
10479 #if WIN32
10480     return NULL;
10481 #else
10482     int  prependComma = 0;
10483     char number[5];
10484     char string[MSG_SIZ];       /* Space for game-list */
10485     int  i;
10486
10487     if (!cmailMsgLoaded) return "";
10488
10489     if (cmailMailedMove) {
10490         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10491     } else {
10492         /* Create a list of games left */
10493         sprintf(string, "[");
10494         for (i = 0; i < nCmailGames; i ++) {
10495             if (! (   cmailMoveRegistered[i]
10496                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10497                 if (prependComma) {
10498                     sprintf(number, ",%d", i + 1);
10499                 } else {
10500                     sprintf(number, "%d", i + 1);
10501                     prependComma = 1;
10502                 }
10503
10504                 strcat(string, number);
10505             }
10506         }
10507         strcat(string, "]");
10508
10509         if (nCmailMovesRegistered + nCmailResults == 0) {
10510             switch (nCmailGames) {
10511               case 1:
10512                 sprintf(cmailMsg,
10513                         _("Still need to make move for game\n"));
10514                 break;
10515
10516               case 2:
10517                 sprintf(cmailMsg,
10518                         _("Still need to make moves for both games\n"));
10519                 break;
10520
10521               default:
10522                 sprintf(cmailMsg,
10523                         _("Still need to make moves for all %d games\n"),
10524                         nCmailGames);
10525                 break;
10526             }
10527         } else {
10528             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10529               case 1:
10530                 sprintf(cmailMsg,
10531                         _("Still need to make a move for game %s\n"),
10532                         string);
10533                 break;
10534
10535               case 0:
10536                 if (nCmailResults == nCmailGames) {
10537                     sprintf(cmailMsg, _("No unfinished games\n"));
10538                 } else {
10539                     sprintf(cmailMsg, _("Ready to send mail\n"));
10540                 }
10541                 break;
10542
10543               default:
10544                 sprintf(cmailMsg,
10545                         _("Still need to make moves for games %s\n"),
10546                         string);
10547             }
10548         }
10549     }
10550     return cmailMsg;
10551 #endif /* WIN32 */
10552 }
10553
10554 void
10555 ResetGameEvent()
10556 {
10557     if (gameMode == Training)
10558       SetTrainingModeOff();
10559
10560     Reset(TRUE, TRUE);
10561     cmailMsgLoaded = FALSE;
10562     if (appData.icsActive) {
10563       SendToICS(ics_prefix);
10564       SendToICS("refresh\n");
10565     }
10566 }
10567
10568 void
10569 ExitEvent(status)
10570      int status;
10571 {
10572     exiting++;
10573     if (exiting > 2) {
10574       /* Give up on clean exit */
10575       exit(status);
10576     }
10577     if (exiting > 1) {
10578       /* Keep trying for clean exit */
10579       return;
10580     }
10581
10582     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10583
10584     if (telnetISR != NULL) {
10585       RemoveInputSource(telnetISR);
10586     }
10587     if (icsPR != NoProc) {
10588       DestroyChildProcess(icsPR, TRUE);
10589     }
10590
10591     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10592     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10593
10594     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10595     /* make sure this other one finishes before killing it!                  */
10596     if(endingGame) { int count = 0;
10597         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10598         while(endingGame && count++ < 10) DoSleep(1);
10599         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10600     }
10601
10602     /* Kill off chess programs */
10603     if (first.pr != NoProc) {
10604         ExitAnalyzeMode();
10605
10606         DoSleep( appData.delayBeforeQuit );
10607         SendToProgram("quit\n", &first);
10608         DoSleep( appData.delayAfterQuit );
10609         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10610     }
10611     if (second.pr != NoProc) {
10612         DoSleep( appData.delayBeforeQuit );
10613         SendToProgram("quit\n", &second);
10614         DoSleep( appData.delayAfterQuit );
10615         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10616     }
10617     if (first.isr != NULL) {
10618         RemoveInputSource(first.isr);
10619     }
10620     if (second.isr != NULL) {
10621         RemoveInputSource(second.isr);
10622     }
10623
10624     ShutDownFrontEnd();
10625     exit(status);
10626 }
10627
10628 void
10629 PauseEvent()
10630 {
10631     if (appData.debugMode)
10632         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10633     if (pausing) {
10634         pausing = FALSE;
10635         ModeHighlight();
10636         if (gameMode == MachinePlaysWhite ||
10637             gameMode == MachinePlaysBlack) {
10638             StartClocks();
10639         } else {
10640             DisplayBothClocks();
10641         }
10642         if (gameMode == PlayFromGameFile) {
10643             if (appData.timeDelay >= 0)
10644                 AutoPlayGameLoop();
10645         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10646             Reset(FALSE, TRUE);
10647             SendToICS(ics_prefix);
10648             SendToICS("refresh\n");
10649         } else if (currentMove < forwardMostMove) {
10650             ForwardInner(forwardMostMove);
10651         }
10652         pauseExamInvalid = FALSE;
10653     } else {
10654         switch (gameMode) {
10655           default:
10656             return;
10657           case IcsExamining:
10658             pauseExamForwardMostMove = forwardMostMove;
10659             pauseExamInvalid = FALSE;
10660             /* fall through */
10661           case IcsObserving:
10662           case IcsPlayingWhite:
10663           case IcsPlayingBlack:
10664             pausing = TRUE;
10665             ModeHighlight();
10666             return;
10667           case PlayFromGameFile:
10668             (void) StopLoadGameTimer();
10669             pausing = TRUE;
10670             ModeHighlight();
10671             break;
10672           case BeginningOfGame:
10673             if (appData.icsActive) return;
10674             /* else fall through */
10675           case MachinePlaysWhite:
10676           case MachinePlaysBlack:
10677           case TwoMachinesPlay:
10678             if (forwardMostMove == 0)
10679               return;           /* don't pause if no one has moved */
10680             if ((gameMode == MachinePlaysWhite &&
10681                  !WhiteOnMove(forwardMostMove)) ||
10682                 (gameMode == MachinePlaysBlack &&
10683                  WhiteOnMove(forwardMostMove))) {
10684                 StopClocks();
10685             }
10686             pausing = TRUE;
10687             ModeHighlight();
10688             break;
10689         }
10690     }
10691 }
10692
10693 void
10694 EditCommentEvent()
10695 {
10696     char title[MSG_SIZ];
10697
10698     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10699         strcpy(title, _("Edit comment"));
10700     } else {
10701         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10702                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10703                 parseList[currentMove - 1]);
10704     }
10705
10706     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10707 }
10708
10709
10710 void
10711 EditTagsEvent()
10712 {
10713     char *tags = PGNTags(&gameInfo);
10714     EditTagsPopUp(tags);
10715     free(tags);
10716 }
10717
10718 void
10719 AnalyzeModeEvent()
10720 {
10721     if (appData.noChessProgram || gameMode == AnalyzeMode)
10722       return;
10723
10724     if (gameMode != AnalyzeFile) {
10725         if (!appData.icsEngineAnalyze) {
10726                EditGameEvent();
10727                if (gameMode != EditGame) return;
10728         }
10729         ResurrectChessProgram();
10730         SendToProgram("analyze\n", &first);
10731         first.analyzing = TRUE;
10732         /*first.maybeThinking = TRUE;*/
10733         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10734         EngineOutputPopUp();
10735     }
10736     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10737     pausing = FALSE;
10738     ModeHighlight();
10739     SetGameInfo();
10740
10741     StartAnalysisClock();
10742     GetTimeMark(&lastNodeCountTime);
10743     lastNodeCount = 0;
10744 }
10745
10746 void
10747 AnalyzeFileEvent()
10748 {
10749     if (appData.noChessProgram || gameMode == AnalyzeFile)
10750       return;
10751
10752     if (gameMode != AnalyzeMode) {
10753         EditGameEvent();
10754         if (gameMode != EditGame) return;
10755         ResurrectChessProgram();
10756         SendToProgram("analyze\n", &first);
10757         first.analyzing = TRUE;
10758         /*first.maybeThinking = TRUE;*/
10759         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10760         EngineOutputPopUp();
10761     }
10762     gameMode = AnalyzeFile;
10763     pausing = FALSE;
10764     ModeHighlight();
10765     SetGameInfo();
10766
10767     StartAnalysisClock();
10768     GetTimeMark(&lastNodeCountTime);
10769     lastNodeCount = 0;
10770 }
10771
10772 void
10773 MachineWhiteEvent()
10774 {
10775     char buf[MSG_SIZ];
10776     char *bookHit = NULL;
10777
10778     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10779       return;
10780
10781
10782     if (gameMode == PlayFromGameFile ||
10783         gameMode == TwoMachinesPlay  ||
10784         gameMode == Training         ||
10785         gameMode == AnalyzeMode      ||
10786         gameMode == EndOfGame)
10787         EditGameEvent();
10788
10789     if (gameMode == EditPosition)
10790         EditPositionDone();
10791
10792     if (!WhiteOnMove(currentMove)) {
10793         DisplayError(_("It is not White's turn"), 0);
10794         return;
10795     }
10796
10797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10798       ExitAnalyzeMode();
10799
10800     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10801         gameMode == AnalyzeFile)
10802         TruncateGame();
10803
10804     ResurrectChessProgram();    /* in case it isn't running */
10805     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10806         gameMode = MachinePlaysWhite;
10807         ResetClocks();
10808     } else
10809     gameMode = MachinePlaysWhite;
10810     pausing = FALSE;
10811     ModeHighlight();
10812     SetGameInfo();
10813     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10814     DisplayTitle(buf);
10815     if (first.sendName) {
10816       sprintf(buf, "name %s\n", gameInfo.black);
10817       SendToProgram(buf, &first);
10818     }
10819     if (first.sendTime) {
10820       if (first.useColors) {
10821         SendToProgram("black\n", &first); /*gnu kludge*/
10822       }
10823       SendTimeRemaining(&first, TRUE);
10824     }
10825     if (first.useColors) {
10826       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10827     }
10828     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10829     SetMachineThinkingEnables();
10830     first.maybeThinking = TRUE;
10831     StartClocks();
10832     firstMove = FALSE;
10833
10834     if (appData.autoFlipView && !flipView) {
10835       flipView = !flipView;
10836       DrawPosition(FALSE, NULL);
10837       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10838     }
10839
10840     if(bookHit) { // [HGM] book: simulate book reply
10841         static char bookMove[MSG_SIZ]; // a bit generous?
10842
10843         programStats.nodes = programStats.depth = programStats.time =
10844         programStats.score = programStats.got_only_move = 0;
10845         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10846
10847         strcpy(bookMove, "move ");
10848         strcat(bookMove, bookHit);
10849         HandleMachineMove(bookMove, &first);
10850     }
10851 }
10852
10853 void
10854 MachineBlackEvent()
10855 {
10856     char buf[MSG_SIZ];
10857    char *bookHit = NULL;
10858
10859     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10860         return;
10861
10862
10863     if (gameMode == PlayFromGameFile ||
10864         gameMode == TwoMachinesPlay  ||
10865         gameMode == Training         ||
10866         gameMode == AnalyzeMode      ||
10867         gameMode == EndOfGame)
10868         EditGameEvent();
10869
10870     if (gameMode == EditPosition)
10871         EditPositionDone();
10872
10873     if (WhiteOnMove(currentMove)) {
10874         DisplayError(_("It is not Black's turn"), 0);
10875         return;
10876     }
10877
10878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10879       ExitAnalyzeMode();
10880
10881     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10882         gameMode == AnalyzeFile)
10883         TruncateGame();
10884
10885     ResurrectChessProgram();    /* in case it isn't running */
10886     gameMode = MachinePlaysBlack;
10887     pausing = FALSE;
10888     ModeHighlight();
10889     SetGameInfo();
10890     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10891     DisplayTitle(buf);
10892     if (first.sendName) {
10893       sprintf(buf, "name %s\n", gameInfo.white);
10894       SendToProgram(buf, &first);
10895     }
10896     if (first.sendTime) {
10897       if (first.useColors) {
10898         SendToProgram("white\n", &first); /*gnu kludge*/
10899       }
10900       SendTimeRemaining(&first, FALSE);
10901     }
10902     if (first.useColors) {
10903       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10904     }
10905     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10906     SetMachineThinkingEnables();
10907     first.maybeThinking = TRUE;
10908     StartClocks();
10909
10910     if (appData.autoFlipView && flipView) {
10911       flipView = !flipView;
10912       DrawPosition(FALSE, NULL);
10913       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10914     }
10915     if(bookHit) { // [HGM] book: simulate book reply
10916         static char bookMove[MSG_SIZ]; // a bit generous?
10917
10918         programStats.nodes = programStats.depth = programStats.time =
10919         programStats.score = programStats.got_only_move = 0;
10920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10921
10922         strcpy(bookMove, "move ");
10923         strcat(bookMove, bookHit);
10924         HandleMachineMove(bookMove, &first);
10925     }
10926 }
10927
10928
10929 void
10930 DisplayTwoMachinesTitle()
10931 {
10932     char buf[MSG_SIZ];
10933     if (appData.matchGames > 0) {
10934         if (first.twoMachinesColor[0] == 'w') {
10935             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10936                     gameInfo.white, gameInfo.black,
10937                     first.matchWins, second.matchWins,
10938                     matchGame - 1 - (first.matchWins + second.matchWins));
10939         } else {
10940             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10941                     gameInfo.white, gameInfo.black,
10942                     second.matchWins, first.matchWins,
10943                     matchGame - 1 - (first.matchWins + second.matchWins));
10944         }
10945     } else {
10946         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10947     }
10948     DisplayTitle(buf);
10949 }
10950
10951 void
10952 TwoMachinesEvent P((void))
10953 {
10954     int i;
10955     char buf[MSG_SIZ];
10956     ChessProgramState *onmove;
10957     char *bookHit = NULL;
10958
10959     if (appData.noChessProgram) return;
10960
10961     switch (gameMode) {
10962       case TwoMachinesPlay:
10963         return;
10964       case MachinePlaysWhite:
10965       case MachinePlaysBlack:
10966         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10967             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10968             return;
10969         }
10970         /* fall through */
10971       case BeginningOfGame:
10972       case PlayFromGameFile:
10973       case EndOfGame:
10974         EditGameEvent();
10975         if (gameMode != EditGame) return;
10976         break;
10977       case EditPosition:
10978         EditPositionDone();
10979         break;
10980       case AnalyzeMode:
10981       case AnalyzeFile:
10982         ExitAnalyzeMode();
10983         break;
10984       case EditGame:
10985       default:
10986         break;
10987     }
10988
10989     forwardMostMove = currentMove;
10990     ResurrectChessProgram();    /* in case first program isn't running */
10991
10992     if (second.pr == NULL) {
10993         StartChessProgram(&second);
10994         if (second.protocolVersion == 1) {
10995           TwoMachinesEventIfReady();
10996         } else {
10997           /* kludge: allow timeout for initial "feature" command */
10998           FreezeUI();
10999           DisplayMessage("", _("Starting second chess program"));
11000           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11001         }
11002         return;
11003     }
11004     DisplayMessage("", "");
11005     InitChessProgram(&second, FALSE);
11006     SendToProgram("force\n", &second);
11007     if (startedFromSetupPosition) {
11008         SendBoard(&second, backwardMostMove);
11009     if (appData.debugMode) {
11010         fprintf(debugFP, "Two Machines\n");
11011     }
11012     }
11013     for (i = backwardMostMove; i < forwardMostMove; i++) {
11014         SendMoveToProgram(i, &second);
11015     }
11016
11017     gameMode = TwoMachinesPlay;
11018     pausing = FALSE;
11019     ModeHighlight();
11020     SetGameInfo();
11021     DisplayTwoMachinesTitle();
11022     firstMove = TRUE;
11023     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11024         onmove = &first;
11025     } else {
11026         onmove = &second;
11027     }
11028
11029     SendToProgram(first.computerString, &first);
11030     if (first.sendName) {
11031       sprintf(buf, "name %s\n", second.tidy);
11032       SendToProgram(buf, &first);
11033     }
11034     SendToProgram(second.computerString, &second);
11035     if (second.sendName) {
11036       sprintf(buf, "name %s\n", first.tidy);
11037       SendToProgram(buf, &second);
11038     }
11039
11040     ResetClocks();
11041     if (!first.sendTime || !second.sendTime) {
11042         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11043         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11044     }
11045     if (onmove->sendTime) {
11046       if (onmove->useColors) {
11047         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11048       }
11049       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11050     }
11051     if (onmove->useColors) {
11052       SendToProgram(onmove->twoMachinesColor, onmove);
11053     }
11054     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11055 //    SendToProgram("go\n", onmove);
11056     onmove->maybeThinking = TRUE;
11057     SetMachineThinkingEnables();
11058
11059     StartClocks();
11060
11061     if(bookHit) { // [HGM] book: simulate book reply
11062         static char bookMove[MSG_SIZ]; // a bit generous?
11063
11064         programStats.nodes = programStats.depth = programStats.time =
11065         programStats.score = programStats.got_only_move = 0;
11066         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11067
11068         strcpy(bookMove, "move ");
11069         strcat(bookMove, bookHit);
11070         savedMessage = bookMove; // args for deferred call
11071         savedState = onmove;
11072         ScheduleDelayedEvent(DeferredBookMove, 1);
11073     }
11074 }
11075
11076 void
11077 TrainingEvent()
11078 {
11079     if (gameMode == Training) {
11080       SetTrainingModeOff();
11081       gameMode = PlayFromGameFile;
11082       DisplayMessage("", _("Training mode off"));
11083     } else {
11084       gameMode = Training;
11085       animateTraining = appData.animate;
11086
11087       /* make sure we are not already at the end of the game */
11088       if (currentMove < forwardMostMove) {
11089         SetTrainingModeOn();
11090         DisplayMessage("", _("Training mode on"));
11091       } else {
11092         gameMode = PlayFromGameFile;
11093         DisplayError(_("Already at end of game"), 0);
11094       }
11095     }
11096     ModeHighlight();
11097 }
11098
11099 void
11100 IcsClientEvent()
11101 {
11102     if (!appData.icsActive) return;
11103     switch (gameMode) {
11104       case IcsPlayingWhite:
11105       case IcsPlayingBlack:
11106       case IcsObserving:
11107       case IcsIdle:
11108       case BeginningOfGame:
11109       case IcsExamining:
11110         return;
11111
11112       case EditGame:
11113         break;
11114
11115       case EditPosition:
11116         EditPositionDone();
11117         break;
11118
11119       case AnalyzeMode:
11120       case AnalyzeFile:
11121         ExitAnalyzeMode();
11122         break;
11123
11124       default:
11125         EditGameEvent();
11126         break;
11127     }
11128
11129     gameMode = IcsIdle;
11130     ModeHighlight();
11131     return;
11132 }
11133
11134
11135 void
11136 EditGameEvent()
11137 {
11138     int i;
11139
11140     switch (gameMode) {
11141       case Training:
11142         SetTrainingModeOff();
11143         break;
11144       case MachinePlaysWhite:
11145       case MachinePlaysBlack:
11146       case BeginningOfGame:
11147         SendToProgram("force\n", &first);
11148         SetUserThinkingEnables();
11149         break;
11150       case PlayFromGameFile:
11151         (void) StopLoadGameTimer();
11152         if (gameFileFP != NULL) {
11153             gameFileFP = NULL;
11154         }
11155         break;
11156       case EditPosition:
11157         EditPositionDone();
11158         break;
11159       case AnalyzeMode:
11160       case AnalyzeFile:
11161         ExitAnalyzeMode();
11162         SendToProgram("force\n", &first);
11163         break;
11164       case TwoMachinesPlay:
11165         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11166         ResurrectChessProgram();
11167         SetUserThinkingEnables();
11168         break;
11169       case EndOfGame:
11170         ResurrectChessProgram();
11171         break;
11172       case IcsPlayingBlack:
11173       case IcsPlayingWhite:
11174         DisplayError(_("Warning: You are still playing a game"), 0);
11175         break;
11176       case IcsObserving:
11177         DisplayError(_("Warning: You are still observing a game"), 0);
11178         break;
11179       case IcsExamining:
11180         DisplayError(_("Warning: You are still examining a game"), 0);
11181         break;
11182       case IcsIdle:
11183         break;
11184       case EditGame:
11185       default:
11186         return;
11187     }
11188
11189     pausing = FALSE;
11190     StopClocks();
11191     first.offeredDraw = second.offeredDraw = 0;
11192
11193     if (gameMode == PlayFromGameFile) {
11194         whiteTimeRemaining = timeRemaining[0][currentMove];
11195         blackTimeRemaining = timeRemaining[1][currentMove];
11196         DisplayTitle("");
11197     }
11198
11199     if (gameMode == MachinePlaysWhite ||
11200         gameMode == MachinePlaysBlack ||
11201         gameMode == TwoMachinesPlay ||
11202         gameMode == EndOfGame) {
11203         i = forwardMostMove;
11204         while (i > currentMove) {
11205             SendToProgram("undo\n", &first);
11206             i--;
11207         }
11208         whiteTimeRemaining = timeRemaining[0][currentMove];
11209         blackTimeRemaining = timeRemaining[1][currentMove];
11210         DisplayBothClocks();
11211         if (whiteFlag || blackFlag) {
11212             whiteFlag = blackFlag = 0;
11213         }
11214         DisplayTitle("");
11215     }
11216
11217     gameMode = EditGame;
11218     ModeHighlight();
11219     SetGameInfo();
11220 }
11221
11222
11223 void
11224 EditPositionEvent()
11225 {
11226     if (gameMode == EditPosition) {
11227         EditGameEvent();
11228         return;
11229     }
11230
11231     EditGameEvent();
11232     if (gameMode != EditGame) return;
11233
11234     gameMode = EditPosition;
11235     ModeHighlight();
11236     SetGameInfo();
11237     if (currentMove > 0)
11238       CopyBoard(boards[0], boards[currentMove]);
11239
11240     blackPlaysFirst = !WhiteOnMove(currentMove);
11241     ResetClocks();
11242     currentMove = forwardMostMove = backwardMostMove = 0;
11243     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11244     DisplayMove(-1);
11245 }
11246
11247 void
11248 ExitAnalyzeMode()
11249 {
11250     /* [DM] icsEngineAnalyze - possible call from other functions */
11251     if (appData.icsEngineAnalyze) {
11252         appData.icsEngineAnalyze = FALSE;
11253
11254         DisplayMessage("",_("Close ICS engine analyze..."));
11255     }
11256     if (first.analysisSupport && first.analyzing) {
11257       SendToProgram("exit\n", &first);
11258       first.analyzing = FALSE;
11259     }
11260     thinkOutput[0] = NULLCHAR;
11261 }
11262
11263 void
11264 EditPositionDone()
11265 {
11266     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11267
11268     startedFromSetupPosition = TRUE;
11269     InitChessProgram(&first, FALSE);
11270     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11271     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11272         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11273         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11274     } else castlingRights[0][2] = -1;
11275     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11276         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11277         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11278     } else castlingRights[0][5] = -1;
11279     SendToProgram("force\n", &first);
11280     if (blackPlaysFirst) {
11281         strcpy(moveList[0], "");
11282         strcpy(parseList[0], "");
11283         currentMove = forwardMostMove = backwardMostMove = 1;
11284         CopyBoard(boards[1], boards[0]);
11285         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11286         { int i;
11287           epStatus[1] = epStatus[0];
11288           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11289         }
11290     } else {
11291         currentMove = forwardMostMove = backwardMostMove = 0;
11292     }
11293     SendBoard(&first, forwardMostMove);
11294     if (appData.debugMode) {
11295         fprintf(debugFP, "EditPosDone\n");
11296     }
11297     DisplayTitle("");
11298     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11299     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11300     gameMode = EditGame;
11301     ModeHighlight();
11302     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11303     ClearHighlights(); /* [AS] */
11304 }
11305
11306 /* Pause for `ms' milliseconds */
11307 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11308 void
11309 TimeDelay(ms)
11310      long ms;
11311 {
11312     TimeMark m1, m2;
11313
11314     GetTimeMark(&m1);
11315     do {
11316         GetTimeMark(&m2);
11317     } while (SubtractTimeMarks(&m2, &m1) < ms);
11318 }
11319
11320 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11321 void
11322 SendMultiLineToICS(buf)
11323      char *buf;
11324 {
11325     char temp[MSG_SIZ+1], *p;
11326     int len;
11327
11328     len = strlen(buf);
11329     if (len > MSG_SIZ)
11330       len = MSG_SIZ;
11331
11332     strncpy(temp, buf, len);
11333     temp[len] = 0;
11334
11335     p = temp;
11336     while (*p) {
11337         if (*p == '\n' || *p == '\r')
11338           *p = ' ';
11339         ++p;
11340     }
11341
11342     strcat(temp, "\n");
11343     SendToICS(temp);
11344     SendToPlayer(temp, strlen(temp));
11345 }
11346
11347 void
11348 SetWhiteToPlayEvent()
11349 {
11350     if (gameMode == EditPosition) {
11351         blackPlaysFirst = FALSE;
11352         DisplayBothClocks();    /* works because currentMove is 0 */
11353     } else if (gameMode == IcsExamining) {
11354         SendToICS(ics_prefix);
11355         SendToICS("tomove white\n");
11356     }
11357 }
11358
11359 void
11360 SetBlackToPlayEvent()
11361 {
11362     if (gameMode == EditPosition) {
11363         blackPlaysFirst = TRUE;
11364         currentMove = 1;        /* kludge */
11365         DisplayBothClocks();
11366         currentMove = 0;
11367     } else if (gameMode == IcsExamining) {
11368         SendToICS(ics_prefix);
11369         SendToICS("tomove black\n");
11370     }
11371 }
11372
11373 void
11374 EditPositionMenuEvent(selection, x, y)
11375      ChessSquare selection;
11376      int x, y;
11377 {
11378     char buf[MSG_SIZ];
11379     ChessSquare piece = boards[0][y][x];
11380
11381     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11382
11383     switch (selection) {
11384       case ClearBoard:
11385         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11386             SendToICS(ics_prefix);
11387             SendToICS("bsetup clear\n");
11388         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11389             SendToICS(ics_prefix);
11390             SendToICS("clearboard\n");
11391         } else {
11392             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11393                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11394                 for (y = 0; y < BOARD_HEIGHT; y++) {
11395                     if (gameMode == IcsExamining) {
11396                         if (boards[currentMove][y][x] != EmptySquare) {
11397                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11398                                     AAA + x, ONE + y);
11399                             SendToICS(buf);
11400                         }
11401                     } else {
11402                         boards[0][y][x] = p;
11403                     }
11404                 }
11405             }
11406         }
11407         if (gameMode == EditPosition) {
11408             DrawPosition(FALSE, boards[0]);
11409         }
11410         break;
11411
11412       case WhitePlay:
11413         SetWhiteToPlayEvent();
11414         break;
11415
11416       case BlackPlay:
11417         SetBlackToPlayEvent();
11418         break;
11419
11420       case EmptySquare:
11421         if (gameMode == IcsExamining) {
11422             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11423             SendToICS(buf);
11424         } else {
11425             boards[0][y][x] = EmptySquare;
11426             DrawPosition(FALSE, boards[0]);
11427         }
11428         break;
11429
11430       case PromotePiece:
11431         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11432            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11433             selection = (ChessSquare) (PROMOTED piece);
11434         } else if(piece == EmptySquare) selection = WhiteSilver;
11435         else selection = (ChessSquare)((int)piece - 1);
11436         goto defaultlabel;
11437
11438       case DemotePiece:
11439         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11440            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11441             selection = (ChessSquare) (DEMOTED piece);
11442         } else if(piece == EmptySquare) selection = BlackSilver;
11443         else selection = (ChessSquare)((int)piece + 1);
11444         goto defaultlabel;
11445
11446       case WhiteQueen:
11447       case BlackQueen:
11448         if(gameInfo.variant == VariantShatranj ||
11449            gameInfo.variant == VariantXiangqi  ||
11450            gameInfo.variant == VariantCourier    )
11451             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11452         goto defaultlabel;
11453
11454       case WhiteKing:
11455       case BlackKing:
11456         if(gameInfo.variant == VariantXiangqi)
11457             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11458         if(gameInfo.variant == VariantKnightmate)
11459             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11460       default:
11461         defaultlabel:
11462         if (gameMode == IcsExamining) {
11463             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11464                     PieceToChar(selection), AAA + x, ONE + y);
11465             SendToICS(buf);
11466         } else {
11467             boards[0][y][x] = selection;
11468             DrawPosition(FALSE, boards[0]);
11469         }
11470         break;
11471     }
11472 }
11473
11474
11475 void
11476 DropMenuEvent(selection, x, y)
11477      ChessSquare selection;
11478      int x, y;
11479 {
11480     ChessMove moveType;
11481
11482     switch (gameMode) {
11483       case IcsPlayingWhite:
11484       case MachinePlaysBlack:
11485         if (!WhiteOnMove(currentMove)) {
11486             DisplayMoveError(_("It is Black's turn"));
11487             return;
11488         }
11489         moveType = WhiteDrop;
11490         break;
11491       case IcsPlayingBlack:
11492       case MachinePlaysWhite:
11493         if (WhiteOnMove(currentMove)) {
11494             DisplayMoveError(_("It is White's turn"));
11495             return;
11496         }
11497         moveType = BlackDrop;
11498         break;
11499       case EditGame:
11500         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11501         break;
11502       default:
11503         return;
11504     }
11505
11506     if (moveType == BlackDrop && selection < BlackPawn) {
11507       selection = (ChessSquare) ((int) selection
11508                                  + (int) BlackPawn - (int) WhitePawn);
11509     }
11510     if (boards[currentMove][y][x] != EmptySquare) {
11511         DisplayMoveError(_("That square is occupied"));
11512         return;
11513     }
11514
11515     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11516 }
11517
11518 void
11519 AcceptEvent()
11520 {
11521     /* Accept a pending offer of any kind from opponent */
11522
11523     if (appData.icsActive) {
11524         SendToICS(ics_prefix);
11525         SendToICS("accept\n");
11526     } else if (cmailMsgLoaded) {
11527         if (currentMove == cmailOldMove &&
11528             commentList[cmailOldMove] != NULL &&
11529             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11530                    "Black offers a draw" : "White offers a draw")) {
11531             TruncateGame();
11532             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11533             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11534         } else {
11535             DisplayError(_("There is no pending offer on this move"), 0);
11536             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11537         }
11538     } else {
11539         /* Not used for offers from chess program */
11540     }
11541 }
11542
11543 void
11544 DeclineEvent()
11545 {
11546     /* Decline a pending offer of any kind from opponent */
11547
11548     if (appData.icsActive) {
11549         SendToICS(ics_prefix);
11550         SendToICS("decline\n");
11551     } else if (cmailMsgLoaded) {
11552         if (currentMove == cmailOldMove &&
11553             commentList[cmailOldMove] != NULL &&
11554             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11555                    "Black offers a draw" : "White offers a draw")) {
11556 #ifdef NOTDEF
11557             AppendComment(cmailOldMove, "Draw declined");
11558             DisplayComment(cmailOldMove - 1, "Draw declined");
11559 #endif /*NOTDEF*/
11560         } else {
11561             DisplayError(_("There is no pending offer on this move"), 0);
11562         }
11563     } else {
11564         /* Not used for offers from chess program */
11565     }
11566 }
11567
11568 void
11569 RematchEvent()
11570 {
11571     /* Issue ICS rematch command */
11572     if (appData.icsActive) {
11573         SendToICS(ics_prefix);
11574         SendToICS("rematch\n");
11575     }
11576 }
11577
11578 void
11579 CallFlagEvent()
11580 {
11581     /* Call your opponent's flag (claim a win on time) */
11582     if (appData.icsActive) {
11583         SendToICS(ics_prefix);
11584         SendToICS("flag\n");
11585     } else {
11586         switch (gameMode) {
11587           default:
11588             return;
11589           case MachinePlaysWhite:
11590             if (whiteFlag) {
11591                 if (blackFlag)
11592                   GameEnds(GameIsDrawn, "Both players ran out of time",
11593                            GE_PLAYER);
11594                 else
11595                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11596             } else {
11597                 DisplayError(_("Your opponent is not out of time"), 0);
11598             }
11599             break;
11600           case MachinePlaysBlack:
11601             if (blackFlag) {
11602                 if (whiteFlag)
11603                   GameEnds(GameIsDrawn, "Both players ran out of time",
11604                            GE_PLAYER);
11605                 else
11606                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11607             } else {
11608                 DisplayError(_("Your opponent is not out of time"), 0);
11609             }
11610             break;
11611         }
11612     }
11613 }
11614
11615 void
11616 DrawEvent()
11617 {
11618     /* Offer draw or accept pending draw offer from opponent */
11619
11620     if (appData.icsActive) {
11621         /* Note: tournament rules require draw offers to be
11622            made after you make your move but before you punch
11623            your clock.  Currently ICS doesn't let you do that;
11624            instead, you immediately punch your clock after making
11625            a move, but you can offer a draw at any time. */
11626
11627         SendToICS(ics_prefix);
11628         SendToICS("draw\n");
11629     } else if (cmailMsgLoaded) {
11630         if (currentMove == cmailOldMove &&
11631             commentList[cmailOldMove] != NULL &&
11632             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11633                    "Black offers a draw" : "White offers a draw")) {
11634             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11636         } else if (currentMove == cmailOldMove + 1) {
11637             char *offer = WhiteOnMove(cmailOldMove) ?
11638               "White offers a draw" : "Black offers a draw";
11639             AppendComment(currentMove, offer);
11640             DisplayComment(currentMove - 1, offer);
11641             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11642         } else {
11643             DisplayError(_("You must make your move before offering a draw"), 0);
11644             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11645         }
11646     } else if (first.offeredDraw) {
11647         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11648     } else {
11649         if (first.sendDrawOffers) {
11650             SendToProgram("draw\n", &first);
11651             userOfferedDraw = TRUE;
11652         }
11653     }
11654 }
11655
11656 void
11657 AdjournEvent()
11658 {
11659     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11660
11661     if (appData.icsActive) {
11662         SendToICS(ics_prefix);
11663         SendToICS("adjourn\n");
11664     } else {
11665         /* Currently GNU Chess doesn't offer or accept Adjourns */
11666     }
11667 }
11668
11669
11670 void
11671 AbortEvent()
11672 {
11673     /* Offer Abort or accept pending Abort offer from opponent */
11674
11675     if (appData.icsActive) {
11676         SendToICS(ics_prefix);
11677         SendToICS("abort\n");
11678     } else {
11679         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11680     }
11681 }
11682
11683 void
11684 ResignEvent()
11685 {
11686     /* Resign.  You can do this even if it's not your turn. */
11687
11688     if (appData.icsActive) {
11689         SendToICS(ics_prefix);
11690         SendToICS("resign\n");
11691     } else {
11692         switch (gameMode) {
11693           case MachinePlaysWhite:
11694             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11695             break;
11696           case MachinePlaysBlack:
11697             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11698             break;
11699           case EditGame:
11700             if (cmailMsgLoaded) {
11701                 TruncateGame();
11702                 if (WhiteOnMove(cmailOldMove)) {
11703                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11704                 } else {
11705                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11706                 }
11707                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11708             }
11709             break;
11710           default:
11711             break;
11712         }
11713     }
11714 }
11715
11716
11717 void
11718 StopObservingEvent()
11719 {
11720     /* Stop observing current games */
11721     SendToICS(ics_prefix);
11722     SendToICS("unobserve\n");
11723 }
11724
11725 void
11726 StopExaminingEvent()
11727 {
11728     /* Stop observing current game */
11729     SendToICS(ics_prefix);
11730     SendToICS("unexamine\n");
11731 }
11732
11733 void
11734 ForwardInner(target)
11735      int target;
11736 {
11737     int limit;
11738
11739     if (appData.debugMode)
11740         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11741                 target, currentMove, forwardMostMove);
11742
11743     if (gameMode == EditPosition)
11744       return;
11745
11746     if (gameMode == PlayFromGameFile && !pausing)
11747       PauseEvent();
11748
11749     if (gameMode == IcsExamining && pausing)
11750       limit = pauseExamForwardMostMove;
11751     else
11752       limit = forwardMostMove;
11753
11754     if (target > limit) target = limit;
11755
11756     if (target > 0 && moveList[target - 1][0]) {
11757         int fromX, fromY, toX, toY;
11758         toX = moveList[target - 1][2] - AAA;
11759         toY = moveList[target - 1][3] - ONE;
11760         if (moveList[target - 1][1] == '@') {
11761             if (appData.highlightLastMove) {
11762                 SetHighlights(-1, -1, toX, toY);
11763             }
11764         } else {
11765             fromX = moveList[target - 1][0] - AAA;
11766             fromY = moveList[target - 1][1] - ONE;
11767             if (target == currentMove + 1) {
11768                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11769             }
11770             if (appData.highlightLastMove) {
11771                 SetHighlights(fromX, fromY, toX, toY);
11772             }
11773         }
11774     }
11775     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11776         gameMode == Training || gameMode == PlayFromGameFile ||
11777         gameMode == AnalyzeFile) {
11778         while (currentMove < target) {
11779             SendMoveToProgram(currentMove++, &first);
11780         }
11781     } else {
11782         currentMove = target;
11783     }
11784
11785     if (gameMode == EditGame || gameMode == EndOfGame) {
11786         whiteTimeRemaining = timeRemaining[0][currentMove];
11787         blackTimeRemaining = timeRemaining[1][currentMove];
11788     }
11789     DisplayBothClocks();
11790     DisplayMove(currentMove - 1);
11791     DrawPosition(FALSE, boards[currentMove]);
11792     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11793     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11794         DisplayComment(currentMove - 1, commentList[currentMove]);
11795     }
11796 }
11797
11798
11799 void
11800 ForwardEvent()
11801 {
11802     if (gameMode == IcsExamining && !pausing) {
11803         SendToICS(ics_prefix);
11804         SendToICS("forward\n");
11805     } else {
11806         ForwardInner(currentMove + 1);
11807     }
11808 }
11809
11810 void
11811 ToEndEvent()
11812 {
11813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11814         /* to optimze, we temporarily turn off analysis mode while we feed
11815          * the remaining moves to the engine. Otherwise we get analysis output
11816          * after each move.
11817          */
11818         if (first.analysisSupport) {
11819           SendToProgram("exit\nforce\n", &first);
11820           first.analyzing = FALSE;
11821         }
11822     }
11823
11824     if (gameMode == IcsExamining && !pausing) {
11825         SendToICS(ics_prefix);
11826         SendToICS("forward 999999\n");
11827     } else {
11828         ForwardInner(forwardMostMove);
11829     }
11830
11831     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11832         /* we have fed all the moves, so reactivate analysis mode */
11833         SendToProgram("analyze\n", &first);
11834         first.analyzing = TRUE;
11835         /*first.maybeThinking = TRUE;*/
11836         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11837     }
11838 }
11839
11840 void
11841 BackwardInner(target)
11842      int target;
11843 {
11844     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11845
11846     if (appData.debugMode)
11847         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11848                 target, currentMove, forwardMostMove);
11849
11850     if (gameMode == EditPosition) return;
11851     if (currentMove <= backwardMostMove) {
11852         ClearHighlights();
11853         DrawPosition(full_redraw, boards[currentMove]);
11854         return;
11855     }
11856     if (gameMode == PlayFromGameFile && !pausing)
11857       PauseEvent();
11858
11859     if (moveList[target][0]) {
11860         int fromX, fromY, toX, toY;
11861         toX = moveList[target][2] - AAA;
11862         toY = moveList[target][3] - ONE;
11863         if (moveList[target][1] == '@') {
11864             if (appData.highlightLastMove) {
11865                 SetHighlights(-1, -1, toX, toY);
11866             }
11867         } else {
11868             fromX = moveList[target][0] - AAA;
11869             fromY = moveList[target][1] - ONE;
11870             if (target == currentMove - 1) {
11871                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11872             }
11873             if (appData.highlightLastMove) {
11874                 SetHighlights(fromX, fromY, toX, toY);
11875             }
11876         }
11877     }
11878     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11879         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11880         while (currentMove > target) {
11881             SendToProgram("undo\n", &first);
11882             currentMove--;
11883         }
11884     } else {
11885         currentMove = target;
11886     }
11887
11888     if (gameMode == EditGame || gameMode == EndOfGame) {
11889         whiteTimeRemaining = timeRemaining[0][currentMove];
11890         blackTimeRemaining = timeRemaining[1][currentMove];
11891     }
11892     DisplayBothClocks();
11893     DisplayMove(currentMove - 1);
11894     DrawPosition(full_redraw, boards[currentMove]);
11895     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11896     // [HGM] PV info: routine tests if comment empty
11897     DisplayComment(currentMove - 1, commentList[currentMove]);
11898 }
11899
11900 void
11901 BackwardEvent()
11902 {
11903     if (gameMode == IcsExamining && !pausing) {
11904         SendToICS(ics_prefix);
11905         SendToICS("backward\n");
11906     } else {
11907         BackwardInner(currentMove - 1);
11908     }
11909 }
11910
11911 void
11912 ToStartEvent()
11913 {
11914     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11915         /* to optimze, we temporarily turn off analysis mode while we undo
11916          * all the moves. Otherwise we get analysis output after each undo.
11917          */
11918         if (first.analysisSupport) {
11919           SendToProgram("exit\nforce\n", &first);
11920           first.analyzing = FALSE;
11921         }
11922     }
11923
11924     if (gameMode == IcsExamining && !pausing) {
11925         SendToICS(ics_prefix);
11926         SendToICS("backward 999999\n");
11927     } else {
11928         BackwardInner(backwardMostMove);
11929     }
11930
11931     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11932         /* we have fed all the moves, so reactivate analysis mode */
11933         SendToProgram("analyze\n", &first);
11934         first.analyzing = TRUE;
11935         /*first.maybeThinking = TRUE;*/
11936         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11937     }
11938 }
11939
11940 void
11941 ToNrEvent(int to)
11942 {
11943   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11944   if (to >= forwardMostMove) to = forwardMostMove;
11945   if (to <= backwardMostMove) to = backwardMostMove;
11946   if (to < currentMove) {
11947     BackwardInner(to);
11948   } else {
11949     ForwardInner(to);
11950   }
11951 }
11952
11953 void
11954 RevertEvent()
11955 {
11956     if (gameMode != IcsExamining) {
11957         DisplayError(_("You are not examining a game"), 0);
11958         return;
11959     }
11960     if (pausing) {
11961         DisplayError(_("You can't revert while pausing"), 0);
11962         return;
11963     }
11964     SendToICS(ics_prefix);
11965     SendToICS("revert\n");
11966 }
11967
11968 void
11969 RetractMoveEvent()
11970 {
11971     switch (gameMode) {
11972       case MachinePlaysWhite:
11973       case MachinePlaysBlack:
11974         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11975             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11976             return;
11977         }
11978         if (forwardMostMove < 2) return;
11979         currentMove = forwardMostMove = forwardMostMove - 2;
11980         whiteTimeRemaining = timeRemaining[0][currentMove];
11981         blackTimeRemaining = timeRemaining[1][currentMove];
11982         DisplayBothClocks();
11983         DisplayMove(currentMove - 1);
11984         ClearHighlights();/*!! could figure this out*/
11985         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11986         SendToProgram("remove\n", &first);
11987         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11988         break;
11989
11990       case BeginningOfGame:
11991       default:
11992         break;
11993
11994       case IcsPlayingWhite:
11995       case IcsPlayingBlack:
11996         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11997             SendToICS(ics_prefix);
11998             SendToICS("takeback 2\n");
11999         } else {
12000             SendToICS(ics_prefix);
12001             SendToICS("takeback 1\n");
12002         }
12003         break;
12004     }
12005 }
12006
12007 void
12008 MoveNowEvent()
12009 {
12010     ChessProgramState *cps;
12011
12012     switch (gameMode) {
12013       case MachinePlaysWhite:
12014         if (!WhiteOnMove(forwardMostMove)) {
12015             DisplayError(_("It is your turn"), 0);
12016             return;
12017         }
12018         cps = &first;
12019         break;
12020       case MachinePlaysBlack:
12021         if (WhiteOnMove(forwardMostMove)) {
12022             DisplayError(_("It is your turn"), 0);
12023             return;
12024         }
12025         cps = &first;
12026         break;
12027       case TwoMachinesPlay:
12028         if (WhiteOnMove(forwardMostMove) ==
12029             (first.twoMachinesColor[0] == 'w')) {
12030             cps = &first;
12031         } else {
12032             cps = &second;
12033         }
12034         break;
12035       case BeginningOfGame:
12036       default:
12037         return;
12038     }
12039     SendToProgram("?\n", cps);
12040 }
12041
12042 void
12043 TruncateGameEvent()
12044 {
12045     EditGameEvent();
12046     if (gameMode != EditGame) return;
12047     TruncateGame();
12048 }
12049
12050 void
12051 TruncateGame()
12052 {
12053     if (forwardMostMove > currentMove) {
12054         if (gameInfo.resultDetails != NULL) {
12055             free(gameInfo.resultDetails);
12056             gameInfo.resultDetails = NULL;
12057             gameInfo.result = GameUnfinished;
12058         }
12059         forwardMostMove = currentMove;
12060         HistorySet(parseList, backwardMostMove, forwardMostMove,
12061                    currentMove-1);
12062     }
12063 }
12064
12065 void
12066 HintEvent()
12067 {
12068     if (appData.noChessProgram) return;
12069     switch (gameMode) {
12070       case MachinePlaysWhite:
12071         if (WhiteOnMove(forwardMostMove)) {
12072             DisplayError(_("Wait until your turn"), 0);
12073             return;
12074         }
12075         break;
12076       case BeginningOfGame:
12077       case MachinePlaysBlack:
12078         if (!WhiteOnMove(forwardMostMove)) {
12079             DisplayError(_("Wait until your turn"), 0);
12080             return;
12081         }
12082         break;
12083       default:
12084         DisplayError(_("No hint available"), 0);
12085         return;
12086     }
12087     SendToProgram("hint\n", &first);
12088     hintRequested = TRUE;
12089 }
12090
12091 void
12092 BookEvent()
12093 {
12094     if (appData.noChessProgram) return;
12095     switch (gameMode) {
12096       case MachinePlaysWhite:
12097         if (WhiteOnMove(forwardMostMove)) {
12098             DisplayError(_("Wait until your turn"), 0);
12099             return;
12100         }
12101         break;
12102       case BeginningOfGame:
12103       case MachinePlaysBlack:
12104         if (!WhiteOnMove(forwardMostMove)) {
12105             DisplayError(_("Wait until your turn"), 0);
12106             return;
12107         }
12108         break;
12109       case EditPosition:
12110         EditPositionDone();
12111         break;
12112       case TwoMachinesPlay:
12113         return;
12114       default:
12115         break;
12116     }
12117     SendToProgram("bk\n", &first);
12118     bookOutput[0] = NULLCHAR;
12119     bookRequested = TRUE;
12120 }
12121
12122 void
12123 AboutGameEvent()
12124 {
12125     char *tags = PGNTags(&gameInfo);
12126     TagsPopUp(tags, CmailMsg());
12127     free(tags);
12128 }
12129
12130 /* end button procedures */
12131
12132 void
12133 PrintPosition(fp, move)
12134      FILE *fp;
12135      int move;
12136 {
12137     int i, j;
12138
12139     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12140         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12141             char c = PieceToChar(boards[move][i][j]);
12142             fputc(c == 'x' ? '.' : c, fp);
12143             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12144         }
12145     }
12146     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12147       fprintf(fp, "white to play\n");
12148     else
12149       fprintf(fp, "black to play\n");
12150 }
12151
12152 void
12153 PrintOpponents(fp)
12154      FILE *fp;
12155 {
12156     if (gameInfo.white != NULL) {
12157         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12158     } else {
12159         fprintf(fp, "\n");
12160     }
12161 }
12162
12163 /* Find last component of program's own name, using some heuristics */
12164 void
12165 TidyProgramName(prog, host, buf)
12166      char *prog, *host, buf[MSG_SIZ];
12167 {
12168     char *p, *q;
12169     int local = (strcmp(host, "localhost") == 0);
12170     while (!local && (p = strchr(prog, ';')) != NULL) {
12171         p++;
12172         while (*p == ' ') p++;
12173         prog = p;
12174     }
12175     if (*prog == '"' || *prog == '\'') {
12176         q = strchr(prog + 1, *prog);
12177     } else {
12178         q = strchr(prog, ' ');
12179     }
12180     if (q == NULL) q = prog + strlen(prog);
12181     p = q;
12182     while (p >= prog && *p != '/' && *p != '\\') p--;
12183     p++;
12184     if(p == prog && *p == '"') p++;
12185     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12186     memcpy(buf, p, q - p);
12187     buf[q - p] = NULLCHAR;
12188     if (!local) {
12189         strcat(buf, "@");
12190         strcat(buf, host);
12191     }
12192 }
12193
12194 char *
12195 TimeControlTagValue()
12196 {
12197     char buf[MSG_SIZ];
12198     if (!appData.clockMode) {
12199         strcpy(buf, "-");
12200     } else if (movesPerSession > 0) {
12201         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12202     } else if (timeIncrement == 0) {
12203         sprintf(buf, "%ld", timeControl/1000);
12204     } else {
12205         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12206     }
12207     return StrSave(buf);
12208 }
12209
12210 void
12211 SetGameInfo()
12212 {
12213     /* This routine is used only for certain modes */
12214     VariantClass v = gameInfo.variant;
12215     ClearGameInfo(&gameInfo);
12216     gameInfo.variant = v;
12217
12218     switch (gameMode) {
12219       case MachinePlaysWhite:
12220         gameInfo.event = StrSave( appData.pgnEventHeader );
12221         gameInfo.site = StrSave(HostName());
12222         gameInfo.date = PGNDate();
12223         gameInfo.round = StrSave("-");
12224         gameInfo.white = StrSave(first.tidy);
12225         gameInfo.black = StrSave(UserName());
12226         gameInfo.timeControl = TimeControlTagValue();
12227         break;
12228
12229       case MachinePlaysBlack:
12230         gameInfo.event = StrSave( appData.pgnEventHeader );
12231         gameInfo.site = StrSave(HostName());
12232         gameInfo.date = PGNDate();
12233         gameInfo.round = StrSave("-");
12234         gameInfo.white = StrSave(UserName());
12235         gameInfo.black = StrSave(first.tidy);
12236         gameInfo.timeControl = TimeControlTagValue();
12237         break;
12238
12239       case TwoMachinesPlay:
12240         gameInfo.event = StrSave( appData.pgnEventHeader );
12241         gameInfo.site = StrSave(HostName());
12242         gameInfo.date = PGNDate();
12243         if (matchGame > 0) {
12244             char buf[MSG_SIZ];
12245             sprintf(buf, "%d", matchGame);
12246             gameInfo.round = StrSave(buf);
12247         } else {
12248             gameInfo.round = StrSave("-");
12249         }
12250         if (first.twoMachinesColor[0] == 'w') {
12251             gameInfo.white = StrSave(first.tidy);
12252             gameInfo.black = StrSave(second.tidy);
12253         } else {
12254             gameInfo.white = StrSave(second.tidy);
12255             gameInfo.black = StrSave(first.tidy);
12256         }
12257         gameInfo.timeControl = TimeControlTagValue();
12258         break;
12259
12260       case EditGame:
12261         gameInfo.event = StrSave("Edited game");
12262         gameInfo.site = StrSave(HostName());
12263         gameInfo.date = PGNDate();
12264         gameInfo.round = StrSave("-");
12265         gameInfo.white = StrSave("-");
12266         gameInfo.black = StrSave("-");
12267         break;
12268
12269       case EditPosition:
12270         gameInfo.event = StrSave("Edited position");
12271         gameInfo.site = StrSave(HostName());
12272         gameInfo.date = PGNDate();
12273         gameInfo.round = StrSave("-");
12274         gameInfo.white = StrSave("-");
12275         gameInfo.black = StrSave("-");
12276         break;
12277
12278       case IcsPlayingWhite:
12279       case IcsPlayingBlack:
12280       case IcsObserving:
12281       case IcsExamining:
12282         break;
12283
12284       case PlayFromGameFile:
12285         gameInfo.event = StrSave("Game from non-PGN file");
12286         gameInfo.site = StrSave(HostName());
12287         gameInfo.date = PGNDate();
12288         gameInfo.round = StrSave("-");
12289         gameInfo.white = StrSave("?");
12290         gameInfo.black = StrSave("?");
12291         break;
12292
12293       default:
12294         break;
12295     }
12296 }
12297
12298 void
12299 ReplaceComment(index, text)
12300      int index;
12301      char *text;
12302 {
12303     int len;
12304
12305     while (*text == '\n') text++;
12306     len = strlen(text);
12307     while (len > 0 && text[len - 1] == '\n') len--;
12308
12309     if (commentList[index] != NULL)
12310       free(commentList[index]);
12311
12312     if (len == 0) {
12313         commentList[index] = NULL;
12314         return;
12315     }
12316     commentList[index] = (char *) malloc(len + 2);
12317     strncpy(commentList[index], text, len);
12318     commentList[index][len] = '\n';
12319     commentList[index][len + 1] = NULLCHAR;
12320 }
12321
12322 void
12323 CrushCRs(text)
12324      char *text;
12325 {
12326   char *p = text;
12327   char *q = text;
12328   char ch;
12329
12330   do {
12331     ch = *p++;
12332     if (ch == '\r') continue;
12333     *q++ = ch;
12334   } while (ch != '\0');
12335 }
12336
12337 void
12338 AppendComment(index, text)
12339      int index;
12340      char *text;
12341 {
12342     int oldlen, len;
12343     char *old;
12344
12345     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12346
12347     CrushCRs(text);
12348     while (*text == '\n') text++;
12349     len = strlen(text);
12350     while (len > 0 && text[len - 1] == '\n') len--;
12351
12352     if (len == 0) return;
12353
12354     if (commentList[index] != NULL) {
12355         old = commentList[index];
12356         oldlen = strlen(old);
12357         commentList[index] = (char *) malloc(oldlen + len + 2);
12358         strcpy(commentList[index], old);
12359         free(old);
12360         strncpy(&commentList[index][oldlen], text, len);
12361         commentList[index][oldlen + len] = '\n';
12362         commentList[index][oldlen + len + 1] = NULLCHAR;
12363     } else {
12364         commentList[index] = (char *) malloc(len + 2);
12365         strncpy(commentList[index], text, len);
12366         commentList[index][len] = '\n';
12367         commentList[index][len + 1] = NULLCHAR;
12368     }
12369 }
12370
12371 static char * FindStr( char * text, char * sub_text )
12372 {
12373     char * result = strstr( text, sub_text );
12374
12375     if( result != NULL ) {
12376         result += strlen( sub_text );
12377     }
12378
12379     return result;
12380 }
12381
12382 /* [AS] Try to extract PV info from PGN comment */
12383 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12384 char *GetInfoFromComment( int index, char * text )
12385 {
12386     char * sep = text;
12387
12388     if( text != NULL && index > 0 ) {
12389         int score = 0;
12390         int depth = 0;
12391         int time = -1, sec = 0, deci;
12392         char * s_eval = FindStr( text, "[%eval " );
12393         char * s_emt = FindStr( text, "[%emt " );
12394
12395         if( s_eval != NULL || s_emt != NULL ) {
12396             /* New style */
12397             char delim;
12398
12399             if( s_eval != NULL ) {
12400                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12401                     return text;
12402                 }
12403
12404                 if( delim != ']' ) {
12405                     return text;
12406                 }
12407             }
12408
12409             if( s_emt != NULL ) {
12410             }
12411         }
12412         else {
12413             /* We expect something like: [+|-]nnn.nn/dd */
12414             int score_lo = 0;
12415
12416             sep = strchr( text, '/' );
12417             if( sep == NULL || sep < (text+4) ) {
12418                 return text;
12419             }
12420
12421             time = -1; sec = -1; deci = -1;
12422             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12423                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12424                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12425                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12426                 return text;
12427             }
12428
12429             if( score_lo < 0 || score_lo >= 100 ) {
12430                 return text;
12431             }
12432
12433             if(sec >= 0) time = 600*time + 10*sec; else
12434             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12435
12436             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12437
12438             /* [HGM] PV time: now locate end of PV info */
12439             while( *++sep >= '0' && *sep <= '9'); // strip depth
12440             if(time >= 0)
12441             while( *++sep >= '0' && *sep <= '9'); // strip time
12442             if(sec >= 0)
12443             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12444             if(deci >= 0)
12445             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12446             while(*sep == ' ') sep++;
12447         }
12448
12449         if( depth <= 0 ) {
12450             return text;
12451         }
12452
12453         if( time < 0 ) {
12454             time = -1;
12455         }
12456
12457         pvInfoList[index-1].depth = depth;
12458         pvInfoList[index-1].score = score;
12459         pvInfoList[index-1].time  = 10*time; // centi-sec
12460     }
12461     return sep;
12462 }
12463
12464 void
12465 SendToProgram(message, cps)
12466      char *message;
12467      ChessProgramState *cps;
12468 {
12469     int count, outCount, error;
12470     char buf[MSG_SIZ];
12471
12472     if (cps->pr == NULL) return;
12473     Attention(cps);
12474
12475     if (appData.debugMode) {
12476         TimeMark now;
12477         GetTimeMark(&now);
12478         fprintf(debugFP, "%ld >%-6s: %s",
12479                 SubtractTimeMarks(&now, &programStartTime),
12480                 cps->which, message);
12481     }
12482
12483     count = strlen(message);
12484     outCount = OutputToProcess(cps->pr, message, count, &error);
12485     if (outCount < count && !exiting
12486                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12487         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12488         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12489             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12490                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12491                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12492             } else {
12493                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12494             }
12495             gameInfo.resultDetails = buf;
12496         }
12497         DisplayFatalError(buf, error, 1);
12498     }
12499 }
12500
12501 void
12502 ReceiveFromProgram(isr, closure, message, count, error)
12503      InputSourceRef isr;
12504      VOIDSTAR closure;
12505      char *message;
12506      int count;
12507      int error;
12508 {
12509     char *end_str;
12510     char buf[MSG_SIZ];
12511     ChessProgramState *cps = (ChessProgramState *)closure;
12512
12513     if (isr != cps->isr) return; /* Killed intentionally */
12514     if (count <= 0) {
12515         if (count == 0) {
12516             sprintf(buf,
12517                     _("Error: %s chess program (%s) exited unexpectedly"),
12518                     cps->which, cps->program);
12519         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12520                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12521                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12522                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12523                 } else {
12524                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12525                 }
12526                 gameInfo.resultDetails = buf;
12527             }
12528             RemoveInputSource(cps->isr);
12529             DisplayFatalError(buf, 0, 1);
12530         } else {
12531             sprintf(buf,
12532                     _("Error reading from %s chess program (%s)"),
12533                     cps->which, cps->program);
12534             RemoveInputSource(cps->isr);
12535
12536             /* [AS] Program is misbehaving badly... kill it */
12537             if( count == -2 ) {
12538                 DestroyChildProcess( cps->pr, 9 );
12539                 cps->pr = NoProc;
12540             }
12541
12542             DisplayFatalError(buf, error, 1);
12543         }
12544         return;
12545     }
12546
12547     if ((end_str = strchr(message, '\r')) != NULL)
12548       *end_str = NULLCHAR;
12549     if ((end_str = strchr(message, '\n')) != NULL)
12550       *end_str = NULLCHAR;
12551
12552     if (appData.debugMode) {
12553         TimeMark now; int print = 1;
12554         char *quote = ""; char c; int i;
12555
12556         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12557                 char start = message[0];
12558                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12559                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12560                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12561                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12562                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12563                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12564                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12565                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12566                         { quote = "# "; print = (appData.engineComments == 2); }
12567                 message[0] = start; // restore original message
12568         }
12569         if(print) {
12570                 GetTimeMark(&now);
12571                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12572                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12573                         quote,
12574                         message);
12575         }
12576     }
12577
12578     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12579     if (appData.icsEngineAnalyze) {
12580         if (strstr(message, "whisper") != NULL ||
12581              strstr(message, "kibitz") != NULL ||
12582             strstr(message, "tellics") != NULL) return;
12583     }
12584
12585     HandleMachineMove(message, cps);
12586 }
12587
12588
12589 void
12590 SendTimeControl(cps, mps, tc, inc, sd, st)
12591      ChessProgramState *cps;
12592      int mps, inc, sd, st;
12593      long tc;
12594 {
12595     char buf[MSG_SIZ];
12596     int seconds;
12597
12598     if( timeControl_2 > 0 ) {
12599         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12600             tc = timeControl_2;
12601         }
12602     }
12603     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12604     inc /= cps->timeOdds;
12605     st  /= cps->timeOdds;
12606
12607     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12608
12609     if (st > 0) {
12610       /* Set exact time per move, normally using st command */
12611       if (cps->stKludge) {
12612         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12613         seconds = st % 60;
12614         if (seconds == 0) {
12615           sprintf(buf, "level 1 %d\n", st/60);
12616         } else {
12617           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12618         }
12619       } else {
12620         sprintf(buf, "st %d\n", st);
12621       }
12622     } else {
12623       /* Set conventional or incremental time control, using level command */
12624       if (seconds == 0) {
12625         /* Note old gnuchess bug -- minutes:seconds used to not work.
12626            Fixed in later versions, but still avoid :seconds
12627            when seconds is 0. */
12628         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12629       } else {
12630         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12631                 seconds, inc/1000);
12632       }
12633     }
12634     SendToProgram(buf, cps);
12635
12636     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12637     /* Orthogonally, limit search to given depth */
12638     if (sd > 0) {
12639       if (cps->sdKludge) {
12640         sprintf(buf, "depth\n%d\n", sd);
12641       } else {
12642         sprintf(buf, "sd %d\n", sd);
12643       }
12644       SendToProgram(buf, cps);
12645     }
12646
12647     if(cps->nps > 0) { /* [HGM] nps */
12648         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12649         else {
12650                 sprintf(buf, "nps %d\n", cps->nps);
12651               SendToProgram(buf, cps);
12652         }
12653     }
12654 }
12655
12656 ChessProgramState *WhitePlayer()
12657 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12658 {
12659     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12660        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12661         return &second;
12662     return &first;
12663 }
12664
12665 void
12666 SendTimeRemaining(cps, machineWhite)
12667      ChessProgramState *cps;
12668      int /*boolean*/ machineWhite;
12669 {
12670     char message[MSG_SIZ];
12671     long time, otime;
12672
12673     /* Note: this routine must be called when the clocks are stopped
12674        or when they have *just* been set or switched; otherwise
12675        it will be off by the time since the current tick started.
12676     */
12677     if (machineWhite) {
12678         time = whiteTimeRemaining / 10;
12679         otime = blackTimeRemaining / 10;
12680     } else {
12681         time = blackTimeRemaining / 10;
12682         otime = whiteTimeRemaining / 10;
12683     }
12684     /* [HGM] translate opponent's time by time-odds factor */
12685     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12686     if (appData.debugMode) {
12687         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12688     }
12689
12690     if (time <= 0) time = 1;
12691     if (otime <= 0) otime = 1;
12692
12693     sprintf(message, "time %ld\n", time);
12694     SendToProgram(message, cps);
12695
12696     sprintf(message, "otim %ld\n", otime);
12697     SendToProgram(message, cps);
12698 }
12699
12700 int
12701 BoolFeature(p, name, loc, cps)
12702      char **p;
12703      char *name;
12704      int *loc;
12705      ChessProgramState *cps;
12706 {
12707   char buf[MSG_SIZ];
12708   int len = strlen(name);
12709   int val;
12710   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12711     (*p) += len + 1;
12712     sscanf(*p, "%d", &val);
12713     *loc = (val != 0);
12714     while (**p && **p != ' ') (*p)++;
12715     sprintf(buf, "accepted %s\n", name);
12716     SendToProgram(buf, cps);
12717     return TRUE;
12718   }
12719   return FALSE;
12720 }
12721
12722 int
12723 IntFeature(p, name, loc, cps)
12724      char **p;
12725      char *name;
12726      int *loc;
12727      ChessProgramState *cps;
12728 {
12729   char buf[MSG_SIZ];
12730   int len = strlen(name);
12731   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12732     (*p) += len + 1;
12733     sscanf(*p, "%d", loc);
12734     while (**p && **p != ' ') (*p)++;
12735     sprintf(buf, "accepted %s\n", name);
12736     SendToProgram(buf, cps);
12737     return TRUE;
12738   }
12739   return FALSE;
12740 }
12741
12742 int
12743 StringFeature(p, name, loc, cps)
12744      char **p;
12745      char *name;
12746      char loc[];
12747      ChessProgramState *cps;
12748 {
12749   char buf[MSG_SIZ];
12750   int len = strlen(name);
12751   if (strncmp((*p), name, len) == 0
12752       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12753     (*p) += len + 2;
12754     sscanf(*p, "%[^\"]", loc);
12755     while (**p && **p != '\"') (*p)++;
12756     if (**p == '\"') (*p)++;
12757     sprintf(buf, "accepted %s\n", name);
12758     SendToProgram(buf, cps);
12759     return TRUE;
12760   }
12761   return FALSE;
12762 }
12763
12764 int
12765 ParseOption(Option *opt, ChessProgramState *cps)
12766 // [HGM] options: process the string that defines an engine option, and determine
12767 // name, type, default value, and allowed value range
12768 {
12769         char *p, *q, buf[MSG_SIZ];
12770         int n, min = (-1)<<31, max = 1<<31, def;
12771
12772         if(p = strstr(opt->name, " -spin ")) {
12773             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12774             if(max < min) max = min; // enforce consistency
12775             if(def < min) def = min;
12776             if(def > max) def = max;
12777             opt->value = def;
12778             opt->min = min;
12779             opt->max = max;
12780             opt->type = Spin;
12781         } else if((p = strstr(opt->name, " -slider "))) {
12782             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12783             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12784             if(max < min) max = min; // enforce consistency
12785             if(def < min) def = min;
12786             if(def > max) def = max;
12787             opt->value = def;
12788             opt->min = min;
12789             opt->max = max;
12790             opt->type = Spin; // Slider;
12791         } else if((p = strstr(opt->name, " -string "))) {
12792             opt->textValue = p+9;
12793             opt->type = TextBox;
12794         } else if((p = strstr(opt->name, " -file "))) {
12795             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12796             opt->textValue = p+7;
12797             opt->type = TextBox; // FileName;
12798         } else if((p = strstr(opt->name, " -path "))) {
12799             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12800             opt->textValue = p+7;
12801             opt->type = TextBox; // PathName;
12802         } else if(p = strstr(opt->name, " -check ")) {
12803             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12804             opt->value = (def != 0);
12805             opt->type = CheckBox;
12806         } else if(p = strstr(opt->name, " -combo ")) {
12807             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12808             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12809             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12810             opt->value = n = 0;
12811             while(q = StrStr(q, " /// ")) {
12812                 n++; *q = 0;    // count choices, and null-terminate each of them
12813                 q += 5;
12814                 if(*q == '*') { // remember default, which is marked with * prefix
12815                     q++;
12816                     opt->value = n;
12817                 }
12818                 cps->comboList[cps->comboCnt++] = q;
12819             }
12820             cps->comboList[cps->comboCnt++] = NULL;
12821             opt->max = n + 1;
12822             opt->type = ComboBox;
12823         } else if(p = strstr(opt->name, " -button")) {
12824             opt->type = Button;
12825         } else if(p = strstr(opt->name, " -save")) {
12826             opt->type = SaveButton;
12827         } else return FALSE;
12828         *p = 0; // terminate option name
12829         // now look if the command-line options define a setting for this engine option.
12830         if(cps->optionSettings && cps->optionSettings[0])
12831             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12832         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12833                 sprintf(buf, "option %s", p);
12834                 if(p = strstr(buf, ",")) *p = 0;
12835                 strcat(buf, "\n");
12836                 SendToProgram(buf, cps);
12837         }
12838         return TRUE;
12839 }
12840
12841 void
12842 FeatureDone(cps, val)
12843      ChessProgramState* cps;
12844      int val;
12845 {
12846   DelayedEventCallback cb = GetDelayedEvent();
12847   if ((cb == InitBackEnd3 && cps == &first) ||
12848       (cb == TwoMachinesEventIfReady && cps == &second)) {
12849     CancelDelayedEvent();
12850     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12851   }
12852   cps->initDone = val;
12853 }
12854
12855 /* Parse feature command from engine */
12856 void
12857 ParseFeatures(args, cps)
12858      char* args;
12859      ChessProgramState *cps;
12860 {
12861   char *p = args;
12862   char *q;
12863   int val;
12864   char buf[MSG_SIZ];
12865
12866   for (;;) {
12867     while (*p == ' ') p++;
12868     if (*p == NULLCHAR) return;
12869
12870     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12871     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12872     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12873     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12874     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12875     if (BoolFeature(&p, "reuse", &val, cps)) {
12876       /* Engine can disable reuse, but can't enable it if user said no */
12877       if (!val) cps->reuse = FALSE;
12878       continue;
12879     }
12880     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12881     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12882       if (gameMode == TwoMachinesPlay) {
12883         DisplayTwoMachinesTitle();
12884       } else {
12885         DisplayTitle("");
12886       }
12887       continue;
12888     }
12889     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12890     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12891     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12892     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12893     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12894     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12895     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12896     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12897     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12898     if (IntFeature(&p, "done", &val, cps)) {
12899       FeatureDone(cps, val);
12900       continue;
12901     }
12902     /* Added by Tord: */
12903     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12904     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12905     /* End of additions by Tord */
12906
12907     /* [HGM] added features: */
12908     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12909     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12910     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12911     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12912     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12913     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12914     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12915         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12916             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12917             SendToProgram(buf, cps);
12918             continue;
12919         }
12920         if(cps->nrOptions >= MAX_OPTIONS) {
12921             cps->nrOptions--;
12922             sprintf(buf, "%s engine has too many options\n", cps->which);
12923             DisplayError(buf, 0);
12924         }
12925         continue;
12926     }
12927     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12928     /* End of additions by HGM */
12929
12930     /* unknown feature: complain and skip */
12931     q = p;
12932     while (*q && *q != '=') q++;
12933     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12934     SendToProgram(buf, cps);
12935     p = q;
12936     if (*p == '=') {
12937       p++;
12938       if (*p == '\"') {
12939         p++;
12940         while (*p && *p != '\"') p++;
12941         if (*p == '\"') p++;
12942       } else {
12943         while (*p && *p != ' ') p++;
12944       }
12945     }
12946   }
12947
12948 }
12949
12950 void
12951 PeriodicUpdatesEvent(newState)
12952      int newState;
12953 {
12954     if (newState == appData.periodicUpdates)
12955       return;
12956
12957     appData.periodicUpdates=newState;
12958
12959     /* Display type changes, so update it now */
12960 //    DisplayAnalysis();
12961
12962     /* Get the ball rolling again... */
12963     if (newState) {
12964         AnalysisPeriodicEvent(1);
12965         StartAnalysisClock();
12966     }
12967 }
12968
12969 void
12970 PonderNextMoveEvent(newState)
12971      int newState;
12972 {
12973     if (newState == appData.ponderNextMove) return;
12974     if (gameMode == EditPosition) EditPositionDone();
12975     if (newState) {
12976         SendToProgram("hard\n", &first);
12977         if (gameMode == TwoMachinesPlay) {
12978             SendToProgram("hard\n", &second);
12979         }
12980     } else {
12981         SendToProgram("easy\n", &first);
12982         thinkOutput[0] = NULLCHAR;
12983         if (gameMode == TwoMachinesPlay) {
12984             SendToProgram("easy\n", &second);
12985         }
12986     }
12987     appData.ponderNextMove = newState;
12988 }
12989
12990 void
12991 NewSettingEvent(option, command, value)
12992      char *command;
12993      int option, value;
12994 {
12995     char buf[MSG_SIZ];
12996
12997     if (gameMode == EditPosition) EditPositionDone();
12998     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12999     SendToProgram(buf, &first);
13000     if (gameMode == TwoMachinesPlay) {
13001         SendToProgram(buf, &second);
13002     }
13003 }
13004
13005 void
13006 ShowThinkingEvent()
13007 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13008 {
13009     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13010     int newState = appData.showThinking
13011         // [HGM] thinking: other features now need thinking output as well
13012         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13013
13014     if (oldState == newState) return;
13015     oldState = newState;
13016     if (gameMode == EditPosition) EditPositionDone();
13017     if (oldState) {
13018         SendToProgram("post\n", &first);
13019         if (gameMode == TwoMachinesPlay) {
13020             SendToProgram("post\n", &second);
13021         }
13022     } else {
13023         SendToProgram("nopost\n", &first);
13024         thinkOutput[0] = NULLCHAR;
13025         if (gameMode == TwoMachinesPlay) {
13026             SendToProgram("nopost\n", &second);
13027         }
13028     }
13029 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13030 }
13031
13032 void
13033 AskQuestionEvent(title, question, replyPrefix, which)
13034      char *title; char *question; char *replyPrefix; char *which;
13035 {
13036   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13037   if (pr == NoProc) return;
13038   AskQuestion(title, question, replyPrefix, pr);
13039 }
13040
13041 void
13042 DisplayMove(moveNumber)
13043      int moveNumber;
13044 {
13045     char message[MSG_SIZ];
13046     char res[MSG_SIZ];
13047     char cpThinkOutput[MSG_SIZ];
13048
13049     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13050
13051     if (moveNumber == forwardMostMove - 1 ||
13052         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13053
13054         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13055
13056         if (strchr(cpThinkOutput, '\n')) {
13057             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13058         }
13059     } else {
13060         *cpThinkOutput = NULLCHAR;
13061     }
13062
13063     /* [AS] Hide thinking from human user */
13064     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13065         *cpThinkOutput = NULLCHAR;
13066         if( thinkOutput[0] != NULLCHAR ) {
13067             int i;
13068
13069             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13070                 cpThinkOutput[i] = '.';
13071             }
13072             cpThinkOutput[i] = NULLCHAR;
13073             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13074         }
13075     }
13076
13077     if (moveNumber == forwardMostMove - 1 &&
13078         gameInfo.resultDetails != NULL) {
13079         if (gameInfo.resultDetails[0] == NULLCHAR) {
13080             sprintf(res, " %s", PGNResult(gameInfo.result));
13081         } else {
13082             sprintf(res, " {%s} %s",
13083                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13084         }
13085     } else {
13086         res[0] = NULLCHAR;
13087     }
13088
13089     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13090         DisplayMessage(res, cpThinkOutput);
13091     } else {
13092         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13093                 WhiteOnMove(moveNumber) ? " " : ".. ",
13094                 parseList[moveNumber], res);
13095         DisplayMessage(message, cpThinkOutput);
13096     }
13097 }
13098
13099 void
13100 DisplayComment(moveNumber, text)
13101      int moveNumber;
13102      char *text;
13103 {
13104     char title[MSG_SIZ];
13105     char buf[8000]; // comment can be long!
13106     int score, depth;
13107     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13108       strcpy(title, "Comment");
13109     } else {
13110       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13111               WhiteOnMove(moveNumber) ? " " : ".. ",
13112               parseList[moveNumber]);
13113     }
13114     // [HGM] PV info: display PV info together with (or as) comment
13115     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13116       if(text == NULL) text = "";                                           
13117       score = pvInfoList[moveNumber].score;
13118       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13119               depth, (pvInfoList[moveNumber].time+50)/100, text);
13120       text = buf;
13121     }
13122     if (text != NULL && (appData.autoDisplayComment || commentUp))
13123       CommentPopUp(title, text);
13124 }
13125
13126 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13127  * might be busy thinking or pondering.  It can be omitted if your
13128  * gnuchess is configured to stop thinking immediately on any user
13129  * input.  However, that gnuchess feature depends on the FIONREAD
13130  * ioctl, which does not work properly on some flavors of Unix.
13131  */
13132 void
13133 Attention(cps)
13134      ChessProgramState *cps;
13135 {
13136 #if ATTENTION
13137     if (!cps->useSigint) return;
13138     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13139     switch (gameMode) {
13140       case MachinePlaysWhite:
13141       case MachinePlaysBlack:
13142       case TwoMachinesPlay:
13143       case IcsPlayingWhite:
13144       case IcsPlayingBlack:
13145       case AnalyzeMode:
13146       case AnalyzeFile:
13147         /* Skip if we know it isn't thinking */
13148         if (!cps->maybeThinking) return;
13149         if (appData.debugMode)
13150           fprintf(debugFP, "Interrupting %s\n", cps->which);
13151         InterruptChildProcess(cps->pr);
13152         cps->maybeThinking = FALSE;
13153         break;
13154       default:
13155         break;
13156     }
13157 #endif /*ATTENTION*/
13158 }
13159
13160 int
13161 CheckFlags()
13162 {
13163     if (whiteTimeRemaining <= 0) {
13164         if (!whiteFlag) {
13165             whiteFlag = TRUE;
13166             if (appData.icsActive) {
13167                 if (appData.autoCallFlag &&
13168                     gameMode == IcsPlayingBlack && !blackFlag) {
13169                   SendToICS(ics_prefix);
13170                   SendToICS("flag\n");
13171                 }
13172             } else {
13173                 if (blackFlag) {
13174                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13175                 } else {
13176                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13177                     if (appData.autoCallFlag) {
13178                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13179                         return TRUE;
13180                     }
13181                 }
13182             }
13183         }
13184     }
13185     if (blackTimeRemaining <= 0) {
13186         if (!blackFlag) {
13187             blackFlag = TRUE;
13188             if (appData.icsActive) {
13189                 if (appData.autoCallFlag &&
13190                     gameMode == IcsPlayingWhite && !whiteFlag) {
13191                   SendToICS(ics_prefix);
13192                   SendToICS("flag\n");
13193                 }
13194             } else {
13195                 if (whiteFlag) {
13196                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13197                 } else {
13198                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13199                     if (appData.autoCallFlag) {
13200                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13201                         return TRUE;
13202                     }
13203                 }
13204             }
13205         }
13206     }
13207     return FALSE;
13208 }
13209
13210 void
13211 CheckTimeControl()
13212 {
13213     if (!appData.clockMode || appData.icsActive ||
13214         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13215
13216     /*
13217      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13218      */
13219     if ( !WhiteOnMove(forwardMostMove) )
13220         /* White made time control */
13221         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13222         /* [HGM] time odds: correct new time quota for time odds! */
13223                                             / WhitePlayer()->timeOdds;
13224       else
13225         /* Black made time control */
13226         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13227                                             / WhitePlayer()->other->timeOdds;
13228 }
13229
13230 void
13231 DisplayBothClocks()
13232 {
13233     int wom = gameMode == EditPosition ?
13234       !blackPlaysFirst : WhiteOnMove(currentMove);
13235     DisplayWhiteClock(whiteTimeRemaining, wom);
13236     DisplayBlackClock(blackTimeRemaining, !wom);
13237 }
13238
13239
13240 /* Timekeeping seems to be a portability nightmare.  I think everyone
13241    has ftime(), but I'm really not sure, so I'm including some ifdefs
13242    to use other calls if you don't.  Clocks will be less accurate if
13243    you have neither ftime nor gettimeofday.
13244 */
13245
13246 /* VS 2008 requires the #include outside of the function */
13247 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13248 #include <sys/timeb.h>
13249 #endif
13250
13251 /* Get the current time as a TimeMark */
13252 void
13253 GetTimeMark(tm)
13254      TimeMark *tm;
13255 {
13256 #if HAVE_GETTIMEOFDAY
13257
13258     struct timeval timeVal;
13259     struct timezone timeZone;
13260
13261     gettimeofday(&timeVal, &timeZone);
13262     tm->sec = (long) timeVal.tv_sec;
13263     tm->ms = (int) (timeVal.tv_usec / 1000L);
13264
13265 #else /*!HAVE_GETTIMEOFDAY*/
13266 #if HAVE_FTIME
13267
13268 // include <sys/timeb.h> / moved to just above start of function
13269     struct timeb timeB;
13270
13271     ftime(&timeB);
13272     tm->sec = (long) timeB.time;
13273     tm->ms = (int) timeB.millitm;
13274
13275 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13276     tm->sec = (long) time(NULL);
13277     tm->ms = 0;
13278 #endif
13279 #endif
13280 }
13281
13282 /* Return the difference in milliseconds between two
13283    time marks.  We assume the difference will fit in a long!
13284 */
13285 long
13286 SubtractTimeMarks(tm2, tm1)
13287      TimeMark *tm2, *tm1;
13288 {
13289     return 1000L*(tm2->sec - tm1->sec) +
13290            (long) (tm2->ms - tm1->ms);
13291 }
13292
13293
13294 /*
13295  * Code to manage the game clocks.
13296  *
13297  * In tournament play, black starts the clock and then white makes a move.
13298  * We give the human user a slight advantage if he is playing white---the
13299  * clocks don't run until he makes his first move, so it takes zero time.
13300  * Also, we don't account for network lag, so we could get out of sync
13301  * with GNU Chess's clock -- but then, referees are always right.
13302  */
13303
13304 static TimeMark tickStartTM;
13305 static long intendedTickLength;
13306
13307 long
13308 NextTickLength(timeRemaining)
13309      long timeRemaining;
13310 {
13311     long nominalTickLength, nextTickLength;
13312
13313     if (timeRemaining > 0L && timeRemaining <= 10000L)
13314       nominalTickLength = 100L;
13315     else
13316       nominalTickLength = 1000L;
13317     nextTickLength = timeRemaining % nominalTickLength;
13318     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13319
13320     return nextTickLength;
13321 }
13322
13323 /* Adjust clock one minute up or down */
13324 void
13325 AdjustClock(Boolean which, int dir)
13326 {
13327     if(which) blackTimeRemaining += 60000*dir;
13328     else      whiteTimeRemaining += 60000*dir;
13329     DisplayBothClocks();
13330 }
13331
13332 /* Stop clocks and reset to a fresh time control */
13333 void
13334 ResetClocks()
13335 {
13336     (void) StopClockTimer();
13337     if (appData.icsActive) {
13338         whiteTimeRemaining = blackTimeRemaining = 0;
13339     } else { /* [HGM] correct new time quote for time odds */
13340         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13341         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13342     }
13343     if (whiteFlag || blackFlag) {
13344         DisplayTitle("");
13345         whiteFlag = blackFlag = FALSE;
13346     }
13347     DisplayBothClocks();
13348 }
13349
13350 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13351
13352 /* Decrement running clock by amount of time that has passed */
13353 void
13354 DecrementClocks()
13355 {
13356     long timeRemaining;
13357     long lastTickLength, fudge;
13358     TimeMark now;
13359
13360     if (!appData.clockMode) return;
13361     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13362
13363     GetTimeMark(&now);
13364
13365     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13366
13367     /* Fudge if we woke up a little too soon */
13368     fudge = intendedTickLength - lastTickLength;
13369     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13370
13371     if (WhiteOnMove(forwardMostMove)) {
13372         if(whiteNPS >= 0) lastTickLength = 0;
13373         timeRemaining = whiteTimeRemaining -= lastTickLength;
13374         DisplayWhiteClock(whiteTimeRemaining - fudge,
13375                           WhiteOnMove(currentMove));
13376     } else {
13377         if(blackNPS >= 0) lastTickLength = 0;
13378         timeRemaining = blackTimeRemaining -= lastTickLength;
13379         DisplayBlackClock(blackTimeRemaining - fudge,
13380                           !WhiteOnMove(currentMove));
13381     }
13382
13383     if (CheckFlags()) return;
13384
13385     tickStartTM = now;
13386     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13387     StartClockTimer(intendedTickLength);
13388
13389     /* if the time remaining has fallen below the alarm threshold, sound the
13390      * alarm. if the alarm has sounded and (due to a takeback or time control
13391      * with increment) the time remaining has increased to a level above the
13392      * threshold, reset the alarm so it can sound again.
13393      */
13394
13395     if (appData.icsActive && appData.icsAlarm) {
13396
13397         /* make sure we are dealing with the user's clock */
13398         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13399                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13400            )) return;
13401
13402         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13403             alarmSounded = FALSE;
13404         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13405             PlayAlarmSound();
13406             alarmSounded = TRUE;
13407         }
13408     }
13409 }
13410
13411
13412 /* A player has just moved, so stop the previously running
13413    clock and (if in clock mode) start the other one.
13414    We redisplay both clocks in case we're in ICS mode, because
13415    ICS gives us an update to both clocks after every move.
13416    Note that this routine is called *after* forwardMostMove
13417    is updated, so the last fractional tick must be subtracted
13418    from the color that is *not* on move now.
13419 */
13420 void
13421 SwitchClocks()
13422 {
13423     long lastTickLength;
13424     TimeMark now;
13425     int flagged = FALSE;
13426
13427     GetTimeMark(&now);
13428
13429     if (StopClockTimer() && appData.clockMode) {
13430         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13431         if (WhiteOnMove(forwardMostMove)) {
13432             if(blackNPS >= 0) lastTickLength = 0;
13433             blackTimeRemaining -= lastTickLength;
13434            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13435 //         if(pvInfoList[forwardMostMove-1].time == -1)
13436                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13437                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13438         } else {
13439            if(whiteNPS >= 0) lastTickLength = 0;
13440            whiteTimeRemaining -= lastTickLength;
13441            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13442 //         if(pvInfoList[forwardMostMove-1].time == -1)
13443                  pvInfoList[forwardMostMove-1].time =
13444                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13445         }
13446         flagged = CheckFlags();
13447     }
13448     CheckTimeControl();
13449
13450     if (flagged || !appData.clockMode) return;
13451
13452     switch (gameMode) {
13453       case MachinePlaysBlack:
13454       case MachinePlaysWhite:
13455       case BeginningOfGame:
13456         if (pausing) return;
13457         break;
13458
13459       case EditGame:
13460       case PlayFromGameFile:
13461       case IcsExamining:
13462         return;
13463
13464       default:
13465         break;
13466     }
13467
13468     tickStartTM = now;
13469     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13470       whiteTimeRemaining : blackTimeRemaining);
13471     StartClockTimer(intendedTickLength);
13472 }
13473
13474
13475 /* Stop both clocks */
13476 void
13477 StopClocks()
13478 {
13479     long lastTickLength;
13480     TimeMark now;
13481
13482     if (!StopClockTimer()) return;
13483     if (!appData.clockMode) return;
13484
13485     GetTimeMark(&now);
13486
13487     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13488     if (WhiteOnMove(forwardMostMove)) {
13489         if(whiteNPS >= 0) lastTickLength = 0;
13490         whiteTimeRemaining -= lastTickLength;
13491         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13492     } else {
13493         if(blackNPS >= 0) lastTickLength = 0;
13494         blackTimeRemaining -= lastTickLength;
13495         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13496     }
13497     CheckFlags();
13498 }
13499
13500 /* Start clock of player on move.  Time may have been reset, so
13501    if clock is already running, stop and restart it. */
13502 void
13503 StartClocks()
13504 {
13505     (void) StopClockTimer(); /* in case it was running already */
13506     DisplayBothClocks();
13507     if (CheckFlags()) return;
13508
13509     if (!appData.clockMode) return;
13510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13511
13512     GetTimeMark(&tickStartTM);
13513     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13514       whiteTimeRemaining : blackTimeRemaining);
13515
13516    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13517     whiteNPS = blackNPS = -1;
13518     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13519        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13520         whiteNPS = first.nps;
13521     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13522        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13523         blackNPS = first.nps;
13524     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13525         whiteNPS = second.nps;
13526     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13527         blackNPS = second.nps;
13528     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13529
13530     StartClockTimer(intendedTickLength);
13531 }
13532
13533 char *
13534 TimeString(ms)
13535      long ms;
13536 {
13537     long second, minute, hour, day;
13538     char *sign = "";
13539     static char buf[32];
13540
13541     if (ms > 0 && ms <= 9900) {
13542       /* convert milliseconds to tenths, rounding up */
13543       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13544
13545       sprintf(buf, " %03.1f ", tenths/10.0);
13546       return buf;
13547     }
13548
13549     /* convert milliseconds to seconds, rounding up */
13550     /* use floating point to avoid strangeness of integer division
13551        with negative dividends on many machines */
13552     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13553
13554     if (second < 0) {
13555         sign = "-";
13556         second = -second;
13557     }
13558
13559     day = second / (60 * 60 * 24);
13560     second = second % (60 * 60 * 24);
13561     hour = second / (60 * 60);
13562     second = second % (60 * 60);
13563     minute = second / 60;
13564     second = second % 60;
13565
13566     if (day > 0)
13567       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13568               sign, day, hour, minute, second);
13569     else if (hour > 0)
13570       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13571     else
13572       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13573
13574     return buf;
13575 }
13576
13577
13578 /*
13579  * This is necessary because some C libraries aren't ANSI C compliant yet.
13580  */
13581 char *
13582 StrStr(string, match)
13583      char *string, *match;
13584 {
13585     int i, length;
13586
13587     length = strlen(match);
13588
13589     for (i = strlen(string) - length; i >= 0; i--, string++)
13590       if (!strncmp(match, string, length))
13591         return string;
13592
13593     return NULL;
13594 }
13595
13596 char *
13597 StrCaseStr(string, match)
13598      char *string, *match;
13599 {
13600     int i, j, length;
13601
13602     length = strlen(match);
13603
13604     for (i = strlen(string) - length; i >= 0; i--, string++) {
13605         for (j = 0; j < length; j++) {
13606             if (ToLower(match[j]) != ToLower(string[j]))
13607               break;
13608         }
13609         if (j == length) return string;
13610     }
13611
13612     return NULL;
13613 }
13614
13615 #ifndef _amigados
13616 int
13617 StrCaseCmp(s1, s2)
13618      char *s1, *s2;
13619 {
13620     char c1, c2;
13621
13622     for (;;) {
13623         c1 = ToLower(*s1++);
13624         c2 = ToLower(*s2++);
13625         if (c1 > c2) return 1;
13626         if (c1 < c2) return -1;
13627         if (c1 == NULLCHAR) return 0;
13628     }
13629 }
13630
13631
13632 int
13633 ToLower(c)
13634      int c;
13635 {
13636     return isupper(c) ? tolower(c) : c;
13637 }
13638
13639
13640 int
13641 ToUpper(c)
13642      int c;
13643 {
13644     return islower(c) ? toupper(c) : c;
13645 }
13646 #endif /* !_amigados    */
13647
13648 char *
13649 StrSave(s)
13650      char *s;
13651 {
13652     char *ret;
13653
13654     if ((ret = (char *) malloc(strlen(s) + 1))) {
13655         strcpy(ret, s);
13656     }
13657     return ret;
13658 }
13659
13660 char *
13661 StrSavePtr(s, savePtr)
13662      char *s, **savePtr;
13663 {
13664     if (*savePtr) {
13665         free(*savePtr);
13666     }
13667     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13668         strcpy(*savePtr, s);
13669     }
13670     return(*savePtr);
13671 }
13672
13673 char *
13674 PGNDate()
13675 {
13676     time_t clock;
13677     struct tm *tm;
13678     char buf[MSG_SIZ];
13679
13680     clock = time((time_t *)NULL);
13681     tm = localtime(&clock);
13682     sprintf(buf, "%04d.%02d.%02d",
13683             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13684     return StrSave(buf);
13685 }
13686
13687
13688 char *
13689 PositionToFEN(move, overrideCastling)
13690      int move;
13691      char *overrideCastling;
13692 {
13693     int i, j, fromX, fromY, toX, toY;
13694     int whiteToPlay;
13695     char buf[128];
13696     char *p, *q;
13697     int emptycount;
13698     ChessSquare piece;
13699
13700     whiteToPlay = (gameMode == EditPosition) ?
13701       !blackPlaysFirst : (move % 2 == 0);
13702     p = buf;
13703
13704     /* Piece placement data */
13705     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13706         emptycount = 0;
13707         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13708             if (boards[move][i][j] == EmptySquare) {
13709                 emptycount++;
13710             } else { ChessSquare piece = boards[move][i][j];
13711                 if (emptycount > 0) {
13712                     if(emptycount<10) /* [HGM] can be >= 10 */
13713                         *p++ = '0' + emptycount;
13714                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13715                     emptycount = 0;
13716                 }
13717                 if(PieceToChar(piece) == '+') {
13718                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13719                     *p++ = '+';
13720                     piece = (ChessSquare)(DEMOTED piece);
13721                 }
13722                 *p++ = PieceToChar(piece);
13723                 if(p[-1] == '~') {
13724                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13725                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13726                     *p++ = '~';
13727                 }
13728             }
13729         }
13730         if (emptycount > 0) {
13731             if(emptycount<10) /* [HGM] can be >= 10 */
13732                 *p++ = '0' + emptycount;
13733             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13734             emptycount = 0;
13735         }
13736         *p++ = '/';
13737     }
13738     *(p - 1) = ' ';
13739
13740     /* [HGM] print Crazyhouse or Shogi holdings */
13741     if( gameInfo.holdingsWidth ) {
13742         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13743         q = p;
13744         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13745             piece = boards[move][i][BOARD_WIDTH-1];
13746             if( piece != EmptySquare )
13747               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13748                   *p++ = PieceToChar(piece);
13749         }
13750         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13751             piece = boards[move][BOARD_HEIGHT-i-1][0];
13752             if( piece != EmptySquare )
13753               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13754                   *p++ = PieceToChar(piece);
13755         }
13756
13757         if( q == p ) *p++ = '-';
13758         *p++ = ']';
13759         *p++ = ' ';
13760     }
13761
13762     /* Active color */
13763     *p++ = whiteToPlay ? 'w' : 'b';
13764     *p++ = ' ';
13765
13766   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13767     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13768   } else {
13769   if(nrCastlingRights) {
13770      q = p;
13771      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13772        /* [HGM] write directly from rights */
13773            if(castlingRights[move][2] >= 0 &&
13774               castlingRights[move][0] >= 0   )
13775                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13776            if(castlingRights[move][2] >= 0 &&
13777               castlingRights[move][1] >= 0   )
13778                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13779            if(castlingRights[move][5] >= 0 &&
13780               castlingRights[move][3] >= 0   )
13781                 *p++ = castlingRights[move][3] + AAA;
13782            if(castlingRights[move][5] >= 0 &&
13783               castlingRights[move][4] >= 0   )
13784                 *p++ = castlingRights[move][4] + AAA;
13785      } else {
13786
13787         /* [HGM] write true castling rights */
13788         if( nrCastlingRights == 6 ) {
13789             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13790                castlingRights[move][2] >= 0  ) *p++ = 'K';
13791             if(castlingRights[move][1] == BOARD_LEFT &&
13792                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13793             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13794                castlingRights[move][5] >= 0  ) *p++ = 'k';
13795             if(castlingRights[move][4] == BOARD_LEFT &&
13796                castlingRights[move][5] >= 0  ) *p++ = 'q';
13797         }
13798      }
13799      if (q == p) *p++ = '-'; /* No castling rights */
13800      *p++ = ' ';
13801   }
13802
13803   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13804      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13805     /* En passant target square */
13806     if (move > backwardMostMove) {
13807         fromX = moveList[move - 1][0] - AAA;
13808         fromY = moveList[move - 1][1] - ONE;
13809         toX = moveList[move - 1][2] - AAA;
13810         toY = moveList[move - 1][3] - ONE;
13811         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13812             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13813             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13814             fromX == toX) {
13815             /* 2-square pawn move just happened */
13816             *p++ = toX + AAA;
13817             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13818         } else {
13819             *p++ = '-';
13820         }
13821     } else if(move == backwardMostMove) {
13822         // [HGM] perhaps we should always do it like this, and forget the above?
13823         if(epStatus[move] >= 0) {
13824             *p++ = epStatus[move] + AAA;
13825             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13826         } else {
13827             *p++ = '-';
13828         }
13829     } else {
13830         *p++ = '-';
13831     }
13832     *p++ = ' ';
13833   }
13834   }
13835
13836     /* [HGM] find reversible plies */
13837     {   int i = 0, j=move;
13838
13839         if (appData.debugMode) { int k;
13840             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13841             for(k=backwardMostMove; k<=forwardMostMove; k++)
13842                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13843
13844         }
13845
13846         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13847         if( j == backwardMostMove ) i += initialRulePlies;
13848         sprintf(p, "%d ", i);
13849         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13850     }
13851     /* Fullmove number */
13852     sprintf(p, "%d", (move / 2) + 1);
13853
13854     return StrSave(buf);
13855 }
13856
13857 Boolean
13858 ParseFEN(board, blackPlaysFirst, fen)
13859     Board board;
13860      int *blackPlaysFirst;
13861      char *fen;
13862 {
13863     int i, j;
13864     char *p;
13865     int emptycount;
13866     ChessSquare piece;
13867
13868     p = fen;
13869
13870     /* [HGM] by default clear Crazyhouse holdings, if present */
13871     if(gameInfo.holdingsWidth) {
13872        for(i=0; i<BOARD_HEIGHT; i++) {
13873            board[i][0]             = EmptySquare; /* black holdings */
13874            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13875            board[i][1]             = (ChessSquare) 0; /* black counts */
13876            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13877        }
13878     }
13879
13880     /* Piece placement data */
13881     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13882         j = 0;
13883         for (;;) {
13884             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13885                 if (*p == '/') p++;
13886                 emptycount = gameInfo.boardWidth - j;
13887                 while (emptycount--)
13888                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13889                 break;
13890 #if(BOARD_SIZE >= 10)
13891             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13892                 p++; emptycount=10;
13893                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13894                 while (emptycount--)
13895                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13896 #endif
13897             } else if (isdigit(*p)) {
13898                 emptycount = *p++ - '0';
13899                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13900                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13901                 while (emptycount--)
13902                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13903             } else if (*p == '+' || isalpha(*p)) {
13904                 if (j >= gameInfo.boardWidth) return FALSE;
13905                 if(*p=='+') {
13906                     piece = CharToPiece(*++p);
13907                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13908                     piece = (ChessSquare) (PROMOTED piece ); p++;
13909                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13910                 } else piece = CharToPiece(*p++);
13911
13912                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13913                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13914                     piece = (ChessSquare) (PROMOTED piece);
13915                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13916                     p++;
13917                 }
13918                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13919             } else {
13920                 return FALSE;
13921             }
13922         }
13923     }
13924     while (*p == '/' || *p == ' ') p++;
13925
13926     /* [HGM] look for Crazyhouse holdings here */
13927     while(*p==' ') p++;
13928     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13929         if(*p == '[') p++;
13930         if(*p == '-' ) *p++; /* empty holdings */ else {
13931             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13932             /* if we would allow FEN reading to set board size, we would   */
13933             /* have to add holdings and shift the board read so far here   */
13934             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13935                 *p++;
13936                 if((int) piece >= (int) BlackPawn ) {
13937                     i = (int)piece - (int)BlackPawn;
13938                     i = PieceToNumber((ChessSquare)i);
13939                     if( i >= gameInfo.holdingsSize ) return FALSE;
13940                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13941                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13942                 } else {
13943                     i = (int)piece - (int)WhitePawn;
13944                     i = PieceToNumber((ChessSquare)i);
13945                     if( i >= gameInfo.holdingsSize ) return FALSE;
13946                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13947                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13948                 }
13949             }
13950         }
13951         if(*p == ']') *p++;
13952     }
13953
13954     while(*p == ' ') p++;
13955
13956     /* Active color */
13957     switch (*p++) {
13958       case 'w':
13959         *blackPlaysFirst = FALSE;
13960         break;
13961       case 'b':
13962         *blackPlaysFirst = TRUE;
13963         break;
13964       default:
13965         return FALSE;
13966     }
13967
13968     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13969     /* return the extra info in global variiables             */
13970
13971     /* set defaults in case FEN is incomplete */
13972     FENepStatus = EP_UNKNOWN;
13973     for(i=0; i<nrCastlingRights; i++ ) {
13974         FENcastlingRights[i] =
13975             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13976     }   /* assume possible unless obviously impossible */
13977     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13978     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13979     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13980     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13981     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13982     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13983     FENrulePlies = 0;
13984
13985     while(*p==' ') p++;
13986     if(nrCastlingRights) {
13987       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13988           /* castling indicator present, so default becomes no castlings */
13989           for(i=0; i<nrCastlingRights; i++ ) {
13990                  FENcastlingRights[i] = -1;
13991           }
13992       }
13993       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13994              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13995              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13996              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13997         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13998
13999         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14000             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14001             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14002         }
14003         switch(c) {
14004           case'K':
14005               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14006               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
14007               FENcastlingRights[2] = whiteKingFile;
14008               break;
14009           case'Q':
14010               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14011               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14012               FENcastlingRights[2] = whiteKingFile;
14013               break;
14014           case'k':
14015               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14016               FENcastlingRights[3] = i != blackKingFile ? i : -1;
14017               FENcastlingRights[5] = blackKingFile;
14018               break;
14019           case'q':
14020               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14021               FENcastlingRights[4] = i != blackKingFile ? i : -1;
14022               FENcastlingRights[5] = blackKingFile;
14023           case '-':
14024               break;
14025           default: /* FRC castlings */
14026               if(c >= 'a') { /* black rights */
14027                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14028                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14029                   if(i == BOARD_RGHT) break;
14030                   FENcastlingRights[5] = i;
14031                   c -= AAA;
14032                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14033                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14034                   if(c > i)
14035                       FENcastlingRights[3] = c;
14036                   else
14037                       FENcastlingRights[4] = c;
14038               } else { /* white rights */
14039                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14040                     if(board[0][i] == WhiteKing) break;
14041                   if(i == BOARD_RGHT) break;
14042                   FENcastlingRights[2] = i;
14043                   c -= AAA - 'a' + 'A';
14044                   if(board[0][c] >= WhiteKing) break;
14045                   if(c > i)
14046                       FENcastlingRights[0] = c;
14047                   else
14048                       FENcastlingRights[1] = c;
14049               }
14050         }
14051       }
14052     if (appData.debugMode) {
14053         fprintf(debugFP, "FEN castling rights:");
14054         for(i=0; i<nrCastlingRights; i++)
14055         fprintf(debugFP, " %d", FENcastlingRights[i]);
14056         fprintf(debugFP, "\n");
14057     }
14058
14059       while(*p==' ') p++;
14060     }
14061
14062     /* read e.p. field in games that know e.p. capture */
14063     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14064        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14065       if(*p=='-') {
14066         p++; FENepStatus = EP_NONE;
14067       } else {
14068          char c = *p++ - AAA;
14069
14070          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14071          if(*p >= '0' && *p <='9') *p++;
14072          FENepStatus = c;
14073       }
14074     }
14075
14076
14077     if(sscanf(p, "%d", &i) == 1) {
14078         FENrulePlies = i; /* 50-move ply counter */
14079         /* (The move number is still ignored)    */
14080     }
14081
14082     return TRUE;
14083 }
14084
14085 void
14086 EditPositionPasteFEN(char *fen)
14087 {
14088   if (fen != NULL) {
14089     Board initial_position;
14090
14091     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14092       DisplayError(_("Bad FEN position in clipboard"), 0);
14093       return ;
14094     } else {
14095       int savedBlackPlaysFirst = blackPlaysFirst;
14096       EditPositionEvent();
14097       blackPlaysFirst = savedBlackPlaysFirst;
14098       CopyBoard(boards[0], initial_position);
14099           /* [HGM] copy FEN attributes as well */
14100           {   int i;
14101               initialRulePlies = FENrulePlies;
14102               epStatus[0] = FENepStatus;
14103               for( i=0; i<nrCastlingRights; i++ )
14104                   castlingRights[0][i] = FENcastlingRights[i];
14105           }
14106       EditPositionDone();
14107       DisplayBothClocks();
14108       DrawPosition(FALSE, boards[currentMove]);
14109     }
14110   }
14111 }
14112
14113 static char cseq[12] = "\\   ";
14114
14115 Boolean set_cont_sequence(char *new_seq)
14116 {
14117     int len;
14118     Boolean ret;
14119
14120     // handle bad attempts to set the sequence
14121         if (!new_seq)
14122                 return 0; // acceptable error - no debug
14123
14124     len = strlen(new_seq);
14125     ret = (len > 0) && (len < sizeof(cseq));
14126     if (ret)
14127         strcpy(cseq, new_seq);
14128     else if (appData.debugMode)
14129         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14130     return ret;
14131 }
14132
14133 /*
14134     reformat a source message so words don't cross the width boundary.  internal
14135     newlines are not removed.  returns the wrapped size (no null character unless
14136     included in source message).  If dest is NULL, only calculate the size required
14137     for the dest buffer.  lp argument indicats line position upon entry, and it's
14138     passed back upon exit.
14139 */
14140 int wrap(char *dest, char *src, int count, int width, int *lp)
14141 {
14142     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14143
14144     cseq_len = strlen(cseq);
14145     old_line = line = *lp;
14146     ansi = len = clen = 0;
14147
14148     for (i=0; i < count; i++)
14149     {
14150         if (src[i] == '\033')
14151             ansi = 1;
14152
14153         // if we hit the width, back up
14154         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14155         {
14156             // store i & len in case the word is too long
14157             old_i = i, old_len = len;
14158
14159             // find the end of the last word
14160             while (i && src[i] != ' ' && src[i] != '\n')
14161             {
14162                 i--;
14163                 len--;
14164             }
14165
14166             // word too long?  restore i & len before splitting it
14167             if ((old_i-i+clen) >= width)
14168             {
14169                 i = old_i;
14170                 len = old_len;
14171             }
14172
14173             // extra space?
14174             if (i && src[i-1] == ' ')
14175                 len--;
14176
14177             if (src[i] != ' ' && src[i] != '\n')
14178             {
14179                 i--;
14180                 if (len)
14181                     len--;
14182             }
14183
14184             // now append the newline and continuation sequence
14185             if (dest)
14186                 dest[len] = '\n';
14187             len++;
14188             if (dest)
14189                 strncpy(dest+len, cseq, cseq_len);
14190             len += cseq_len;
14191             line = cseq_len;
14192             clen = cseq_len;
14193             continue;
14194         }
14195
14196         if (dest)
14197             dest[len] = src[i];
14198         len++;
14199         if (!ansi)
14200             line++;
14201         if (src[i] == '\n')
14202             line = 0;
14203         if (src[i] == 'm')
14204             ansi = 0;
14205     }
14206     if (dest && appData.debugMode)
14207     {
14208         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14209             count, width, line, len, *lp);
14210         show_bytes(debugFP, src, count);
14211         fprintf(debugFP, "\ndest: ");
14212         show_bytes(debugFP, dest, len);
14213         fprintf(debugFP, "\n");
14214     }
14215     *lp = dest ? line : old_line;
14216
14217     return len;
14218 }