Merge commit 'v4.4.1.20091019' 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); // [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
1908     if( (int)lowestPiece >= BlackPawn ) {
1909         holdingsColumn = 0;
1910         countsColumn = 1;
1911         holdingsStartRow = BOARD_HEIGHT-1;
1912         direction = -1;
1913     } else {
1914         holdingsColumn = BOARD_WIDTH-1;
1915         countsColumn = BOARD_WIDTH-2;
1916         holdingsStartRow = 0;
1917         direction = 1;
1918     }
1919
1920     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921         board[i][holdingsColumn] = EmptySquare;
1922         board[i][countsColumn]   = (ChessSquare) 0;
1923     }
1924     while( (p=*holdings++) != NULLCHAR ) {
1925         piece = CharToPiece( ToUpper(p) );
1926         if(piece == EmptySquare) continue;
1927         /*j = (int) piece - (int) WhitePawn;*/
1928         j = PieceToNumber(piece);
1929         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930         if(j < 0) continue;               /* should not happen */
1931         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933         board[holdingsStartRow+j*direction][countsColumn]++;
1934     }
1935
1936 }
1937
1938
1939 void
1940 VariantSwitch(Board board, VariantClass newVariant)
1941 {
1942    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943
1944    startedFromPositionFile = FALSE;
1945    if(gameInfo.variant == newVariant) return;
1946
1947    /* [HGM] This routine is called each time an assignment is made to
1948     * gameInfo.variant during a game, to make sure the board sizes
1949     * are set to match the new variant. If that means adding or deleting
1950     * holdings, we shift the playing board accordingly
1951     * This kludge is needed because in ICS observe mode, we get boards
1952     * of an ongoing game without knowing the variant, and learn about the
1953     * latter only later. This can be because of the move list we requested,
1954     * in which case the game history is refilled from the beginning anyway,
1955     * but also when receiving holdings of a crazyhouse game. In the latter
1956     * case we want to add those holdings to the already received position.
1957     */
1958    
1959    if (appData.debugMode) {
1960      fprintf(debugFP, "Switch board from %s to %s\n",
1961              VariantName(gameInfo.variant), VariantName(newVariant));
1962      setbuf(debugFP, NULL);
1963    }
1964    shuffleOpenings = 0;       /* [HGM] shuffle */
1965    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966    switch(newVariant) 
1967      {
1968      case VariantShogi:
1969        newWidth = 9;  newHeight = 9;
1970        gameInfo.holdingsSize = 7;
1971      case VariantBughouse:
1972      case VariantCrazyhouse:
1973        newHoldingsWidth = 2; break;
1974      case VariantGreat:
1975        newWidth = 10;
1976      case VariantSuper:
1977        newHoldingsWidth = 2;
1978        gameInfo.holdingsSize = 8;
1979        return;
1980      case VariantGothic:
1981      case VariantCapablanca:
1982      case VariantCapaRandom:
1983        newWidth = 10;
1984      default:
1985        newHoldingsWidth = gameInfo.holdingsSize = 0;
1986      };
1987    
1988    if(newWidth  != gameInfo.boardWidth  ||
1989       newHeight != gameInfo.boardHeight ||
1990       newHoldingsWidth != gameInfo.holdingsWidth ) {
1991      
1992      /* shift position to new playing area, if needed */
1993      if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994        for(i=0; i<BOARD_HEIGHT; i++) 
1995          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1997              board[i][j];
1998        for(i=0; i<newHeight; i++) {
1999          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2001        }
2002      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003        for(i=0; i<BOARD_HEIGHT; i++)
2004          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2006              board[i][j];
2007      }
2008      gameInfo.boardWidth  = newWidth;
2009      gameInfo.boardHeight = newHeight;
2010      gameInfo.holdingsWidth = newHoldingsWidth;
2011      gameInfo.variant = newVariant;
2012      InitDrawingSizes(-2, 0);
2013      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2014    } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2015    
2016    DrawPosition(TRUE, boards[currentMove]);
2017 }
2018
2019 static int loggedOn = FALSE;
2020
2021 /*-- Game start info cache: --*/
2022 int gs_gamenum;
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\   ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2030
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2033
2034 void
2035 read_from_ics(isr, closure, data, count, error)
2036      InputSourceRef isr;
2037      VOIDSTAR closure;
2038      char *data;
2039      int count;
2040      int error;
2041 {
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2051
2052     static int started = STARTED_NONE;
2053     static char parse[20000];
2054     static int parse_pos = 0;
2055     static char buf[BUF_SIZE + 1];
2056     static int firstTime = TRUE, intfSet = FALSE;
2057     static ColorClass prevColor = ColorNormal;
2058     static int savingComment = FALSE;
2059     static int cmatch = 0; // continuation sequence match
2060     char *bp;
2061     char str[500];
2062     int i, oldi;
2063     int buf_len;
2064     int next_out;
2065     int tkind;
2066     int backup;    /* [DM] For zippy color lines */
2067     char *p;
2068     char talker[MSG_SIZ]; // [HGM] chat
2069     int channel;
2070
2071     if (appData.debugMode) {
2072       if (!error) {
2073         fprintf(debugFP, "<ICS: ");
2074         show_bytes(debugFP, data, count);
2075         fprintf(debugFP, "\n");
2076       }
2077     }
2078
2079     if (appData.debugMode) { int f = forwardMostMove;
2080         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2082     }
2083     if (count > 0) {
2084         /* If last read ended with a partial line that we couldn't parse,
2085            prepend it to the new read and try again. */
2086         if (leftover_len > 0) {
2087             for (i=0; i<leftover_len; i++)
2088               buf[i] = buf[leftover_start + i];
2089         }
2090
2091     /* copy new characters into the buffer */
2092     bp = buf + leftover_len;
2093     buf_len=leftover_len;
2094     for (i=0; i<count; i++)
2095     {
2096         // ignore these
2097         if (data[i] == '\r')
2098             continue;
2099
2100         // join lines split by ICS?
2101         if (!appData.noJoin)
2102         {
2103             /*
2104                 Joining just consists of finding matches against the
2105                 continuation sequence, and discarding that sequence
2106                 if found instead of copying it.  So, until a match
2107                 fails, there's nothing to do since it might be the
2108                 complete sequence, and thus, something we don't want
2109                 copied.
2110             */
2111             if (data[i] == cont_seq[cmatch])
2112             {
2113                 cmatch++;
2114                 if (cmatch == strlen(cont_seq))
2115                 {
2116                     cmatch = 0; // complete match.  just reset the counter
2117
2118                     /*
2119                         it's possible for the ICS to not include the space
2120                         at the end of the last word, making our [correct]
2121                         join operation fuse two separate words.  the server
2122                         does this when the space occurs at the width setting.
2123                     */
2124                     if (!buf_len || buf[buf_len-1] != ' ')
2125                     {
2126                         *bp++ = ' ';
2127                         buf_len++;
2128                     }
2129                 }
2130                 continue;
2131             }
2132             else if (cmatch)
2133             {
2134                 /*
2135                     match failed, so we have to copy what matched before
2136                     falling through and copying this character.  In reality,
2137                     this will only ever be just the newline character, but
2138                     it doesn't hurt to be precise.
2139                 */
2140                 strncpy(bp, cont_seq, cmatch);
2141                 bp += cmatch;
2142                 buf_len += cmatch;
2143                 cmatch = 0;
2144             }
2145         }
2146
2147         // copy this char
2148         *bp++ = data[i];
2149         buf_len++;
2150     }
2151
2152         buf[buf_len] = NULLCHAR;
2153         next_out = leftover_len;
2154         leftover_start = 0;
2155
2156         i = 0;
2157         while (i < buf_len) {
2158             /* Deal with part of the TELNET option negotiation
2159                protocol.  We refuse to do anything beyond the
2160                defaults, except that we allow the WILL ECHO option,
2161                which ICS uses to turn off password echoing when we are
2162                directly connected to it.  We reject this option
2163                if localLineEditing mode is on (always on in xboard)
2164                and we are talking to port 23, which might be a real
2165                telnet server that will try to keep WILL ECHO on permanently.
2166              */
2167             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2168                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2169                 unsigned char option;
2170                 oldi = i;
2171                 switch ((unsigned char) buf[++i]) {
2172                   case TN_WILL:
2173                     if (appData.debugMode)
2174                       fprintf(debugFP, "\n<WILL ");
2175                     switch (option = (unsigned char) buf[++i]) {
2176                       case TN_ECHO:
2177                         if (appData.debugMode)
2178                           fprintf(debugFP, "ECHO ");
2179                         /* Reply only if this is a change, according
2180                            to the protocol rules. */
2181                         if (remoteEchoOption) break;
2182                         if (appData.localLineEditing &&
2183                             atoi(appData.icsPort) == TN_PORT) {
2184                             TelnetRequest(TN_DONT, TN_ECHO);
2185                         } else {
2186                             EchoOff();
2187                             TelnetRequest(TN_DO, TN_ECHO);
2188                             remoteEchoOption = TRUE;
2189                         }
2190                         break;
2191                       default:
2192                         if (appData.debugMode)
2193                           fprintf(debugFP, "%d ", option);
2194                         /* Whatever this is, we don't want it. */
2195                         TelnetRequest(TN_DONT, option);
2196                         break;
2197                     }
2198                     break;
2199                   case TN_WONT:
2200                     if (appData.debugMode)
2201                       fprintf(debugFP, "\n<WONT ");
2202                     switch (option = (unsigned char) buf[++i]) {
2203                       case TN_ECHO:
2204                         if (appData.debugMode)
2205                           fprintf(debugFP, "ECHO ");
2206                         /* Reply only if this is a change, according
2207                            to the protocol rules. */
2208                         if (!remoteEchoOption) break;
2209                         EchoOn();
2210                         TelnetRequest(TN_DONT, TN_ECHO);
2211                         remoteEchoOption = FALSE;
2212                         break;
2213                       default:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "%d ", (unsigned char) option);
2216                         /* Whatever this is, it must already be turned
2217                            off, because we never agree to turn on
2218                            anything non-default, so according to the
2219                            protocol rules, we don't reply. */
2220                         break;
2221                     }
2222                     break;
2223                   case TN_DO:
2224                     if (appData.debugMode)
2225                       fprintf(debugFP, "\n<DO ");
2226                     switch (option = (unsigned char) buf[++i]) {
2227                       default:
2228                         /* Whatever this is, we refuse to do it. */
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         TelnetRequest(TN_WONT, option);
2232                         break;
2233                     }
2234                     break;
2235                   case TN_DONT:
2236                     if (appData.debugMode)
2237                       fprintf(debugFP, "\n<DONT ");
2238                     switch (option = (unsigned char) buf[++i]) {
2239                       default:
2240                         if (appData.debugMode)
2241                           fprintf(debugFP, "%d ", option);
2242                         /* Whatever this is, we are already not doing
2243                            it, because we never agree to do anything
2244                            non-default, so according to the protocol
2245                            rules, we don't reply. */
2246                         break;
2247                     }
2248                     break;
2249                   case TN_IAC:
2250                     if (appData.debugMode)
2251                       fprintf(debugFP, "\n<IAC ");
2252                     /* Doubled IAC; pass it through */
2253                     i--;
2254                     break;
2255                   default:
2256                     if (appData.debugMode)
2257                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2258                     /* Drop all other telnet commands on the floor */
2259                     break;
2260                 }
2261                 if (oldi > next_out)
2262                   SendToPlayer(&buf[next_out], oldi - next_out);
2263                 if (++i > next_out)
2264                   next_out = i;
2265                 continue;
2266             }
2267
2268             /* OK, this at least will *usually* work */
2269             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2270                 loggedOn = TRUE;
2271             }
2272
2273             if (loggedOn && !intfSet) {
2274                 if (ics_type == ICS_ICC) {
2275                   sprintf(str,
2276                           "/set-quietly interface %s\n/set-quietly style 12\n",
2277                           programVersion);
2278                 } else if (ics_type == ICS_CHESSNET) {
2279                   sprintf(str, "/style 12\n");
2280                 } else {
2281                   strcpy(str, "alias $ @\n$set interface ");
2282                   strcat(str, programVersion);
2283                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2284 #ifdef WIN32
2285                   strcat(str, "$iset nohighlight 1\n");
2286 #endif
2287                   strcat(str, "$iset lock 1\n$style 12\n");
2288                 }
2289                 SendToICS(str);
2290                 NotifyFrontendLogin();
2291                 intfSet = TRUE;
2292             }
2293
2294             if (started == STARTED_COMMENT) {
2295                 /* Accumulate characters in comment */
2296                 parse[parse_pos++] = buf[i];
2297                 if (buf[i] == '\n') {
2298                     parse[parse_pos] = NULLCHAR;
2299                     if(chattingPartner>=0) {
2300                         char mess[MSG_SIZ];
2301                         sprintf(mess, "%s%s", talker, parse);
2302                         OutputChatMessage(chattingPartner, mess);
2303                         chattingPartner = -1;
2304                     } else
2305                     if(!suppressKibitz) // [HGM] kibitz
2306                         AppendComment(forwardMostMove, StripHighlight(parse));
2307                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2308                         int nrDigit = 0, nrAlph = 0, i;
2309                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2310                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2311                         parse[parse_pos] = NULLCHAR;
2312                         // try to be smart: if it does not look like search info, it should go to
2313                         // ICS interaction window after all, not to engine-output window.
2314                         for(i=0; i<parse_pos; i++) { // count letters and digits
2315                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2316                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2317                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2318                         }
2319                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2320                             int depth=0; float score;
2321                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2322                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2323                                 pvInfoList[forwardMostMove-1].depth = depth;
2324                                 pvInfoList[forwardMostMove-1].score = 100*score;
2325                             }
2326                             OutputKibitz(suppressKibitz, parse);
2327                         } else {
2328                             char tmp[MSG_SIZ];
2329                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2330                             SendToPlayer(tmp, strlen(tmp));
2331                         }
2332                     }
2333                     started = STARTED_NONE;
2334                 } else {
2335                     /* Don't match patterns against characters in chatter */
2336                     i++;
2337                     continue;
2338                 }
2339             }
2340             if (started == STARTED_CHATTER) {
2341                 if (buf[i] != '\n') {
2342                     /* Don't match patterns against characters in chatter */
2343                     i++;
2344                     continue;
2345                 }
2346                 started = STARTED_NONE;
2347             }
2348
2349             /* Kludge to deal with rcmd protocol */
2350             if (firstTime && looking_at(buf, &i, "\001*")) {
2351                 DisplayFatalError(&buf[1], 0, 1);
2352                 continue;
2353             } else {
2354                 firstTime = FALSE;
2355             }
2356
2357             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2358                 ics_type = ICS_ICC;
2359                 ics_prefix = "/";
2360                 if (appData.debugMode)
2361                   fprintf(debugFP, "ics_type %d\n", ics_type);
2362                 continue;
2363             }
2364             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2365                 ics_type = ICS_FICS;
2366                 ics_prefix = "$";
2367                 if (appData.debugMode)
2368                   fprintf(debugFP, "ics_type %d\n", ics_type);
2369                 continue;
2370             }
2371             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2372                 ics_type = ICS_CHESSNET;
2373                 ics_prefix = "/";
2374                 if (appData.debugMode)
2375                   fprintf(debugFP, "ics_type %d\n", ics_type);
2376                 continue;
2377             }
2378
2379             if (!loggedOn &&
2380                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2381                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2382                  looking_at(buf, &i, "will be \"*\""))) {
2383               strcpy(ics_handle, star_match[0]);
2384               continue;
2385             }
2386
2387             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2388               char buf[MSG_SIZ];
2389               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2390               DisplayIcsInteractionTitle(buf);
2391               have_set_title = TRUE;
2392             }
2393
2394             /* skip finger notes */
2395             if (started == STARTED_NONE &&
2396                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2397                  (buf[i] == '1' && buf[i+1] == '0')) &&
2398                 buf[i+2] == ':' && buf[i+3] == ' ') {
2399               started = STARTED_CHATTER;
2400               i += 3;
2401               continue;
2402             }
2403
2404             /* skip formula vars */
2405             if (started == STARTED_NONE &&
2406                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2407               started = STARTED_CHATTER;
2408               i += 3;
2409               continue;
2410             }
2411
2412             oldi = i;
2413             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2414             if (appData.autoKibitz && started == STARTED_NONE &&
2415                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2416                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2417                 if(looking_at(buf, &i, "* kibitzes: ") &&
2418                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2419                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2420                         suppressKibitz = TRUE;
2421                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2422                                 && (gameMode == IcsPlayingWhite)) ||
2423                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2424                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2425                             started = STARTED_CHATTER; // own kibitz we simply discard
2426                         else {
2427                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2428                             parse_pos = 0; parse[0] = NULLCHAR;
2429                             savingComment = TRUE;
2430                             suppressKibitz = gameMode != IcsObserving ? 2 :
2431                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2432                         }
2433                         continue;
2434                 } else
2435                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2436                     started = STARTED_CHATTER;
2437                     suppressKibitz = TRUE;
2438                 }
2439             } // [HGM] kibitz: end of patch
2440
2441 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2442
2443             // [HGM] chat: intercept tells by users for which we have an open chat window
2444             channel = -1;
2445             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2446                                            looking_at(buf, &i, "* whispers:") ||
2447                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2448                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2449                 int p;
2450                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2451                 chattingPartner = -1;
2452
2453                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2454                 for(p=0; p<MAX_CHAT; p++) {
2455                     if(channel == atoi(chatPartner[p])) {
2456                     talker[0] = '['; strcat(talker, "]");
2457                     chattingPartner = p; break;
2458                     }
2459                 } else
2460                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2461                 for(p=0; p<MAX_CHAT; p++) {
2462                     if(!strcmp("WHISPER", chatPartner[p])) {
2463                         talker[0] = '['; strcat(talker, "]");
2464                         chattingPartner = p; break;
2465                     }
2466                 }
2467                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2468                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2469                     talker[0] = 0;
2470                     chattingPartner = p; break;
2471                 }
2472                 if(chattingPartner<0) i = oldi; else {
2473                     started = STARTED_COMMENT;
2474                     parse_pos = 0; parse[0] = NULLCHAR;
2475                     savingComment = TRUE;
2476                     suppressKibitz = TRUE;
2477                 }
2478             } // [HGM] chat: end of patch
2479
2480             if (appData.zippyTalk || appData.zippyPlay) {
2481                 /* [DM] Backup address for color zippy lines */
2482                 backup = i;
2483 #if ZIPPY
2484        #ifdef WIN32
2485                if (loggedOn == TRUE)
2486                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2487                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2488        #else
2489                 if (ZippyControl(buf, &i) ||
2490                     ZippyConverse(buf, &i) ||
2491                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2492                       loggedOn = TRUE;
2493                       if (!appData.colorize) continue;
2494                 }
2495        #endif
2496 #endif
2497             } // [DM] 'else { ' deleted
2498                 if (
2499                     /* Regular tells and says */
2500                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2501                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2502                     looking_at(buf, &i, "* says: ") ||
2503                     /* Don't color "message" or "messages" output */
2504                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2505                     looking_at(buf, &i, "*. * at *:*: ") ||
2506                     looking_at(buf, &i, "--* (*:*): ") ||
2507                     /* Message notifications (same color as tells) */
2508                     looking_at(buf, &i, "* has left a message ") ||
2509                     looking_at(buf, &i, "* just sent you a message:\n") ||
2510                     /* Whispers and kibitzes */
2511                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2512                     looking_at(buf, &i, "* kibitzes: ") ||
2513                     /* Channel tells */
2514                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2515
2516                   if (tkind == 1 && strchr(star_match[0], ':')) {
2517                       /* Avoid "tells you:" spoofs in channels */
2518                      tkind = 3;
2519                   }
2520                   if (star_match[0][0] == NULLCHAR ||
2521                       strchr(star_match[0], ' ') ||
2522                       (tkind == 3 && strchr(star_match[1], ' '))) {
2523                     /* Reject bogus matches */
2524                     i = oldi;
2525                   } else {
2526                     if (appData.colorize) {
2527                       if (oldi > next_out) {
2528                         SendToPlayer(&buf[next_out], oldi - next_out);
2529                         next_out = oldi;
2530                       }
2531                       switch (tkind) {
2532                       case 1:
2533                         Colorize(ColorTell, FALSE);
2534                         curColor = ColorTell;
2535                         break;
2536                       case 2:
2537                         Colorize(ColorKibitz, FALSE);
2538                         curColor = ColorKibitz;
2539                         break;
2540                       case 3:
2541                         p = strrchr(star_match[1], '(');
2542                         if (p == NULL) {
2543                           p = star_match[1];
2544                         } else {
2545                           p++;
2546                         }
2547                         if (atoi(p) == 1) {
2548                           Colorize(ColorChannel1, FALSE);
2549                           curColor = ColorChannel1;
2550                         } else {
2551                           Colorize(ColorChannel, FALSE);
2552                           curColor = ColorChannel;
2553                         }
2554                         break;
2555                       case 5:
2556                         curColor = ColorNormal;
2557                         break;
2558                       }
2559                     }
2560                     if (started == STARTED_NONE && appData.autoComment &&
2561                         (gameMode == IcsObserving ||
2562                          gameMode == IcsPlayingWhite ||
2563                          gameMode == IcsPlayingBlack)) {
2564                       parse_pos = i - oldi;
2565                       memcpy(parse, &buf[oldi], parse_pos);
2566                       parse[parse_pos] = NULLCHAR;
2567                       started = STARTED_COMMENT;
2568                       savingComment = TRUE;
2569                     } else {
2570                       started = STARTED_CHATTER;
2571                       savingComment = FALSE;
2572                     }
2573                     loggedOn = TRUE;
2574                     continue;
2575                   }
2576                 }
2577
2578                 if (looking_at(buf, &i, "* s-shouts: ") ||
2579                     looking_at(buf, &i, "* c-shouts: ")) {
2580                     if (appData.colorize) {
2581                         if (oldi > next_out) {
2582                             SendToPlayer(&buf[next_out], oldi - next_out);
2583                             next_out = oldi;
2584                         }
2585                         Colorize(ColorSShout, FALSE);
2586                         curColor = ColorSShout;
2587                     }
2588                     loggedOn = TRUE;
2589                     started = STARTED_CHATTER;
2590                     continue;
2591                 }
2592
2593                 if (looking_at(buf, &i, "--->")) {
2594                     loggedOn = TRUE;
2595                     continue;
2596                 }
2597
2598                 if (looking_at(buf, &i, "* shouts: ") ||
2599                     looking_at(buf, &i, "--> ")) {
2600                     if (appData.colorize) {
2601                         if (oldi > next_out) {
2602                             SendToPlayer(&buf[next_out], oldi - next_out);
2603                             next_out = oldi;
2604                         }
2605                         Colorize(ColorShout, FALSE);
2606                         curColor = ColorShout;
2607                     }
2608                     loggedOn = TRUE;
2609                     started = STARTED_CHATTER;
2610                     continue;
2611                 }
2612
2613                 if (looking_at( buf, &i, "Challenge:")) {
2614                     if (appData.colorize) {
2615                         if (oldi > next_out) {
2616                             SendToPlayer(&buf[next_out], oldi - next_out);
2617                             next_out = oldi;
2618                         }
2619                         Colorize(ColorChallenge, FALSE);
2620                         curColor = ColorChallenge;
2621                     }
2622                     loggedOn = TRUE;
2623                     continue;
2624                 }
2625
2626                 if (looking_at(buf, &i, "* offers you") ||
2627                     looking_at(buf, &i, "* offers to be") ||
2628                     looking_at(buf, &i, "* would like to") ||
2629                     looking_at(buf, &i, "* requests to") ||
2630                     looking_at(buf, &i, "Your opponent offers") ||
2631                     looking_at(buf, &i, "Your opponent requests")) {
2632
2633                     if (appData.colorize) {
2634                         if (oldi > next_out) {
2635                             SendToPlayer(&buf[next_out], oldi - next_out);
2636                             next_out = oldi;
2637                         }
2638                         Colorize(ColorRequest, FALSE);
2639                         curColor = ColorRequest;
2640                     }
2641                     continue;
2642                 }
2643
2644                 if (looking_at(buf, &i, "* (*) seeking")) {
2645                     if (appData.colorize) {
2646                         if (oldi > next_out) {
2647                             SendToPlayer(&buf[next_out], oldi - next_out);
2648                             next_out = oldi;
2649                         }
2650                         Colorize(ColorSeek, FALSE);
2651                         curColor = ColorSeek;
2652                     }
2653                     continue;
2654             }
2655
2656             if (looking_at(buf, &i, "\\   ")) {
2657                 if (prevColor != ColorNormal) {
2658                     if (oldi > next_out) {
2659                         SendToPlayer(&buf[next_out], oldi - next_out);
2660                         next_out = oldi;
2661                     }
2662                     Colorize(prevColor, TRUE);
2663                     curColor = prevColor;
2664                 }
2665                 if (savingComment) {
2666                     parse_pos = i - oldi;
2667                     memcpy(parse, &buf[oldi], parse_pos);
2668                     parse[parse_pos] = NULLCHAR;
2669                     started = STARTED_COMMENT;
2670                 } else {
2671                     started = STARTED_CHATTER;
2672                 }
2673                 continue;
2674             }
2675
2676             if (looking_at(buf, &i, "Black Strength :") ||
2677                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2678                 looking_at(buf, &i, "<10>") ||
2679                 looking_at(buf, &i, "#@#")) {
2680                 /* Wrong board style */
2681                 loggedOn = TRUE;
2682                 SendToICS(ics_prefix);
2683                 SendToICS("set style 12\n");
2684                 SendToICS(ics_prefix);
2685                 SendToICS("refresh\n");
2686                 continue;
2687             }
2688
2689             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2690                 ICSInitScript();
2691                 have_sent_ICS_logon = 1;
2692                 continue;
2693             }
2694
2695             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2696                 (looking_at(buf, &i, "\n<12> ") ||
2697                  looking_at(buf, &i, "<12> "))) {
2698                 loggedOn = TRUE;
2699                 if (oldi > next_out) {
2700                     SendToPlayer(&buf[next_out], oldi - next_out);
2701                 }
2702                 next_out = i;
2703                 started = STARTED_BOARD;
2704                 parse_pos = 0;
2705                 continue;
2706             }
2707
2708             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2709                 looking_at(buf, &i, "<b1> ")) {
2710                 if (oldi > next_out) {
2711                     SendToPlayer(&buf[next_out], oldi - next_out);
2712                 }
2713                 next_out = i;
2714                 started = STARTED_HOLDINGS;
2715                 parse_pos = 0;
2716                 continue;
2717             }
2718
2719             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2720                 loggedOn = TRUE;
2721                 /* Header for a move list -- first line */
2722
2723                 switch (ics_getting_history) {
2724                   case H_FALSE:
2725                     switch (gameMode) {
2726                       case IcsIdle:
2727                       case BeginningOfGame:
2728                         /* User typed "moves" or "oldmoves" while we
2729                            were idle.  Pretend we asked for these
2730                            moves and soak them up so user can step
2731                            through them and/or save them.
2732                            */
2733                         Reset(FALSE, TRUE);
2734                         gameMode = IcsObserving;
2735                         ModeHighlight();
2736                         ics_gamenum = -1;
2737                         ics_getting_history = H_GOT_UNREQ_HEADER;
2738                         break;
2739                       case EditGame: /*?*/
2740                       case EditPosition: /*?*/
2741                         /* Should above feature work in these modes too? */
2742                         /* For now it doesn't */
2743                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2744                         break;
2745                       default:
2746                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2747                         break;
2748                     }
2749                     break;
2750                   case H_REQUESTED:
2751                     /* Is this the right one? */
2752                     if (gameInfo.white && gameInfo.black &&
2753                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2754                         strcmp(gameInfo.black, star_match[2]) == 0) {
2755                         /* All is well */
2756                         ics_getting_history = H_GOT_REQ_HEADER;
2757                     }
2758                     break;
2759                   case H_GOT_REQ_HEADER:
2760                   case H_GOT_UNREQ_HEADER:
2761                   case H_GOT_UNWANTED_HEADER:
2762                   case H_GETTING_MOVES:
2763                     /* Should not happen */
2764                     DisplayError(_("Error gathering move list: two headers"), 0);
2765                     ics_getting_history = H_FALSE;
2766                     break;
2767                 }
2768
2769                 /* Save player ratings into gameInfo if needed */
2770                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2771                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2772                     (gameInfo.whiteRating == -1 ||
2773                      gameInfo.blackRating == -1)) {
2774
2775                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2776                     gameInfo.blackRating = string_to_rating(star_match[3]);
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2779                               gameInfo.whiteRating, gameInfo.blackRating);
2780                 }
2781                 continue;
2782             }
2783
2784             if (looking_at(buf, &i,
2785               "* * match, initial time: * minute*, increment: * second")) {
2786                 /* Header for a move list -- second line */
2787                 /* Initial board will follow if this is a wild game */
2788                 if (gameInfo.event != NULL) free(gameInfo.event);
2789                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2790                 gameInfo.event = StrSave(str);
2791                 /* [HGM] we switched variant. Translate boards if needed. */
2792                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2793                 continue;
2794             }
2795
2796             if (looking_at(buf, &i, "Move  ")) {
2797                 /* Beginning of a move list */
2798                 switch (ics_getting_history) {
2799                   case H_FALSE:
2800                     /* Normally should not happen */
2801                     /* Maybe user hit reset while we were parsing */
2802                     break;
2803                   case H_REQUESTED:
2804                     /* Happens if we are ignoring a move list that is not
2805                      * the one we just requested.  Common if the user
2806                      * tries to observe two games without turning off
2807                      * getMoveList */
2808                     break;
2809                   case H_GETTING_MOVES:
2810                     /* Should not happen */
2811                     DisplayError(_("Error gathering move list: nested"), 0);
2812                     ics_getting_history = H_FALSE;
2813                     break;
2814                   case H_GOT_REQ_HEADER:
2815                     ics_getting_history = H_GETTING_MOVES;
2816                     started = STARTED_MOVES;
2817                     parse_pos = 0;
2818                     if (oldi > next_out) {
2819                         SendToPlayer(&buf[next_out], oldi - next_out);
2820                     }
2821                     break;
2822                   case H_GOT_UNREQ_HEADER:
2823                     ics_getting_history = H_GETTING_MOVES;
2824                     started = STARTED_MOVES_NOHIDE;
2825                     parse_pos = 0;
2826                     break;
2827                   case H_GOT_UNWANTED_HEADER:
2828                     ics_getting_history = H_FALSE;
2829                     break;
2830                 }
2831                 continue;
2832             }
2833
2834             if (looking_at(buf, &i, "% ") ||
2835                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2836                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2837                 savingComment = FALSE;
2838                 switch (started) {
2839                   case STARTED_MOVES:
2840                   case STARTED_MOVES_NOHIDE:
2841                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2842                     parse[parse_pos + i - oldi] = NULLCHAR;
2843                     ParseGameHistory(parse);
2844 #if ZIPPY
2845                     if (appData.zippyPlay && first.initDone) {
2846                         FeedMovesToProgram(&first, forwardMostMove);
2847                         if (gameMode == IcsPlayingWhite) {
2848                             if (WhiteOnMove(forwardMostMove)) {
2849                                 if (first.sendTime) {
2850                                   if (first.useColors) {
2851                                     SendToProgram("black\n", &first);
2852                                   }
2853                                   SendTimeRemaining(&first, TRUE);
2854                                 }
2855                                 if (first.useColors) {
2856                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2857                                 }
2858                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2859                                 first.maybeThinking = TRUE;
2860                             } else {
2861                                 if (first.usePlayother) {
2862                                   if (first.sendTime) {
2863                                     SendTimeRemaining(&first, TRUE);
2864                                   }
2865                                   SendToProgram("playother\n", &first);
2866                                   firstMove = FALSE;
2867                                 } else {
2868                                   firstMove = TRUE;
2869                                 }
2870                             }
2871                         } else if (gameMode == IcsPlayingBlack) {
2872                             if (!WhiteOnMove(forwardMostMove)) {
2873                                 if (first.sendTime) {
2874                                   if (first.useColors) {
2875                                     SendToProgram("white\n", &first);
2876                                   }
2877                                   SendTimeRemaining(&first, FALSE);
2878                                 }
2879                                 if (first.useColors) {
2880                                   SendToProgram("black\n", &first);
2881                                 }
2882                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2883                                 first.maybeThinking = TRUE;
2884                             } else {
2885                                 if (first.usePlayother) {
2886                                   if (first.sendTime) {
2887                                     SendTimeRemaining(&first, FALSE);
2888                                   }
2889                                   SendToProgram("playother\n", &first);
2890                                   firstMove = FALSE;
2891                                 } else {
2892                                   firstMove = TRUE;
2893                                 }
2894                             }
2895                         }
2896                     }
2897 #endif
2898                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2899                         /* Moves came from oldmoves or moves command
2900                            while we weren't doing anything else.
2901                            */
2902                         currentMove = forwardMostMove;
2903                         ClearHighlights();/*!!could figure this out*/
2904                         flipView = appData.flipView;
2905                         DrawPosition(FALSE, boards[currentMove]);
2906                         DisplayBothClocks();
2907                         sprintf(str, "%s vs. %s",
2908                                 gameInfo.white, gameInfo.black);
2909                         DisplayTitle(str);
2910                         gameMode = IcsIdle;
2911                     } else {
2912                         /* Moves were history of an active game */
2913                         if (gameInfo.resultDetails != NULL) {
2914                             free(gameInfo.resultDetails);
2915                             gameInfo.resultDetails = NULL;
2916                         }
2917                     }
2918                     HistorySet(parseList, backwardMostMove,
2919                                forwardMostMove, currentMove-1);
2920                     DisplayMove(currentMove - 1);
2921                     if (started == STARTED_MOVES) next_out = i;
2922                     started = STARTED_NONE;
2923                     ics_getting_history = H_FALSE;
2924                     break;
2925
2926                   case STARTED_OBSERVE:
2927                     started = STARTED_NONE;
2928                     SendToICS(ics_prefix);
2929                     SendToICS("refresh\n");
2930                     break;
2931
2932                   default:
2933                     break;
2934                 }
2935                 if(bookHit) { // [HGM] book: simulate book reply
2936                     static char bookMove[MSG_SIZ]; // a bit generous?
2937
2938                     programStats.nodes = programStats.depth = programStats.time =
2939                     programStats.score = programStats.got_only_move = 0;
2940                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2941
2942                     strcpy(bookMove, "move ");
2943                     strcat(bookMove, bookHit);
2944                     HandleMachineMove(bookMove, &first);
2945                 }
2946                 continue;
2947             }
2948
2949             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2950                  started == STARTED_HOLDINGS ||
2951                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2952                 /* Accumulate characters in move list or board */
2953                 parse[parse_pos++] = buf[i];
2954             }
2955
2956             /* Start of game messages.  Mostly we detect start of game
2957                when the first board image arrives.  On some versions
2958                of the ICS, though, we need to do a "refresh" after starting
2959                to observe in order to get the current board right away. */
2960             if (looking_at(buf, &i, "Adding game * to observation list")) {
2961                 started = STARTED_OBSERVE;
2962                 continue;
2963             }
2964
2965             /* Handle auto-observe */
2966             if (appData.autoObserve &&
2967                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2968                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2969                 char *player;
2970                 /* Choose the player that was highlighted, if any. */
2971                 if (star_match[0][0] == '\033' ||
2972                     star_match[1][0] != '\033') {
2973                     player = star_match[0];
2974                 } else {
2975                     player = star_match[2];
2976                 }
2977                 sprintf(str, "%sobserve %s\n",
2978                         ics_prefix, StripHighlightAndTitle(player));
2979                 SendToICS(str);
2980
2981                 /* Save ratings from notify string */
2982                 strcpy(player1Name, star_match[0]);
2983                 player1Rating = string_to_rating(star_match[1]);
2984                 strcpy(player2Name, star_match[2]);
2985                 player2Rating = string_to_rating(star_match[3]);
2986
2987                 if (appData.debugMode)
2988                   fprintf(debugFP,
2989                           "Ratings from 'Game notification:' %s %d, %s %d\n",
2990                           player1Name, player1Rating,
2991                           player2Name, player2Rating);
2992
2993                 continue;
2994             }
2995
2996             /* Deal with automatic examine mode after a game,
2997                and with IcsObserving -> IcsExamining transition */
2998             if (looking_at(buf, &i, "Entering examine mode for game *") ||
2999                 looking_at(buf, &i, "has made you an examiner of game *")) {
3000
3001                 int gamenum = atoi(star_match[0]);
3002                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3003                     gamenum == ics_gamenum) {
3004                     /* We were already playing or observing this game;
3005                        no need to refetch history */
3006                     gameMode = IcsExamining;
3007                     if (pausing) {
3008                         pauseExamForwardMostMove = forwardMostMove;
3009                     } else if (currentMove < forwardMostMove) {
3010                         ForwardInner(forwardMostMove);
3011                     }
3012                 } else {
3013                     /* I don't think this case really can happen */
3014                     SendToICS(ics_prefix);
3015                     SendToICS("refresh\n");
3016                 }
3017                 continue;
3018             }
3019
3020             /* Error messages */
3021 //          if (ics_user_moved) {
3022             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3023                 if (looking_at(buf, &i, "Illegal move") ||
3024                     looking_at(buf, &i, "Not a legal move") ||
3025                     looking_at(buf, &i, "Your king is in check") ||
3026                     looking_at(buf, &i, "It isn't your turn") ||
3027                     looking_at(buf, &i, "It is not your move")) {
3028                     /* Illegal move */
3029                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3030                         currentMove = --forwardMostMove;
3031                         DisplayMove(currentMove - 1); /* before DMError */
3032                         DrawPosition(FALSE, boards[currentMove]);
3033                         SwitchClocks();
3034                         DisplayBothClocks();
3035                     }
3036                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037                     ics_user_moved = 0;
3038                     continue;
3039                 }
3040             }
3041
3042             if (looking_at(buf, &i, "still have time") ||
3043                 looking_at(buf, &i, "not out of time") ||
3044                 looking_at(buf, &i, "either player is out of time") ||
3045                 looking_at(buf, &i, "has timeseal; checking")) {
3046                 /* We must have called his flag a little too soon */
3047                 whiteFlag = blackFlag = FALSE;
3048                 continue;
3049             }
3050
3051             if (looking_at(buf, &i, "added * seconds to") ||
3052                 looking_at(buf, &i, "seconds were added to")) {
3053                 /* Update the clocks */
3054                 SendToICS(ics_prefix);
3055                 SendToICS("refresh\n");
3056                 continue;
3057             }
3058
3059             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3060                 ics_clock_paused = TRUE;
3061                 StopClocks();
3062                 continue;
3063             }
3064
3065             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3066                 ics_clock_paused = FALSE;
3067                 StartClocks();
3068                 continue;
3069             }
3070
3071             /* Grab player ratings from the Creating: message.
3072                Note we have to check for the special case when
3073                the ICS inserts things like [white] or [black]. */
3074             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3075                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3076                 /* star_matches:
3077                    0    player 1 name (not necessarily white)
3078                    1    player 1 rating
3079                    2    empty, white, or black (IGNORED)
3080                    3    player 2 name (not necessarily black)
3081                    4    player 2 rating
3082
3083                    The names/ratings are sorted out when the game
3084                    actually starts (below).
3085                 */
3086                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3087                 player1Rating = string_to_rating(star_match[1]);
3088                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3089                 player2Rating = string_to_rating(star_match[4]);
3090
3091                 if (appData.debugMode)
3092                   fprintf(debugFP,
3093                           "Ratings from 'Creating:' %s %d, %s %d\n",
3094                           player1Name, player1Rating,
3095                           player2Name, player2Rating);
3096
3097                 continue;
3098             }
3099
3100             /* Improved generic start/end-of-game messages */
3101             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3102                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3103                 /* If tkind == 0: */
3104                 /* star_match[0] is the game number */
3105                 /*           [1] is the white player's name */
3106                 /*           [2] is the black player's name */
3107                 /* For end-of-game: */
3108                 /*           [3] is the reason for the game end */
3109                 /*           [4] is a PGN end game-token, preceded by " " */
3110                 /* For start-of-game: */
3111                 /*           [3] begins with "Creating" or "Continuing" */
3112                 /*           [4] is " *" or empty (don't care). */
3113                 int gamenum = atoi(star_match[0]);
3114                 char *whitename, *blackname, *why, *endtoken;
3115                 ChessMove endtype = (ChessMove) 0;
3116
3117                 if (tkind == 0) {
3118                   whitename = star_match[1];
3119                   blackname = star_match[2];
3120                   why = star_match[3];
3121                   endtoken = star_match[4];
3122                 } else {
3123                   whitename = star_match[1];
3124                   blackname = star_match[3];
3125                   why = star_match[5];
3126                   endtoken = star_match[6];
3127                 }
3128
3129                 /* Game start messages */
3130                 if (strncmp(why, "Creating ", 9) == 0 ||
3131                     strncmp(why, "Continuing ", 11) == 0) {
3132                     gs_gamenum = gamenum;
3133                     strcpy(gs_kind, strchr(why, ' ') + 1);
3134 #if ZIPPY
3135                     if (appData.zippyPlay) {
3136                         ZippyGameStart(whitename, blackname);
3137                     }
3138 #endif /*ZIPPY*/
3139                     continue;
3140                 }
3141
3142                 /* Game end messages */
3143                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3144                     ics_gamenum != gamenum) {
3145                     continue;
3146                 }
3147                 while (endtoken[0] == ' ') endtoken++;
3148                 switch (endtoken[0]) {
3149                   case '*':
3150                   default:
3151                     endtype = GameUnfinished;
3152                     break;
3153                   case '0':
3154                     endtype = BlackWins;
3155                     break;
3156                   case '1':
3157                     if (endtoken[1] == '/')
3158                       endtype = GameIsDrawn;
3159                     else
3160                       endtype = WhiteWins;
3161                     break;
3162                 }
3163                 GameEnds(endtype, why, GE_ICS);
3164 #if ZIPPY
3165                 if (appData.zippyPlay && first.initDone) {
3166                     ZippyGameEnd(endtype, why);
3167                     if (first.pr == NULL) {
3168                       /* Start the next process early so that we'll
3169                          be ready for the next challenge */
3170                       StartChessProgram(&first);
3171                     }
3172                     /* Send "new" early, in case this command takes
3173                        a long time to finish, so that we'll be ready
3174                        for the next challenge. */
3175                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3176                     Reset(TRUE, TRUE);
3177                 }
3178 #endif /*ZIPPY*/
3179                 continue;
3180             }
3181
3182             if (looking_at(buf, &i, "Removing game * from observation") ||
3183                 looking_at(buf, &i, "no longer observing game *") ||
3184                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3185                 if (gameMode == IcsObserving &&
3186                     atoi(star_match[0]) == ics_gamenum)
3187                   {
3188                       /* icsEngineAnalyze */
3189                       if (appData.icsEngineAnalyze) {
3190                             ExitAnalyzeMode();
3191                             ModeHighlight();
3192                       }
3193                       StopClocks();
3194                       gameMode = IcsIdle;
3195                       ics_gamenum = -1;
3196                       ics_user_moved = FALSE;
3197                   }
3198                 continue;
3199             }
3200
3201             if (looking_at(buf, &i, "no longer examining game *")) {
3202                 if (gameMode == IcsExamining &&
3203                     atoi(star_match[0]) == ics_gamenum)
3204                   {
3205                       gameMode = IcsIdle;
3206                       ics_gamenum = -1;
3207                       ics_user_moved = FALSE;
3208                   }
3209                 continue;
3210             }
3211
3212             /* Advance leftover_start past any newlines we find,
3213                so only partial lines can get reparsed */
3214             if (looking_at(buf, &i, "\n")) {
3215                 prevColor = curColor;
3216                 if (curColor != ColorNormal) {
3217                     if (oldi > next_out) {
3218                         SendToPlayer(&buf[next_out], oldi - next_out);
3219                         next_out = oldi;
3220                     }
3221                     Colorize(ColorNormal, FALSE);
3222                     curColor = ColorNormal;
3223                 }
3224                 if (started == STARTED_BOARD) {
3225                     started = STARTED_NONE;
3226                     parse[parse_pos] = NULLCHAR;
3227                     ParseBoard12(parse);
3228                     ics_user_moved = 0;
3229
3230                     /* Send premove here */
3231                     if (appData.premove) {
3232                       char str[MSG_SIZ];
3233                       if (currentMove == 0 &&
3234                           gameMode == IcsPlayingWhite &&
3235                           appData.premoveWhite) {
3236                         sprintf(str, "%s%s\n", ics_prefix,
3237                                 appData.premoveWhiteText);
3238                         if (appData.debugMode)
3239                           fprintf(debugFP, "Sending premove:\n");
3240                         SendToICS(str);
3241                       } else if (currentMove == 1 &&
3242                                  gameMode == IcsPlayingBlack &&
3243                                  appData.premoveBlack) {
3244                         sprintf(str, "%s%s\n", ics_prefix,
3245                                 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                         if (looking_at(buf, &i, "*% ")) {
3263                             savingComment = FALSE;
3264                         }
3265                     }
3266                     next_out = i;
3267                 } else if (started == STARTED_HOLDINGS) {
3268                     int gamenum;
3269                     char new_piece[MSG_SIZ];
3270                     started = STARTED_NONE;
3271                     parse[parse_pos] = NULLCHAR;
3272                     if (appData.debugMode)
3273                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274                                                         parse, currentMove);
3275                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276                         gamenum == ics_gamenum) {
3277                         if (gameInfo.variant == VariantNormal) {
3278                           /* [HGM] We seem to switch variant during a game!
3279                            * Presumably no holdings were displayed, so we have
3280                            * to move the position two files to the right to
3281                            * create room for them!
3282                            */
3283                           VariantClass newVariant;
3284                           switch(gameInfo.boardWidth) { // base guess on board width
3285                                 case 9:  newVariant = VariantShogi; break;
3286                                 case 10: newVariant = VariantGreat; break;
3287                                 default: newVariant = VariantCrazyhouse; break;
3288                           }
3289                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3290                           /* Get a move list just to see the header, which
3291                              will tell us whether this is really bug or zh */
3292                           if (ics_getting_history == H_FALSE) {
3293                             ics_getting_history = H_REQUESTED;
3294                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3295                             SendToICS(str);
3296                           }
3297                         }
3298                         new_piece[0] = NULLCHAR;
3299                         sscanf(parse, "game %d white [%s black [%s <- %s",
3300                                &gamenum, white_holding, black_holding,
3301                                new_piece);
3302                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3303                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3304                         /* [HGM] copy holdings to board holdings area */
3305                         CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3306                         CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3307 #if ZIPPY
3308                         if (appData.zippyPlay && first.initDone) {
3309                             ZippyHoldings(white_holding, black_holding,
3310                                           new_piece);
3311                         }
3312 #endif /*ZIPPY*/
3313                         if (tinyLayout || smallLayout) {
3314                             char wh[16], bh[16];
3315                             PackHolding(wh, white_holding);
3316                             PackHolding(bh, black_holding);
3317                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3318                                     gameInfo.white, gameInfo.black);
3319                         } else {
3320                             sprintf(str, "%s [%s] vs. %s [%s]",
3321                                     gameInfo.white, white_holding,
3322                                     gameInfo.black, black_holding);
3323                         }
3324
3325                         DrawPosition(FALSE, boards[currentMove]);
3326                         DisplayTitle(str);
3327                     }
3328                     /* Suppress following prompt */
3329                     if (looking_at(buf, &i, "*% ")) {
3330                         savingComment = FALSE;
3331                     }
3332                     next_out = i;
3333                 }
3334                 continue;
3335             }
3336
3337             i++;                /* skip unparsed character and loop back */
3338         }
3339
3340         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3341             started != STARTED_HOLDINGS && i > next_out) {
3342             SendToPlayer(&buf[next_out], i - next_out);
3343             next_out = i;
3344         }
3345         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3346
3347         leftover_len = buf_len - leftover_start;
3348         /* if buffer ends with something we couldn't parse,
3349            reparse it after appending the next read */
3350
3351     } else if (count == 0) {
3352         RemoveInputSource(isr);
3353         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3354     } else {
3355         DisplayFatalError(_("Error reading from ICS"), error, 1);
3356     }
3357 }
3358
3359
3360 /* Board style 12 looks like this:
3361
3362    <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
3363
3364  * The "<12> " is stripped before it gets to this routine.  The two
3365  * trailing 0's (flip state and clock ticking) are later addition, and
3366  * some chess servers may not have them, or may have only the first.
3367  * Additional trailing fields may be added in the future.
3368  */
3369
3370 #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"
3371
3372 #define RELATION_OBSERVING_PLAYED    0
3373 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3374 #define RELATION_PLAYING_MYMOVE      1
3375 #define RELATION_PLAYING_NOTMYMOVE  -1
3376 #define RELATION_EXAMINING           2
3377 #define RELATION_ISOLATED_BOARD     -3
3378 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3379
3380 void
3381 ParseBoard12(string)
3382      char *string;
3383 {
3384     GameMode newGameMode;
3385     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3386     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3387     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3388     char to_play, board_chars[200];
3389     char move_str[500], str[500], elapsed_time[500];
3390     char black[32], white[32];
3391     Board board;
3392     int prevMove = currentMove;
3393     int ticking = 2;
3394     ChessMove moveType;
3395     int fromX, fromY, toX, toY;
3396     char promoChar;
3397     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3398     char *bookHit = NULL; // [HGM] book
3399     Boolean weird = FALSE;
3400
3401     fromX = fromY = toX = toY = -1;
3402
3403     newGame = FALSE;
3404
3405     if (appData.debugMode)
3406       fprintf(debugFP, _("Parsing board: %s\n"), string);
3407
3408     move_str[0] = NULLCHAR;
3409     elapsed_time[0] = NULLCHAR;
3410     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3411         int  i = 0, j;
3412         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3413             if(string[i] == ' ') { ranks++; files = 0; }
3414             else files++;
3415             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3416             i++;
3417         }
3418         for(j = 0; j <i; j++) board_chars[j] = string[j];
3419         board_chars[i] = '\0';
3420         string += i + 1;
3421     }
3422     n = sscanf(string, PATTERN, &to_play, &double_push,
3423                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3424                &gamenum, white, black, &relation, &basetime, &increment,
3425                &white_stren, &black_stren, &white_time, &black_time,
3426                &moveNum, str, elapsed_time, move_str, &ics_flip,
3427                &ticking);
3428 fprintf(debugFP, "old: %dx%d   new: %dx%d weird=%d variant=%d\n",gameInfo.boardHeight,gameInfo.boardWidth,ranks,files,weird,gameInfo.variant);fflush(debugFP);
3429    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3430                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3431      /* [HGM] We seem to switch variant during a game!
3432       * Try to guess new variant from board size
3433       */
3434           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3435           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3436           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3437           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3438           if(ranks == 9 && files == 9)  newVariant = VariantShogi;
3439           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3440           /* Get a move list just to see the header, which
3441              will tell us whether this is really bug or zh */
3442           if (ics_getting_history == H_FALSE) {
3443             ics_getting_history = H_REQUESTED;
3444             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3445             SendToICS(str);
3446           }
3447     }
3448
3449     if (n < 21) {
3450         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3451         DisplayError(str, 0);
3452         return;
3453     }
3454
3455     /* Convert the move number to internal form */
3456     moveNum = (moveNum - 1) * 2;
3457     if (to_play == 'B') moveNum++;
3458     if (moveNum >= MAX_MOVES) {
3459       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460                         0, 1);
3461       return;
3462     }
3463
3464     switch (relation) {
3465       case RELATION_OBSERVING_PLAYED:
3466       case RELATION_OBSERVING_STATIC:
3467         if (gamenum == -1) {
3468             /* Old ICC buglet */
3469             relation = RELATION_OBSERVING_STATIC;
3470         }
3471         newGameMode = IcsObserving;
3472         break;
3473       case RELATION_PLAYING_MYMOVE:
3474       case RELATION_PLAYING_NOTMYMOVE:
3475         newGameMode =
3476           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3477             IcsPlayingWhite : IcsPlayingBlack;
3478         break;
3479       case RELATION_EXAMINING:
3480         newGameMode = IcsExamining;
3481         break;
3482       case RELATION_ISOLATED_BOARD:
3483       default:
3484         /* Just display this board.  If user was doing something else,
3485            we will forget about it until the next board comes. */
3486         newGameMode = IcsIdle;
3487         break;
3488       case RELATION_STARTING_POSITION:
3489         newGameMode = gameMode;
3490         break;
3491     }
3492
3493     /* Modify behavior for initial board display on move listing
3494        of wild games.
3495        */
3496     switch (ics_getting_history) {
3497       case H_FALSE:
3498       case H_REQUESTED:
3499         break;
3500       case H_GOT_REQ_HEADER:
3501       case H_GOT_UNREQ_HEADER:
3502         /* This is the initial position of the current game */
3503         gamenum = ics_gamenum;
3504         moveNum = 0;            /* old ICS bug workaround */
3505         if (to_play == 'B') {
3506           startedFromSetupPosition = TRUE;
3507           blackPlaysFirst = TRUE;
3508           moveNum = 1;
3509           if (forwardMostMove == 0) forwardMostMove = 1;
3510           if (backwardMostMove == 0) backwardMostMove = 1;
3511           if (currentMove == 0) currentMove = 1;
3512         }
3513         newGameMode = gameMode;
3514         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3515         break;
3516       case H_GOT_UNWANTED_HEADER:
3517         /* This is an initial board that we don't want */
3518         return;
3519       case H_GETTING_MOVES:
3520         /* Should not happen */
3521         DisplayError(_("Error gathering move list: extra board"), 0);
3522         ics_getting_history = H_FALSE;
3523         return;
3524     }
3525
3526     /* Take action if this is the first board of a new game, or of a
3527        different game than is currently being displayed.  */
3528     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3529         relation == RELATION_ISOLATED_BOARD) {
3530
3531         /* Forget the old game and get the history (if any) of the new one */
3532         if (gameMode != BeginningOfGame) {
3533           Reset(FALSE, TRUE);
3534         }
3535         newGame = TRUE;
3536         if (appData.autoRaiseBoard) BoardToTop();
3537         prevMove = -3;
3538         if (gamenum == -1) {
3539             newGameMode = IcsIdle;
3540         } else if (moveNum > 0 && newGameMode != IcsIdle &&
3541                    appData.getMoveList) {
3542             /* Need to get game history */
3543             ics_getting_history = H_REQUESTED;
3544             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3545             SendToICS(str);
3546         }
3547
3548         /* Initially flip the board to have black on the bottom if playing
3549            black or if the ICS flip flag is set, but let the user change
3550            it with the Flip View button. */
3551         flipView = appData.autoFlipView ?
3552           (newGameMode == IcsPlayingBlack) || ics_flip :
3553           appData.flipView;
3554
3555         /* Done with values from previous mode; copy in new ones */
3556         gameMode = newGameMode;
3557         ModeHighlight();
3558         ics_gamenum = gamenum;
3559         if (gamenum == gs_gamenum) {
3560             int klen = strlen(gs_kind);
3561             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3562             sprintf(str, "ICS %s", gs_kind);
3563             gameInfo.event = StrSave(str);
3564         } else {
3565             gameInfo.event = StrSave("ICS game");
3566         }
3567         gameInfo.site = StrSave(appData.icsHost);
3568         gameInfo.date = PGNDate();
3569         gameInfo.round = StrSave("-");
3570         gameInfo.white = StrSave(white);
3571         gameInfo.black = StrSave(black);
3572         timeControl = basetime * 60 * 1000;
3573         timeControl_2 = 0;
3574         timeIncrement = increment * 1000;
3575         movesPerSession = 0;
3576         gameInfo.timeControl = TimeControlTagValue();
3577         VariantSwitch(board, StringToVariant(gameInfo.event) );
3578   if (appData.debugMode) {
3579     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3580     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3581     setbuf(debugFP, NULL);
3582   }
3583
3584         gameInfo.outOfBook = NULL;
3585
3586         /* Do we have the ratings? */
3587         if (strcmp(player1Name, white) == 0 &&
3588             strcmp(player2Name, black) == 0) {
3589             if (appData.debugMode)
3590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3591                       player1Rating, player2Rating);
3592             gameInfo.whiteRating = player1Rating;
3593             gameInfo.blackRating = player2Rating;
3594         } else if (strcmp(player2Name, white) == 0 &&
3595                    strcmp(player1Name, black) == 0) {
3596             if (appData.debugMode)
3597               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3598                       player2Rating, player1Rating);
3599             gameInfo.whiteRating = player2Rating;
3600             gameInfo.blackRating = player1Rating;
3601         }
3602         player1Name[0] = player2Name[0] = NULLCHAR;
3603
3604         /* Silence shouts if requested */
3605         if (appData.quietPlay &&
3606             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3607             SendToICS(ics_prefix);
3608             SendToICS("set shout 0\n");
3609         }
3610     }
3611
3612     /* Deal with midgame name changes */
3613     if (!newGame) {
3614         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3615             if (gameInfo.white) free(gameInfo.white);
3616             gameInfo.white = StrSave(white);
3617         }
3618         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3619             if (gameInfo.black) free(gameInfo.black);
3620             gameInfo.black = StrSave(black);
3621         }
3622     }
3623
3624     /* Throw away game result if anything actually changes in examine mode */
3625     if (gameMode == IcsExamining && !newGame) {
3626         gameInfo.result = GameUnfinished;
3627         if (gameInfo.resultDetails != NULL) {
3628             free(gameInfo.resultDetails);
3629             gameInfo.resultDetails = NULL;
3630         }
3631     }
3632
3633     /* In pausing && IcsExamining mode, we ignore boards coming
3634        in if they are in a different variation than we are. */
3635     if (pauseExamInvalid) return;
3636     if (pausing && gameMode == IcsExamining) {
3637         if (moveNum <= pauseExamForwardMostMove) {
3638             pauseExamInvalid = TRUE;
3639             forwardMostMove = pauseExamForwardMostMove;
3640             return;
3641         }
3642     }
3643
3644   if (appData.debugMode) {
3645     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3646   }
3647     /* Parse the board */
3648     for (k = 0; k < ranks; k++) {
3649       for (j = 0; j < files; j++)
3650         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3651       if(gameInfo.holdingsWidth > 1) {
3652            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3653            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3654       }
3655     }
3656     CopyBoard(boards[moveNum], board);
3657     if (moveNum == 0) {
3658         startedFromSetupPosition =
3659           !CompareBoards(board, initialPosition);
3660         if(startedFromSetupPosition)
3661             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3662     }
3663
3664     /* [HGM] Set castling rights. Take the outermost Rooks,
3665        to make it also work for FRC opening positions. Note that board12
3666        is really defective for later FRC positions, as it has no way to
3667        indicate which Rook can castle if they are on the same side of King.
3668        For the initial position we grant rights to the outermost Rooks,
3669        and remember thos rights, and we then copy them on positions
3670        later in an FRC game. This means WB might not recognize castlings with
3671        Rooks that have moved back to their original position as illegal,
3672        but in ICS mode that is not its job anyway.
3673     */
3674     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3675     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3676
3677         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3678             if(board[0][i] == WhiteRook) j = i;
3679         initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3680         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3681             if(board[0][i] == WhiteRook) j = i;
3682         initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3683         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3684             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3685         initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3686         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3687             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3688         initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3689
3690         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3691         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3692             if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694             if(board[BOARD_HEIGHT-1][k] == bKing)
3695                 initialRights[5] = castlingRights[moveNum][5] = k;
3696     } else { int r;
3697         r = castlingRights[moveNum][0] = initialRights[0];
3698         if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3699         r = castlingRights[moveNum][1] = initialRights[1];
3700         if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3701         r = castlingRights[moveNum][3] = initialRights[3];
3702         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3703         r = castlingRights[moveNum][4] = initialRights[4];
3704         if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3705         /* wildcastle kludge: always assume King has rights */
3706         r = castlingRights[moveNum][2] = initialRights[2];
3707         r = castlingRights[moveNum][5] = initialRights[5];
3708     }
3709     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3710     epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3711
3712
3713     if (ics_getting_history == H_GOT_REQ_HEADER ||
3714         ics_getting_history == H_GOT_UNREQ_HEADER) {
3715         /* This was an initial position from a move list, not
3716            the current position */
3717         return;
3718     }
3719
3720     /* Update currentMove and known move number limits */
3721     newMove = newGame || moveNum > forwardMostMove;
3722
3723     /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3724     if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3725         takeback = forwardMostMove - moveNum;
3726         for (i = 0; i < takeback; i++) {
3727              if (appData.debugMode) fprintf(debugFP, "take back move\n");
3728              SendToProgram("undo\n", &first);
3729         }
3730     }
3731
3732     if (newGame) {
3733         forwardMostMove = backwardMostMove = currentMove = moveNum;
3734         if (gameMode == IcsExamining && moveNum == 0) {
3735           /* Workaround for ICS limitation: we are not told the wild
3736              type when starting to examine a game.  But if we ask for
3737              the move list, the move list header will tell us */
3738             ics_getting_history = H_REQUESTED;
3739             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3740             SendToICS(str);
3741         }
3742     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3743                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3744         forwardMostMove = moveNum;
3745         if (!pausing || currentMove > forwardMostMove)
3746           currentMove = forwardMostMove;
3747     } else {
3748         /* New part of history that is not contiguous with old part */
3749         if (pausing && gameMode == IcsExamining) {
3750             pauseExamInvalid = TRUE;
3751             forwardMostMove = pauseExamForwardMostMove;
3752             return;
3753         }
3754         forwardMostMove = backwardMostMove = currentMove = moveNum;
3755         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3756             ics_getting_history = H_REQUESTED;
3757             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3758             SendToICS(str);
3759         }
3760     }
3761
3762     /* Update the clocks */
3763     if (strchr(elapsed_time, '.')) {
3764       /* Time is in ms */
3765       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3766       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3767     } else {
3768       /* Time is in seconds */
3769       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3770       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3771     }
3772
3773
3774 #if ZIPPY
3775     if (appData.zippyPlay && newGame &&
3776         gameMode != IcsObserving && gameMode != IcsIdle &&
3777         gameMode != IcsExamining)
3778       ZippyFirstBoard(moveNum, basetime, increment);
3779 #endif
3780
3781     /* Put the move on the move list, first converting
3782        to canonical algebraic form. */
3783     if (moveNum > 0) {
3784   if (appData.debugMode) {
3785     if (appData.debugMode) { int f = forwardMostMove;
3786         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3787                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3788     }
3789     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3790     fprintf(debugFP, "moveNum = %d\n", moveNum);
3791     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3792     setbuf(debugFP, NULL);
3793   }
3794         if (moveNum <= backwardMostMove) {
3795             /* We don't know what the board looked like before
3796                this move.  Punt. */
3797             strcpy(parseList[moveNum - 1], move_str);
3798             strcat(parseList[moveNum - 1], " ");
3799             strcat(parseList[moveNum - 1], elapsed_time);
3800             moveList[moveNum - 1][0] = NULLCHAR;
3801         } else if (strcmp(move_str, "none") == 0) {
3802             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3803             /* Again, we don't know what the board looked like;
3804                this is really the start of the game. */
3805             parseList[moveNum - 1][0] = NULLCHAR;
3806             moveList[moveNum - 1][0] = NULLCHAR;
3807             backwardMostMove = moveNum;
3808             startedFromSetupPosition = TRUE;
3809             fromX = fromY = toX = toY = -1;
3810         } else {
3811           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3812           //                 So we parse the long-algebraic move string in stead of the SAN move
3813           int valid; char buf[MSG_SIZ], *prom;
3814
3815           // str looks something like "Q/a1-a2"; kill the slash
3816           if(str[1] == '/')
3817                 sprintf(buf, "%c%s", str[0], str+2);
3818           else  strcpy(buf, str); // might be castling
3819           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3820                 strcat(buf, prom); // long move lacks promo specification!
3821           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3822                 if(appData.debugMode)
3823                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3824                 strcpy(move_str, buf);
3825           }
3826           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3827                                 &fromX, &fromY, &toX, &toY, &promoChar)
3828                || ParseOneMove(buf, moveNum - 1, &moveType,
3829                                 &fromX, &fromY, &toX, &toY, &promoChar);
3830           // end of long SAN patch
3831           if (valid) {
3832             (void) CoordsToAlgebraic(boards[moveNum - 1],
3833                                      PosFlags(moveNum - 1), EP_UNKNOWN,
3834                                      fromY, fromX, toY, toX, promoChar,
3835                                      parseList[moveNum-1]);
3836             switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3837                              castlingRights[moveNum]) ) {
3838               case MT_NONE:
3839               case MT_STALEMATE:
3840               default:
3841                 break;
3842               case MT_CHECK:
3843                 if(gameInfo.variant != VariantShogi)
3844                     strcat(parseList[moveNum - 1], "+");
3845                 break;
3846               case MT_CHECKMATE:
3847               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3848                 strcat(parseList[moveNum - 1], "#");
3849                 break;
3850             }
3851             strcat(parseList[moveNum - 1], " ");
3852             strcat(parseList[moveNum - 1], elapsed_time);
3853             /* currentMoveString is set as a side-effect of ParseOneMove */
3854             strcpy(moveList[moveNum - 1], currentMoveString);
3855             strcat(moveList[moveNum - 1], "\n");
3856           } else {
3857             /* Move from ICS was illegal!?  Punt. */
3858   if (appData.debugMode) {
3859     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3860     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3861   }
3862             strcpy(parseList[moveNum - 1], move_str);
3863             strcat(parseList[moveNum - 1], " ");
3864             strcat(parseList[moveNum - 1], elapsed_time);
3865             moveList[moveNum - 1][0] = NULLCHAR;
3866             fromX = fromY = toX = toY = -1;
3867           }
3868         }
3869   if (appData.debugMode) {
3870     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3871     setbuf(debugFP, NULL);
3872   }
3873
3874 #if ZIPPY
3875         /* Send move to chess program (BEFORE animating it). */
3876         if (appData.zippyPlay && !newGame && newMove &&
3877            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3878
3879             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3880                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3881                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3882                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3883                             move_str);
3884                     DisplayError(str, 0);
3885                 } else {
3886                     if (first.sendTime) {
3887                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3888                     }
3889                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3890                     if (firstMove && !bookHit) {
3891                         firstMove = FALSE;
3892                         if (first.useColors) {
3893                           SendToProgram(gameMode == IcsPlayingWhite ?
3894                                         "white\ngo\n" :
3895                                         "black\ngo\n", &first);
3896                         } else {
3897                           SendToProgram("go\n", &first);
3898                         }
3899                         first.maybeThinking = TRUE;
3900                     }
3901                 }
3902             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3903               if (moveList[moveNum - 1][0] == NULLCHAR) {
3904                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3905                 DisplayError(str, 0);
3906               } else {
3907                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3908                 SendMoveToProgram(moveNum - 1, &first);
3909               }
3910             }
3911         }
3912 #endif
3913     }
3914
3915     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3916         /* If move comes from a remote source, animate it.  If it
3917            isn't remote, it will have already been animated. */
3918         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3919             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3920         }
3921         if (!pausing && appData.highlightLastMove) {
3922             SetHighlights(fromX, fromY, toX, toY);
3923         }
3924     }
3925
3926     /* Start the clocks */
3927     whiteFlag = blackFlag = FALSE;
3928     appData.clockMode = !(basetime == 0 && increment == 0);
3929     if (ticking == 0) {
3930       ics_clock_paused = TRUE;
3931       StopClocks();
3932     } else if (ticking == 1) {
3933       ics_clock_paused = FALSE;
3934     }
3935     if (gameMode == IcsIdle ||
3936         relation == RELATION_OBSERVING_STATIC ||
3937         relation == RELATION_EXAMINING ||
3938         ics_clock_paused)
3939       DisplayBothClocks();
3940     else
3941       StartClocks();
3942
3943     /* Display opponents and material strengths */
3944     if (gameInfo.variant != VariantBughouse &&
3945         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3946         if (tinyLayout || smallLayout) {
3947             if(gameInfo.variant == VariantNormal)
3948                 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3949                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3950                     basetime, increment);
3951             else
3952                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3953                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3954                     basetime, increment, (int) gameInfo.variant);
3955         } else {
3956             if(gameInfo.variant == VariantNormal)
3957                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3958                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3959                     basetime, increment);
3960             else
3961                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3962                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3963                     basetime, increment, VariantName(gameInfo.variant));
3964         }
3965         DisplayTitle(str);
3966   if (appData.debugMode) {
3967     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3968   }
3969     }
3970
3971
3972     /* Display the board */
3973     if (!pausing && !appData.noGUI) {
3974       if (appData.premove)
3975           if (!gotPremove ||
3976              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3977              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3978               ClearPremoveHighlights();
3979
3980       DrawPosition(FALSE, boards[currentMove]);
3981       DisplayMove(moveNum - 1);
3982       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3983             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3984               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
3985         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3986       }
3987     }
3988
3989     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3990 #if ZIPPY
3991     if(bookHit) { // [HGM] book: simulate book reply
3992         static char bookMove[MSG_SIZ]; // a bit generous?
3993
3994         programStats.nodes = programStats.depth = programStats.time =
3995         programStats.score = programStats.got_only_move = 0;
3996         sprintf(programStats.movelist, "%s (xbook)", bookHit);
3997
3998         strcpy(bookMove, "move ");
3999         strcat(bookMove, bookHit);
4000         HandleMachineMove(bookMove, &first);
4001     }
4002 #endif
4003 }
4004
4005 void
4006 GetMoveListEvent()
4007 {
4008     char buf[MSG_SIZ];
4009     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4010         ics_getting_history = H_REQUESTED;
4011         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4012         SendToICS(buf);
4013     }
4014 }
4015
4016 void
4017 AnalysisPeriodicEvent(force)
4018      int force;
4019 {
4020     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4021          && !force) || !appData.periodicUpdates)
4022       return;
4023
4024     /* Send . command to Crafty to collect stats */
4025     SendToProgram(".\n", &first);
4026
4027     /* Don't send another until we get a response (this makes
4028        us stop sending to old Crafty's which don't understand
4029        the "." command (sending illegal cmds resets node count & time,
4030        which looks bad)) */
4031     programStats.ok_to_send = 0;
4032 }
4033
4034 void ics_update_width(new_width)
4035         int new_width;
4036 {
4037         ics_printf("set width %d\n", new_width);
4038 }
4039
4040 void
4041 SendMoveToProgram(moveNum, cps)
4042      int moveNum;
4043      ChessProgramState *cps;
4044 {
4045     char buf[MSG_SIZ];
4046
4047     if (cps->useUsermove) {
4048       SendToProgram("usermove ", cps);
4049     }
4050     if (cps->useSAN) {
4051       char *space;
4052       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4053         int len = space - parseList[moveNum];
4054         memcpy(buf, parseList[moveNum], len);
4055         buf[len++] = '\n';
4056         buf[len] = NULLCHAR;
4057       } else {
4058         sprintf(buf, "%s\n", parseList[moveNum]);
4059       }
4060       SendToProgram(buf, cps);
4061     } else {
4062       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4063         AlphaRank(moveList[moveNum], 4);
4064         SendToProgram(moveList[moveNum], cps);
4065         AlphaRank(moveList[moveNum], 4); // and back
4066       } else
4067       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4068        * the engine. It would be nice to have a better way to identify castle
4069        * moves here. */
4070       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4071                                                                          && cps->useOOCastle) {
4072         int fromX = moveList[moveNum][0] - AAA;
4073         int fromY = moveList[moveNum][1] - ONE;
4074         int toX = moveList[moveNum][2] - AAA;
4075         int toY = moveList[moveNum][3] - ONE;
4076         if((boards[moveNum][fromY][fromX] == WhiteKing
4077             && boards[moveNum][toY][toX] == WhiteRook)
4078            || (boards[moveNum][fromY][fromX] == BlackKing
4079                && boards[moveNum][toY][toX] == BlackRook)) {
4080           if(toX > fromX) SendToProgram("O-O\n", cps);
4081           else SendToProgram("O-O-O\n", cps);
4082         }
4083         else SendToProgram(moveList[moveNum], cps);
4084       }
4085       else SendToProgram(moveList[moveNum], cps);
4086       /* End of additions by Tord */
4087     }
4088
4089     /* [HGM] setting up the opening has brought engine in force mode! */
4090     /*       Send 'go' if we are in a mode where machine should play. */
4091     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4092         (gameMode == TwoMachinesPlay   ||
4093 #ifdef ZIPPY
4094          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4095 #endif
4096          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4097         SendToProgram("go\n", cps);
4098   if (appData.debugMode) {
4099     fprintf(debugFP, "(extra)\n");
4100   }
4101     }
4102     setboardSpoiledMachineBlack = 0;
4103 }
4104
4105 void
4106 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4107      ChessMove moveType;
4108      int fromX, fromY, toX, toY;
4109 {
4110     char user_move[MSG_SIZ];
4111
4112     switch (moveType) {
4113       default:
4114         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4115                 (int)moveType, fromX, fromY, toX, toY);
4116         DisplayError(user_move + strlen("say "), 0);
4117         break;
4118       case WhiteKingSideCastle:
4119       case BlackKingSideCastle:
4120       case WhiteQueenSideCastleWild:
4121       case BlackQueenSideCastleWild:
4122       /* PUSH Fabien */
4123       case WhiteHSideCastleFR:
4124       case BlackHSideCastleFR:
4125       /* POP Fabien */
4126         sprintf(user_move, "o-o\n");
4127         break;
4128       case WhiteQueenSideCastle:
4129       case BlackQueenSideCastle:
4130       case WhiteKingSideCastleWild:
4131       case BlackKingSideCastleWild:
4132       /* PUSH Fabien */
4133       case WhiteASideCastleFR:
4134       case BlackASideCastleFR:
4135       /* POP Fabien */
4136         sprintf(user_move, "o-o-o\n");
4137         break;
4138       case WhitePromotionQueen:
4139       case BlackPromotionQueen:
4140       case WhitePromotionRook:
4141       case BlackPromotionRook:
4142       case WhitePromotionBishop:
4143       case BlackPromotionBishop:
4144       case WhitePromotionKnight:
4145       case BlackPromotionKnight:
4146       case WhitePromotionKing:
4147       case BlackPromotionKing:
4148       case WhitePromotionChancellor:
4149       case BlackPromotionChancellor:
4150       case WhitePromotionArchbishop:
4151       case BlackPromotionArchbishop:
4152         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4153             sprintf(user_move, "%c%c%c%c=%c\n",
4154                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4155                 PieceToChar(WhiteFerz));
4156         else if(gameInfo.variant == VariantGreat)
4157             sprintf(user_move, "%c%c%c%c=%c\n",
4158                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4159                 PieceToChar(WhiteMan));
4160         else
4161             sprintf(user_move, "%c%c%c%c=%c\n",
4162                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4163                 PieceToChar(PromoPiece(moveType)));
4164         break;
4165       case WhiteDrop:
4166       case BlackDrop:
4167         sprintf(user_move, "%c@%c%c\n",
4168                 ToUpper(PieceToChar((ChessSquare) fromX)),
4169                 AAA + toX, ONE + toY);
4170         break;
4171       case NormalMove:
4172       case WhiteCapturesEnPassant:
4173       case BlackCapturesEnPassant:
4174       case IllegalMove:  /* could be a variant we don't quite understand */
4175         sprintf(user_move, "%c%c%c%c\n",
4176                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4177         break;
4178     }
4179     SendToICS(user_move);
4180     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4181         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4182 }
4183
4184 void
4185 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4186      int rf, ff, rt, ft;
4187      char promoChar;
4188      char move[7];
4189 {
4190     if (rf == DROP_RANK) {
4191         sprintf(move, "%c@%c%c\n",
4192                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4193     } else {
4194         if (promoChar == 'x' || promoChar == NULLCHAR) {
4195             sprintf(move, "%c%c%c%c\n",
4196                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4197         } else {
4198             sprintf(move, "%c%c%c%c%c\n",
4199                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4200         }
4201     }
4202 }
4203
4204 void
4205 ProcessICSInitScript(f)
4206      FILE *f;
4207 {
4208     char buf[MSG_SIZ];
4209
4210     while (fgets(buf, MSG_SIZ, f)) {
4211         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4212     }
4213
4214     fclose(f);
4215 }
4216
4217
4218 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4219 void
4220 AlphaRank(char *move, int n)
4221 {
4222 //    char *p = move, c; int x, y;
4223
4224     if (appData.debugMode) {
4225         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4226     }
4227
4228     if(move[1]=='*' &&
4229        move[2]>='0' && move[2]<='9' &&
4230        move[3]>='a' && move[3]<='x'    ) {
4231         move[1] = '@';
4232         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4233         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4234     } else
4235     if(move[0]>='0' && move[0]<='9' &&
4236        move[1]>='a' && move[1]<='x' &&
4237        move[2]>='0' && move[2]<='9' &&
4238        move[3]>='a' && move[3]<='x'    ) {
4239         /* input move, Shogi -> normal */
4240         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4241         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4242         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4243         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4244     } else
4245     if(move[1]=='@' &&
4246        move[3]>='0' && move[3]<='9' &&
4247        move[2]>='a' && move[2]<='x'    ) {
4248         move[1] = '*';
4249         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4250         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4251     } else
4252     if(
4253        move[0]>='a' && move[0]<='x' &&
4254        move[3]>='0' && move[3]<='9' &&
4255        move[2]>='a' && move[2]<='x'    ) {
4256          /* output move, normal -> Shogi */
4257         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4258         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4259         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4260         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4261         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4262     }
4263     if (appData.debugMode) {
4264         fprintf(debugFP, "   out = '%s'\n", move);
4265     }
4266 }
4267
4268 /* Parser for moves from gnuchess, ICS, or user typein box */
4269 Boolean
4270 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4271      char *move;
4272      int moveNum;
4273      ChessMove *moveType;
4274      int *fromX, *fromY, *toX, *toY;
4275      char *promoChar;
4276 {
4277     if (appData.debugMode) {
4278         fprintf(debugFP, "move to parse: %s\n", move);
4279     }
4280     *moveType = yylexstr(moveNum, move);
4281
4282     switch (*moveType) {
4283       case WhitePromotionChancellor:
4284       case BlackPromotionChancellor:
4285       case WhitePromotionArchbishop:
4286       case BlackPromotionArchbishop:
4287       case WhitePromotionQueen:
4288       case BlackPromotionQueen:
4289       case WhitePromotionRook:
4290       case BlackPromotionRook:
4291       case WhitePromotionBishop:
4292       case BlackPromotionBishop:
4293       case WhitePromotionKnight:
4294       case BlackPromotionKnight:
4295       case WhitePromotionKing:
4296       case BlackPromotionKing:
4297       case NormalMove:
4298       case WhiteCapturesEnPassant:
4299       case BlackCapturesEnPassant:
4300       case WhiteKingSideCastle:
4301       case WhiteQueenSideCastle:
4302       case BlackKingSideCastle:
4303       case BlackQueenSideCastle:
4304       case WhiteKingSideCastleWild:
4305       case WhiteQueenSideCastleWild:
4306       case BlackKingSideCastleWild:
4307       case BlackQueenSideCastleWild:
4308       /* Code added by Tord: */
4309       case WhiteHSideCastleFR:
4310       case WhiteASideCastleFR:
4311       case BlackHSideCastleFR:
4312       case BlackASideCastleFR:
4313       /* End of code added by Tord */
4314       case IllegalMove:         /* bug or odd chess variant */
4315         *fromX = currentMoveString[0] - AAA;
4316         *fromY = currentMoveString[1] - ONE;
4317         *toX = currentMoveString[2] - AAA;
4318         *toY = currentMoveString[3] - ONE;
4319         *promoChar = currentMoveString[4];
4320         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4321             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4322     if (appData.debugMode) {
4323         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4324     }
4325             *fromX = *fromY = *toX = *toY = 0;
4326             return FALSE;
4327         }
4328         if (appData.testLegality) {
4329           return (*moveType != IllegalMove);
4330         } else {
4331           return !(fromX == fromY && toX == toY);
4332         }
4333
4334       case WhiteDrop:
4335       case BlackDrop:
4336         *fromX = *moveType == WhiteDrop ?
4337           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4338           (int) CharToPiece(ToLower(currentMoveString[0]));
4339         *fromY = DROP_RANK;
4340         *toX = currentMoveString[2] - AAA;
4341         *toY = currentMoveString[3] - ONE;
4342         *promoChar = NULLCHAR;
4343         return TRUE;
4344
4345       case AmbiguousMove:
4346       case ImpossibleMove:
4347       case (ChessMove) 0:       /* end of file */
4348       case ElapsedTime:
4349       case Comment:
4350       case PGNTag:
4351       case NAG:
4352       case WhiteWins:
4353       case BlackWins:
4354       case GameIsDrawn:
4355       default:
4356     if (appData.debugMode) {
4357         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4358     }
4359         /* bug? */
4360         *fromX = *fromY = *toX = *toY = 0;
4361         *promoChar = NULLCHAR;
4362         return FALSE;
4363     }
4364 }
4365
4366 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4367 // All positions will have equal probability, but the current method will not provide a unique
4368 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4369 #define DARK 1
4370 #define LITE 2
4371 #define ANY 3
4372
4373 int squaresLeft[4];
4374 int piecesLeft[(int)BlackPawn];
4375 int seed, nrOfShuffles;
4376
4377 void GetPositionNumber()
4378 {       // sets global variable seed
4379         int i;
4380
4381         seed = appData.defaultFrcPosition;
4382         if(seed < 0) { // randomize based on time for negative FRC position numbers
4383                 for(i=0; i<50; i++) seed += random();
4384                 seed = random() ^ random() >> 8 ^ random() << 8;
4385                 if(seed<0) seed = -seed;
4386         }
4387 }
4388
4389 int put(Board board, int pieceType, int rank, int n, int shade)
4390 // put the piece on the (n-1)-th empty squares of the given shade
4391 {
4392         int i;
4393
4394         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4395                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4396                         board[rank][i] = (ChessSquare) pieceType;
4397                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4398                         squaresLeft[ANY]--;
4399                         piecesLeft[pieceType]--;
4400                         return i;
4401                 }
4402         }
4403         return -1;
4404 }
4405
4406
4407 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4408 // calculate where the next piece goes, (any empty square), and put it there
4409 {
4410         int i;
4411
4412         i = seed % squaresLeft[shade];
4413         nrOfShuffles *= squaresLeft[shade];
4414         seed /= squaresLeft[shade];
4415         put(board, pieceType, rank, i, shade);
4416 }
4417
4418 void AddTwoPieces(Board board, int pieceType, int rank)
4419 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4420 {
4421         int i, n=squaresLeft[ANY], j=n-1, k;
4422
4423         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4424         i = seed % k;  // pick one
4425         nrOfShuffles *= k;
4426         seed /= k;
4427         while(i >= j) i -= j--;
4428         j = n - 1 - j; i += j;
4429         put(board, pieceType, rank, j, ANY);
4430         put(board, pieceType, rank, i, ANY);
4431 }
4432
4433 void SetUpShuffle(Board board, int number)
4434 {
4435         int i, p, first=1;
4436
4437         GetPositionNumber(); nrOfShuffles = 1;
4438
4439         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4440         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4441         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4442
4443         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4444
4445         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4446             p = (int) board[0][i];
4447             if(p < (int) BlackPawn) piecesLeft[p] ++;
4448             board[0][i] = EmptySquare;
4449         }
4450
4451         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4452             // shuffles restricted to allow normal castling put KRR first
4453             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4454                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4455             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4456                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4457             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4458                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4459             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4460                 put(board, WhiteRook, 0, 0, ANY);
4461             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4462         }
4463
4464         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4465             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4466             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4467                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4468                 while(piecesLeft[p] >= 2) {
4469                     AddOnePiece(board, p, 0, LITE);
4470                     AddOnePiece(board, p, 0, DARK);
4471                 }
4472                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4473             }
4474
4475         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4476             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4477             // but we leave King and Rooks for last, to possibly obey FRC restriction
4478             if(p == (int)WhiteRook) continue;
4479             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4480             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4481         }
4482
4483         // now everything is placed, except perhaps King (Unicorn) and Rooks
4484
4485         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4486             // Last King gets castling rights
4487             while(piecesLeft[(int)WhiteUnicorn]) {
4488                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4489                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4490             }
4491
4492             while(piecesLeft[(int)WhiteKing]) {
4493                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4494                 initialRights[2]  = initialRights[5]  = castlingRights[0][2] = castlingRights[0][5] = i;
4495             }
4496
4497
4498         } else {
4499             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4500             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4501         }
4502
4503         // Only Rooks can be left; simply place them all
4504         while(piecesLeft[(int)WhiteRook]) {
4505                 i = put(board, WhiteRook, 0, 0, ANY);
4506                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4507                         if(first) {
4508                                 first=0;
4509                                 initialRights[1]  = initialRights[4]  = castlingRights[0][1] = castlingRights[0][4] = i;
4510                         }
4511                         initialRights[0]  = initialRights[3]  = castlingRights[0][0] = castlingRights[0][3] = i;
4512                 }
4513         }
4514         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4515             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4516         }
4517
4518         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4519 }
4520
4521 int SetCharTable( char *table, const char * map )
4522 /* [HGM] moved here from winboard.c because of its general usefulness */
4523 /*       Basically a safe strcpy that uses the last character as King */
4524 {
4525     int result = FALSE; int NrPieces;
4526
4527     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4528                     && NrPieces >= 12 && !(NrPieces&1)) {
4529         int i; /* [HGM] Accept even length from 12 to 34 */
4530
4531         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4532         for( i=0; i<NrPieces/2-1; i++ ) {
4533             table[i] = map[i];
4534             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4535         }
4536         table[(int) WhiteKing]  = map[NrPieces/2-1];
4537         table[(int) BlackKing]  = map[NrPieces-1];
4538
4539         result = TRUE;
4540     }
4541
4542     return result;
4543 }
4544
4545 void Prelude(Board board)
4546 {       // [HGM] superchess: random selection of exo-pieces
4547         int i, j, k; ChessSquare p;
4548         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4549
4550         GetPositionNumber(); // use FRC position number
4551
4552         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4553             SetCharTable(pieceToChar, appData.pieceToCharTable);
4554             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4555                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4556         }
4557
4558         j = seed%4;                 seed /= 4;
4559         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4560         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4561         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4562         j = seed%3 + (seed%3 >= j); seed /= 3;
4563         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4564         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4565         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4566         j = seed%3;                 seed /= 3;
4567         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4568         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4569         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4570         j = seed%2 + (seed%2 >= j); seed /= 2;
4571         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4572         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4573         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4574         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4575         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4576         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4577         put(board, exoPieces[0],    0, 0, ANY);
4578         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4579 }
4580
4581 void
4582 InitPosition(redraw)
4583      int redraw;
4584 {
4585     ChessSquare (* pieces)[BOARD_SIZE];
4586     int i, j, pawnRow, overrule,
4587     oldx = gameInfo.boardWidth,
4588     oldy = gameInfo.boardHeight,
4589     oldh = gameInfo.holdingsWidth,
4590     oldv = gameInfo.variant;
4591
4592     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4593
4594     /* [AS] Initialize pv info list [HGM] and game status */
4595     {
4596         for( i=0; i<MAX_MOVES; i++ ) {
4597             pvInfoList[i].depth = 0;
4598             epStatus[i]=EP_NONE;
4599             for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4600         }
4601
4602         initialRulePlies = 0; /* 50-move counter start */
4603
4604         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4605         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4606     }
4607
4608
4609     /* [HGM] logic here is completely changed. In stead of full positions */
4610     /* the initialized data only consist of the two backranks. The switch */
4611     /* selects which one we will use, which is than copied to the Board   */
4612     /* initialPosition, which for the rest is initialized by Pawns and    */
4613     /* empty squares. This initial position is then copied to boards[0],  */
4614     /* possibly after shuffling, so that it remains available.            */
4615
4616     gameInfo.holdingsWidth = 0; /* default board sizes */
4617     gameInfo.boardWidth    = 8;
4618     gameInfo.boardHeight   = 8;
4619     gameInfo.holdingsSize  = 0;
4620     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4621     for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4622     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4623
4624     switch (gameInfo.variant) {
4625     case VariantFischeRandom:
4626       shuffleOpenings = TRUE;
4627     default:
4628       pieces = FIDEArray;
4629       break;
4630     case VariantShatranj:
4631       pieces = ShatranjArray;
4632       nrCastlingRights = 0;
4633       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4634       break;
4635     case VariantTwoKings:
4636       pieces = twoKingsArray;
4637       break;
4638     case VariantCapaRandom:
4639       shuffleOpenings = TRUE;
4640     case VariantCapablanca:
4641       pieces = CapablancaArray;
4642       gameInfo.boardWidth = 10;
4643       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4644       break;
4645     case VariantGothic:
4646       pieces = GothicArray;
4647       gameInfo.boardWidth = 10;
4648       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4649       break;
4650     case VariantJanus:
4651       pieces = JanusArray;
4652       gameInfo.boardWidth = 10;
4653       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4654       nrCastlingRights = 6;
4655         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4656         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4657         castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4658         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4659         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4660         castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4661       break;
4662     case VariantFalcon:
4663       pieces = FalconArray;
4664       gameInfo.boardWidth = 10;
4665       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4666       break;
4667     case VariantXiangqi:
4668       pieces = XiangqiArray;
4669       gameInfo.boardWidth  = 9;
4670       gameInfo.boardHeight = 10;
4671       nrCastlingRights = 0;
4672       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4673       break;
4674     case VariantShogi:
4675       pieces = ShogiArray;
4676       gameInfo.boardWidth  = 9;
4677       gameInfo.boardHeight = 9;
4678       gameInfo.holdingsSize = 7;
4679       nrCastlingRights = 0;
4680       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4681       break;
4682     case VariantCourier:
4683       pieces = CourierArray;
4684       gameInfo.boardWidth  = 12;
4685       nrCastlingRights = 0;
4686       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4687       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4688       break;
4689     case VariantKnightmate:
4690       pieces = KnightmateArray;
4691       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4692       break;
4693     case VariantFairy:
4694       pieces = fairyArray;
4695       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4696       break;
4697     case VariantGreat:
4698       pieces = GreatArray;
4699       gameInfo.boardWidth = 10;
4700       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4701       gameInfo.holdingsSize = 8;
4702       break;
4703     case VariantSuper:
4704       pieces = FIDEArray;
4705       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4706       gameInfo.holdingsSize = 8;
4707       startedFromSetupPosition = TRUE;
4708       break;
4709     case VariantCrazyhouse:
4710     case VariantBughouse:
4711       pieces = FIDEArray;
4712       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4713       gameInfo.holdingsSize = 5;
4714       break;
4715     case VariantWildCastle:
4716       pieces = FIDEArray;
4717       /* !!?shuffle with kings guaranteed to be on d or e file */
4718       shuffleOpenings = 1;
4719       break;
4720     case VariantNoCastle:
4721       pieces = FIDEArray;
4722       nrCastlingRights = 0;
4723       for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4724       /* !!?unconstrained back-rank shuffle */
4725       shuffleOpenings = 1;
4726       break;
4727     }
4728
4729     overrule = 0;
4730     if(appData.NrFiles >= 0) {
4731         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4732         gameInfo.boardWidth = appData.NrFiles;
4733     }
4734     if(appData.NrRanks >= 0) {
4735         gameInfo.boardHeight = appData.NrRanks;
4736     }
4737     if(appData.holdingsSize >= 0) {
4738         i = appData.holdingsSize;
4739         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4740         gameInfo.holdingsSize = i;
4741     }
4742     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4743     if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4744         DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4745
4746     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4747     if(pawnRow < 1) pawnRow = 1;
4748
4749     /* User pieceToChar list overrules defaults */
4750     if(appData.pieceToCharTable != NULL)
4751         SetCharTable(pieceToChar, appData.pieceToCharTable);
4752
4753     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4754
4755         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4756             s = (ChessSquare) 0; /* account holding counts in guard band */
4757         for( i=0; i<BOARD_HEIGHT; i++ )
4758             initialPosition[i][j] = s;
4759
4760         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4761         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4762         initialPosition[pawnRow][j] = WhitePawn;
4763         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4764         if(gameInfo.variant == VariantXiangqi) {
4765             if(j&1) {
4766                 initialPosition[pawnRow][j] =
4767                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4768                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4769                    initialPosition[2][j] = WhiteCannon;
4770                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4771                 }
4772             }
4773         }
4774         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4775     }
4776     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4777
4778             j=BOARD_LEFT+1;
4779             initialPosition[1][j] = WhiteBishop;
4780             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4781             j=BOARD_RGHT-2;
4782             initialPosition[1][j] = WhiteRook;
4783             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4784     }
4785
4786     if( nrCastlingRights == -1) {
4787         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4788         /*       This sets default castling rights from none to normal corners   */
4789         /* Variants with other castling rights must set them themselves above    */
4790         nrCastlingRights = 6;
4791
4792         castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4793         castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4794         castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4795         castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4796         castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4797         castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4798      }
4799
4800      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4801      if(gameInfo.variant == VariantGreat) { // promotion commoners
4802         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4803         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4804         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4805         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4806      }
4807   if (appData.debugMode) {
4808     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4809   }
4810     if(shuffleOpenings) {
4811         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4812         startedFromSetupPosition = TRUE;
4813     }
4814     if(startedFromPositionFile) {
4815       /* [HGM] loadPos: use PositionFile for every new game */
4816       CopyBoard(initialPosition, filePosition);
4817       for(i=0; i<nrCastlingRights; i++)
4818           castlingRights[0][i] = initialRights[i] = fileRights[i];
4819       startedFromSetupPosition = TRUE;
4820     }
4821
4822     CopyBoard(boards[0], initialPosition);
4823     if(oldx != gameInfo.boardWidth ||
4824        oldy != gameInfo.boardHeight ||
4825        oldh != gameInfo.holdingsWidth
4826 #ifdef GOTHIC
4827        || oldv == VariantGothic ||        // For licensing popups
4828        gameInfo.variant == VariantGothic
4829 #endif
4830 #ifdef FALCON
4831        || oldv == VariantFalcon ||
4832        gameInfo.variant == VariantFalcon
4833 #endif
4834                                          )
4835       {
4836             InitDrawingSizes(-2 ,0);
4837       }
4838
4839     if (redraw)
4840       DrawPosition(TRUE, boards[currentMove]);
4841
4842 }
4843
4844 void
4845 SendBoard(cps, moveNum)
4846      ChessProgramState *cps;
4847      int moveNum;
4848 {
4849     char message[MSG_SIZ];
4850
4851     if (cps->useSetboard) {
4852       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4853       sprintf(message, "setboard %s\n", fen);
4854       SendToProgram(message, cps);
4855       free(fen);
4856
4857     } else {
4858       ChessSquare *bp;
4859       int i, j;
4860       /* Kludge to set black to move, avoiding the troublesome and now
4861        * deprecated "black" command.
4862        */
4863       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4864
4865       SendToProgram("edit\n", cps);
4866       SendToProgram("#\n", cps);
4867       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868         bp = &boards[moveNum][i][BOARD_LEFT];
4869         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870           if ((int) *bp < (int) BlackPawn) {
4871             sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4872                     AAA + j, ONE + i);
4873             if(message[0] == '+' || message[0] == '~') {
4874                 sprintf(message, "%c%c%c+\n",
4875                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4876                         AAA + j, ONE + i);
4877             }
4878             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879                 message[1] = BOARD_RGHT   - 1 - j + '1';
4880                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4881             }
4882             SendToProgram(message, cps);
4883           }
4884         }
4885       }
4886
4887       SendToProgram("c\n", cps);
4888       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4889         bp = &boards[moveNum][i][BOARD_LEFT];
4890         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4891           if (((int) *bp != (int) EmptySquare)
4892               && ((int) *bp >= (int) BlackPawn)) {
4893             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4894                     AAA + j, ONE + i);
4895             if(message[0] == '+' || message[0] == '~') {
4896                 sprintf(message, "%c%c%c+\n",
4897                         PieceToChar((ChessSquare)(DEMOTED *bp)),
4898                         AAA + j, ONE + i);
4899             }
4900             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4901                 message[1] = BOARD_RGHT   - 1 - j + '1';
4902                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4903             }
4904             SendToProgram(message, cps);
4905           }
4906         }
4907       }
4908
4909       SendToProgram(".\n", cps);
4910     }
4911     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4912 }
4913
4914 int
4915 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4916 {
4917     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4918     /* [HGM] add Shogi promotions */
4919     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4920     ChessSquare piece;
4921     ChessMove moveType;
4922     Boolean premove;
4923
4924     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4925     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
4926
4927     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4928       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4929         return FALSE;
4930
4931     piece = boards[currentMove][fromY][fromX];
4932     if(gameInfo.variant == VariantShogi) {
4933         promotionZoneSize = 3;
4934         highestPromotingPiece = (int)WhiteFerz;
4935     }
4936
4937     // next weed out all moves that do not touch the promotion zone at all
4938     if((int)piece >= BlackPawn) {
4939         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4940              return FALSE;
4941         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4942     } else {
4943         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
4944            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4945     }
4946
4947     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4948
4949     // weed out mandatory Shogi promotions
4950     if(gameInfo.variant == VariantShogi) {
4951         if(piece >= BlackPawn) {
4952             if(toY == 0 && piece == BlackPawn ||
4953                toY == 0 && piece == BlackQueen ||
4954                toY <= 1 && piece == BlackKnight) {
4955                 *promoChoice = '+';
4956                 return FALSE;
4957             }
4958         } else {
4959             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4960                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4961                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4962                 *promoChoice = '+';
4963                 return FALSE;
4964             }
4965         }
4966     }
4967
4968     // weed out obviously illegal Pawn moves
4969     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
4970         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4971         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4972         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4973         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4974         // note we are not allowed to test for valid (non-)capture, due to premove
4975     }
4976
4977     // we either have a choice what to promote to, or (in Shogi) whether to promote
4978     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4979         *promoChoice = PieceToChar(BlackFerz);  // no choice
4980         return FALSE;
4981     }
4982     if(appData.alwaysPromoteToQueen) { // predetermined
4983         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4984              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4985         else *promoChoice = PieceToChar(BlackQueen);
4986         return FALSE;
4987     }
4988
4989     // suppress promotion popup on illegal moves that are not premoves
4990     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4991               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
4992     if(appData.testLegality && !premove) {
4993         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4994                         epStatus[currentMove], castlingRights[currentMove],
4995                         fromY, fromX, toY, toX, NULLCHAR);
4996         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
4997            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
4998             return FALSE;
4999     }
5000
5001     return TRUE;
5002 }
5003
5004 int
5005 InPalace(row, column)
5006      int row, column;
5007 {   /* [HGM] for Xiangqi */
5008     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5009          column < (BOARD_WIDTH + 4)/2 &&
5010          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5011     return FALSE;
5012 }
5013
5014 int
5015 PieceForSquare (x, y)
5016      int x;
5017      int y;
5018 {
5019   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5020      return -1;
5021   else
5022      return boards[currentMove][y][x];
5023 }
5024
5025 int
5026 OKToStartUserMove(x, y)
5027      int x, y;
5028 {
5029     ChessSquare from_piece;
5030     int white_piece;
5031
5032     if (matchMode) return FALSE;
5033     if (gameMode == EditPosition) return TRUE;
5034
5035     if (x >= 0 && y >= 0)
5036       from_piece = boards[currentMove][y][x];
5037     else
5038       from_piece = EmptySquare;
5039
5040     if (from_piece == EmptySquare) return FALSE;
5041
5042     white_piece = (int)from_piece >= (int)WhitePawn &&
5043       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5044
5045     switch (gameMode) {
5046       case PlayFromGameFile:
5047       case AnalyzeFile:
5048       case TwoMachinesPlay:
5049       case EndOfGame:
5050         return FALSE;
5051
5052       case IcsObserving:
5053       case IcsIdle:
5054         return FALSE;
5055
5056       case MachinePlaysWhite:
5057       case IcsPlayingBlack:
5058         if (appData.zippyPlay) return FALSE;
5059         if (white_piece) {
5060             DisplayMoveError(_("You are playing Black"));
5061             return FALSE;
5062         }
5063         break;
5064
5065       case MachinePlaysBlack:
5066       case IcsPlayingWhite:
5067         if (appData.zippyPlay) return FALSE;
5068         if (!white_piece) {
5069             DisplayMoveError(_("You are playing White"));
5070             return FALSE;
5071         }
5072         break;
5073
5074       case EditGame:
5075         if (!white_piece && WhiteOnMove(currentMove)) {
5076             DisplayMoveError(_("It is White's turn"));
5077             return FALSE;
5078         }
5079         if (white_piece && !WhiteOnMove(currentMove)) {
5080             DisplayMoveError(_("It is Black's turn"));
5081             return FALSE;
5082         }
5083         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5084             /* Editing correspondence game history */
5085             /* Could disallow this or prompt for confirmation */
5086             cmailOldMove = -1;
5087         }
5088         if (currentMove < forwardMostMove) {
5089             /* Discarding moves */
5090             /* Could prompt for confirmation here,
5091                but I don't think that's such a good idea */
5092             forwardMostMove = currentMove;
5093         }
5094         break;
5095
5096       case BeginningOfGame:
5097         if (appData.icsActive) return FALSE;
5098         if (!appData.noChessProgram) {
5099             if (!white_piece) {
5100                 DisplayMoveError(_("You are playing White"));
5101                 return FALSE;
5102             }
5103         }
5104         break;
5105
5106       case Training:
5107         if (!white_piece && WhiteOnMove(currentMove)) {
5108             DisplayMoveError(_("It is White's turn"));
5109             return FALSE;
5110         }
5111         if (white_piece && !WhiteOnMove(currentMove)) {
5112             DisplayMoveError(_("It is Black's turn"));
5113             return FALSE;
5114         }
5115         break;
5116
5117       default:
5118       case IcsExamining:
5119         break;
5120     }
5121     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5122         && gameMode != AnalyzeFile && gameMode != Training) {
5123         DisplayMoveError(_("Displayed position is not current"));
5124         return FALSE;
5125     }
5126     return TRUE;
5127 }
5128
5129 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5130 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5131 int lastLoadGameUseList = FALSE;
5132 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5133 ChessMove lastLoadGameStart = (ChessMove) 0;
5134
5135 ChessMove
5136 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5137      int fromX, fromY, toX, toY;
5138      int promoChar;
5139      Boolean captureOwn;
5140 {
5141     ChessMove moveType;
5142     ChessSquare pdown, pup;
5143
5144     /* Check if the user is playing in turn.  This is complicated because we
5145        let the user "pick up" a piece before it is his turn.  So the piece he
5146        tried to pick up may have been captured by the time he puts it down!
5147        Therefore we use the color the user is supposed to be playing in this
5148        test, not the color of the piece that is currently on the starting
5149        square---except in EditGame mode, where the user is playing both
5150        sides; fortunately there the capture race can't happen.  (It can
5151        now happen in IcsExamining mode, but that's just too bad.  The user
5152        will get a somewhat confusing message in that case.)
5153        */
5154
5155     switch (gameMode) {
5156       case PlayFromGameFile:
5157       case AnalyzeFile:
5158       case TwoMachinesPlay:
5159       case EndOfGame:
5160       case IcsObserving:
5161       case IcsIdle:
5162         /* We switched into a game mode where moves are not accepted,
5163            perhaps while the mouse button was down. */
5164         return ImpossibleMove;
5165
5166       case MachinePlaysWhite:
5167         /* User is moving for Black */
5168         if (WhiteOnMove(currentMove)) {
5169             DisplayMoveError(_("It is White's turn"));
5170             return ImpossibleMove;
5171         }
5172         break;
5173
5174       case MachinePlaysBlack:
5175         /* User is moving for White */
5176         if (!WhiteOnMove(currentMove)) {
5177             DisplayMoveError(_("It is Black's turn"));
5178             return ImpossibleMove;
5179         }
5180         break;
5181
5182       case EditGame:
5183       case IcsExamining:
5184       case BeginningOfGame:
5185       case AnalyzeMode:
5186       case Training:
5187         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5188             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5189             /* User is moving for Black */
5190             if (WhiteOnMove(currentMove)) {
5191                 DisplayMoveError(_("It is White's turn"));
5192                 return ImpossibleMove;
5193             }
5194         } else {
5195             /* User is moving for White */
5196             if (!WhiteOnMove(currentMove)) {
5197                 DisplayMoveError(_("It is Black's turn"));
5198                 return ImpossibleMove;
5199             }
5200         }
5201         break;
5202
5203       case IcsPlayingBlack:
5204         /* User is moving for Black */
5205         if (WhiteOnMove(currentMove)) {
5206             if (!appData.premove) {
5207                 DisplayMoveError(_("It is White's turn"));
5208             } else if (toX >= 0 && toY >= 0) {
5209                 premoveToX = toX;
5210                 premoveToY = toY;
5211                 premoveFromX = fromX;
5212                 premoveFromY = fromY;
5213                 premovePromoChar = promoChar;
5214                 gotPremove = 1;
5215                 if (appData.debugMode)
5216                     fprintf(debugFP, "Got premove: fromX %d,"
5217                             "fromY %d, toX %d, toY %d\n",
5218                             fromX, fromY, toX, toY);
5219             }
5220             return ImpossibleMove;
5221         }
5222         break;
5223
5224       case IcsPlayingWhite:
5225         /* User is moving for White */
5226         if (!WhiteOnMove(currentMove)) {
5227             if (!appData.premove) {
5228                 DisplayMoveError(_("It is Black's turn"));
5229             } else if (toX >= 0 && toY >= 0) {
5230                 premoveToX = toX;
5231                 premoveToY = toY;
5232                 premoveFromX = fromX;
5233                 premoveFromY = fromY;
5234                 premovePromoChar = promoChar;
5235                 gotPremove = 1;
5236                 if (appData.debugMode)
5237                     fprintf(debugFP, "Got premove: fromX %d,"
5238                             "fromY %d, toX %d, toY %d\n",
5239                             fromX, fromY, toX, toY);
5240             }
5241             return ImpossibleMove;
5242         }
5243         break;
5244
5245       default:
5246         break;
5247
5248       case EditPosition:
5249         /* EditPosition, empty square, or different color piece;
5250            click-click move is possible */
5251         if (toX == -2 || toY == -2) {
5252             boards[0][fromY][fromX] = EmptySquare;
5253             return AmbiguousMove;
5254         } else if (toX >= 0 && toY >= 0) {
5255             boards[0][toY][toX] = boards[0][fromY][fromX];
5256             boards[0][fromY][fromX] = EmptySquare;
5257             return AmbiguousMove;
5258         }
5259         return ImpossibleMove;
5260     }
5261
5262     pdown = boards[currentMove][fromY][fromX];
5263     pup = boards[currentMove][toY][toX];
5264
5265     /* [HGM] If move started in holdings, it means a drop */
5266     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5267          if( pup != EmptySquare ) return ImpossibleMove;
5268          if(appData.testLegality) {
5269              /* it would be more logical if LegalityTest() also figured out
5270               * which drops are legal. For now we forbid pawns on back rank.
5271               * Shogi is on its own here...
5272               */
5273              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5274                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5275                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5276          }
5277          return WhiteDrop; /* Not needed to specify white or black yet */
5278     }
5279
5280     userOfferedDraw = FALSE;
5281
5282     /* [HGM] always test for legality, to get promotion info */
5283     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5284                           epStatus[currentMove], castlingRights[currentMove],
5285                                          fromY, fromX, toY, toX, promoChar);
5286     /* [HGM] but possibly ignore an IllegalMove result */
5287     if (appData.testLegality) {
5288         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5289             DisplayMoveError(_("Illegal move"));
5290             return ImpossibleMove;
5291         }
5292     }
5293     if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5294     return moveType;
5295     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5296        function is made into one that returns an OK move type if FinishMove
5297        should be called. This to give the calling driver routine the
5298        opportunity to finish the userMove input with a promotion popup,
5299        without bothering the user with this for invalid or illegal moves */
5300
5301 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5302 }
5303
5304 /* Common tail of UserMoveEvent and DropMenuEvent */
5305 int
5306 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5307      ChessMove moveType;
5308      int fromX, fromY, toX, toY;
5309      /*char*/int promoChar;
5310 {
5311   char *bookHit = 0;
5312
5313   if(appData.debugMode)
5314     fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5315
5316   if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5317     {
5318       // [HGM] superchess: suppress promotions to non-available piece
5319       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5320       if(WhiteOnMove(currentMove))
5321         {
5322           if(!boards[currentMove][k][BOARD_WIDTH-2])
5323             return 0;
5324         }
5325       else
5326         {
5327           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5328             return 0;
5329         }
5330     }
5331   
5332   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5333      move type in caller when we know the move is a legal promotion */
5334   if(moveType == NormalMove && promoChar)
5335     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5336
5337   if(appData.debugMode) 
5338     fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5339
5340   /* [HGM] convert drag-and-drop piece drops to standard form */
5341   if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) 
5342     {
5343       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5344       if(appData.debugMode) 
5345         fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5346                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5347       //         fromX = boards[currentMove][fromY][fromX];
5348       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5349       if(fromX == 0) 
5350         fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5351
5352       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5353
5354       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) 
5355         fromX++; 
5356
5357       fromY = DROP_RANK;
5358     }
5359
5360   /* [HGM] <popupFix> The following if has been moved here from
5361      UserMoveEvent(). Because it seemed to belon here (why not allow
5362      piece drops in training games?), and because it can only be
5363      performed after it is known to what we promote. */
5364   if (gameMode == Training)
5365     {
5366       /* compare the move played on the board to the next move in the
5367        * game. If they match, display the move and the opponent's response.
5368        * If they don't match, display an error message.
5369        */
5370       int saveAnimate;
5371       Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5372       CopyBoard(testBoard, boards[currentMove]);
5373       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5374
5375       if (CompareBoards(testBoard, boards[currentMove+1]))
5376         {
5377           ForwardInner(currentMove+1);
5378
5379           /* Autoplay the opponent's response.
5380            * if appData.animate was TRUE when Training mode was entered,
5381            * the response will be animated.
5382            */
5383           saveAnimate = appData.animate;
5384           appData.animate = animateTraining;
5385           ForwardInner(currentMove+1);
5386           appData.animate = saveAnimate;
5387
5388           /* check for the end of the game */
5389           if (currentMove >= forwardMostMove)
5390             {
5391               gameMode = PlayFromGameFile;
5392               ModeHighlight();
5393               SetTrainingModeOff();
5394               DisplayInformation(_("End of game"));
5395             }
5396         }
5397       else
5398         {
5399           DisplayError(_("Incorrect move"), 0);
5400         }
5401       return 1;
5402     }
5403
5404   /* Ok, now we know that the move is good, so we can kill
5405      the previous line in Analysis Mode */
5406   if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5407     {
5408       forwardMostMove = currentMove;
5409     }
5410
5411   /* If we need the chess program but it's dead, restart it */
5412   ResurrectChessProgram();
5413
5414   /* A user move restarts a paused game*/
5415   if (pausing)
5416     PauseEvent();
5417
5418   thinkOutput[0] = NULLCHAR;
5419
5420   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5421
5422   if (gameMode == BeginningOfGame)
5423     {
5424       if (appData.noChessProgram)
5425         {
5426           gameMode = EditGame;
5427           SetGameInfo();
5428         }
5429       else
5430         {
5431           char buf[MSG_SIZ];
5432           gameMode = MachinePlaysBlack;
5433           StartClocks();
5434           SetGameInfo();
5435           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5436           DisplayTitle(buf);
5437           if (first.sendName)
5438             {
5439               sprintf(buf, "name %s\n", gameInfo.white);
5440               SendToProgram(buf, &first);
5441             }
5442           StartClocks();
5443         }
5444       ModeHighlight();
5445     }
5446   if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5447
5448   /* Relay move to ICS or chess engine */
5449   if (appData.icsActive)
5450     {
5451       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5452           gameMode == IcsExamining)
5453         {
5454           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5455           ics_user_moved = 1;
5456         }
5457     }
5458   else
5459     {
5460       if (first.sendTime && (gameMode == BeginningOfGame ||
5461                              gameMode == MachinePlaysWhite ||
5462                              gameMode == MachinePlaysBlack))
5463         {
5464           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5465         }
5466       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5467         {
5468           // [HGM] book: if program might be playing, let it use book
5469           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5470           first.maybeThinking = TRUE;
5471         }
5472       else
5473         SendMoveToProgram(forwardMostMove-1, &first);
5474       if (currentMove == cmailOldMove + 1)
5475         {
5476           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5477         }
5478     }
5479
5480   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5481
5482   switch (gameMode)
5483     {
5484     case EditGame:
5485       switch (MateTest(boards[currentMove], PosFlags(currentMove),
5486                        EP_UNKNOWN, castlingRights[currentMove]) )
5487         {
5488         case MT_NONE:
5489         case MT_CHECK:
5490           break;
5491         case MT_CHECKMATE:
5492         case MT_STAINMATE:
5493           if (WhiteOnMove(currentMove))
5494             {
5495               GameEnds(BlackWins, "Black mates", GE_PLAYER);
5496             }
5497           else
5498             {
5499               GameEnds(WhiteWins, "White mates", GE_PLAYER);
5500             }
5501           break;
5502         case MT_STALEMATE:
5503           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5504           break;
5505     }
5506       break;
5507
5508     case MachinePlaysBlack:
5509     case MachinePlaysWhite:
5510       /* disable certain menu options while machine is thinking */
5511       SetMachineThinkingEnables();
5512       break;
5513
5514     default:
5515       break;
5516     }
5517
5518   if(bookHit)
5519     { // [HGM] book: simulate book reply
5520       static char bookMove[MSG_SIZ]; // a bit generous?
5521
5522       programStats.nodes = programStats.depth = programStats.time =
5523         programStats.score = programStats.got_only_move = 0;
5524       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5525
5526       strcpy(bookMove, "move ");
5527       strcat(bookMove, bookHit);
5528       HandleMachineMove(bookMove, &first);
5529     }
5530
5531   return 1;
5532 }
5533
5534 void
5535 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5536      int fromX, fromY, toX, toY;
5537      int promoChar;
5538 {
5539     /* [HGM] This routine was added to allow calling of its two logical
5540        parts from other modules in the old way. Before, UserMoveEvent()
5541        automatically called FinishMove() if the move was OK, and returned
5542        otherwise. I separated the two, in order to make it possible to
5543        slip a promotion popup in between. But that it always needs two
5544        calls, to the first part, (now called UserMoveTest() ), and to
5545        FinishMove if the first part succeeded. Calls that do not need
5546        to do anything in between, can call this routine the old way.
5547     */
5548     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5549 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5550     if(moveType == AmbiguousMove)
5551         DrawPosition(FALSE, boards[currentMove]);
5552     else if(moveType != ImpossibleMove && moveType != Comment)
5553         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5554 }
5555
5556 void LeftClick(ClickType clickType, int xPix, int yPix)
5557 {
5558     int x, y;
5559     Boolean saveAnimate;
5560     static int second = 0, promotionChoice = 0;
5561     char promoChoice = NULLCHAR;
5562
5563     if (clickType == Press) ErrorPopDown();
5564
5565     x = EventToSquare(xPix, BOARD_WIDTH);
5566     y = EventToSquare(yPix, BOARD_HEIGHT);
5567     if (!flipView && y >= 0) {
5568         y = BOARD_HEIGHT - 1 - y;
5569     }
5570     if (flipView && x >= 0) {
5571         x = BOARD_WIDTH - 1 - x;
5572     }
5573
5574     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5575         if(clickType == Release) return; // ignore upclick of click-click destination
5576         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5577         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5578         if(gameInfo.holdingsWidth && 
5579                 (WhiteOnMove(currentMove) 
5580                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5581                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5582             // click in right holdings, for determining promotion piece
5583             ChessSquare p = boards[currentMove][y][x];
5584             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5585             if(p != EmptySquare) {
5586                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5587                 fromX = fromY = -1;
5588                 return;
5589             }
5590         }
5591         DrawPosition(FALSE, boards[currentMove]);
5592         return;
5593     }
5594
5595     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5596     if(clickType == Press
5597             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5598               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5599               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5600         return;
5601
5602     if (fromX == -1) {
5603         if (clickType == Press) {
5604             /* First square */
5605             if (OKToStartUserMove(x, y)) {
5606                 fromX = x;
5607                 fromY = y;
5608                 second = 0;
5609                 DragPieceBegin(xPix, yPix);
5610                 if (appData.highlightDragging) {
5611                     SetHighlights(x, y, -1, -1);
5612                 }
5613             }
5614         }
5615         return;
5616     }
5617
5618     /* fromX != -1 */
5619     if (clickType == Press && gameMode != EditPosition) {
5620         ChessSquare fromP;
5621         ChessSquare toP;
5622         int frc;
5623
5624         // ignore off-board to clicks
5625         if(y < 0 || x < 0) return;
5626
5627         /* Check if clicking again on the same color piece */
5628         fromP = boards[currentMove][fromY][fromX];
5629         toP = boards[currentMove][y][x];
5630         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5631         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5632              WhitePawn <= toP && toP <= WhiteKing &&
5633              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5634              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5635             (BlackPawn <= fromP && fromP <= BlackKing && 
5636              BlackPawn <= toP && toP <= BlackKing &&
5637              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5638              !(fromP == BlackKing && toP == BlackRook && frc))) {
5639             /* Clicked again on same color piece -- changed his mind */
5640             second = (x == fromX && y == fromY);
5641             if (appData.highlightDragging) {
5642                 SetHighlights(x, y, -1, -1);
5643             } else {
5644                 ClearHighlights();
5645             }
5646             if (OKToStartUserMove(x, y)) {
5647                 fromX = x;
5648                 fromY = y;
5649                 DragPieceBegin(xPix, yPix);
5650             }
5651             return;
5652         }
5653         // ignore to-clicks in holdings
5654         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5655     }
5656
5657     if (clickType == Release && (x == fromX && y == fromY ||
5658         x < BOARD_LEFT || x >= BOARD_RGHT)) {
5659
5660         // treat drags into holding as click on start square
5661         x = fromX; y = fromY;
5662
5663         DragPieceEnd(xPix, yPix);
5664         if (appData.animateDragging) {
5665             /* Undo animation damage if any */
5666             DrawPosition(FALSE, NULL);
5667         }
5668         if (second) {
5669             /* Second up/down in same square; just abort move */
5670             second = 0;
5671             fromX = fromY = -1;
5672             ClearHighlights();
5673             gotPremove = 0;
5674             ClearPremoveHighlights();
5675         } else {
5676             /* First upclick in same square; start click-click mode */
5677             SetHighlights(x, y, -1, -1);
5678         }
5679         return;
5680     }
5681
5682     /* we now have a different from- and to-square */
5683     /* Completed move */
5684     toX = x;
5685     toY = y;
5686     saveAnimate = appData.animate;
5687     if (clickType == Press) {
5688         /* Finish clickclick move */
5689         if (appData.animate || appData.highlightLastMove) {
5690             SetHighlights(fromX, fromY, toX, toY);
5691         } else {
5692             ClearHighlights();
5693         }
5694     } else {
5695         /* Finish drag move */
5696         if (appData.highlightLastMove) {
5697             SetHighlights(fromX, fromY, toX, toY);
5698         } else {
5699             ClearHighlights();
5700         }
5701         DragPieceEnd(xPix, yPix);
5702         /* Don't animate move and drag both */
5703         appData.animate = FALSE;
5704     }
5705     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5706         SetHighlights(fromX, fromY, toX, toY);
5707         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5708             // [HGM] super: promotion to captured piece selected from holdings
5709             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5710             promotionChoice = TRUE;
5711             // kludge follows to temporarily execute move on display, without promoting yet
5712             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5713             boards[currentMove][toY][toX] = p;
5714             DrawPosition(FALSE, boards[currentMove]);
5715             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5716             boards[currentMove][toY][toX] = q;
5717             DisplayMessage("Click in holdings to choose piece", "");
5718             return;
5719         }
5720         PromotionPopUp();
5721     } else {
5722         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5723         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5724         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5725         fromX = fromY = -1;
5726     }
5727     appData.animate = saveAnimate;
5728     if (appData.animate || appData.animateDragging) {
5729         /* Undo animation damage if needed */
5730         DrawPosition(FALSE, NULL);
5731     }
5732 }
5733
5734 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5735 {
5736 //    char * hint = lastHint;
5737     FrontEndProgramStats stats;
5738
5739     stats.which = cps == &first ? 0 : 1;
5740     stats.depth = cpstats->depth;
5741     stats.nodes = cpstats->nodes;
5742     stats.score = cpstats->score;
5743     stats.time = cpstats->time;
5744     stats.pv = cpstats->movelist;
5745     stats.hint = lastHint;
5746     stats.an_move_index = 0;
5747     stats.an_move_count = 0;
5748
5749     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5750         stats.hint = cpstats->move_name;
5751         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5752         stats.an_move_count = cpstats->nr_moves;
5753     }
5754
5755     SetProgramStats( &stats );
5756 }
5757
5758 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5759 {   // [HGM] book: this routine intercepts moves to simulate book replies
5760     char *bookHit = NULL;
5761
5762     //first determine if the incoming move brings opponent into his book
5763     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5764         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5765     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5766     if(bookHit != NULL && !cps->bookSuspend) {
5767         // make sure opponent is not going to reply after receiving move to book position
5768         SendToProgram("force\n", cps);
5769         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5770     }
5771     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5772     // now arrange restart after book miss
5773     if(bookHit) {
5774         // after a book hit we never send 'go', and the code after the call to this routine
5775         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5776         char buf[MSG_SIZ];
5777         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5778         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5779         SendToProgram(buf, cps);
5780         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5781     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5782         SendToProgram("go\n", cps);
5783         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5784     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5785         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5786             SendToProgram("go\n", cps);
5787         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5788     }
5789     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5790 }
5791
5792 char *savedMessage;
5793 ChessProgramState *savedState;
5794 void DeferredBookMove(void)
5795 {
5796         if(savedState->lastPing != savedState->lastPong)
5797                     ScheduleDelayedEvent(DeferredBookMove, 10);
5798         else
5799         HandleMachineMove(savedMessage, savedState);
5800 }
5801
5802 void
5803 HandleMachineMove(message, cps)
5804      char *message;
5805      ChessProgramState *cps;
5806 {
5807     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5808     char realname[MSG_SIZ];
5809     int fromX, fromY, toX, toY;
5810     ChessMove moveType;
5811     char promoChar;
5812     char *p;
5813     int machineWhite;
5814     char *bookHit;
5815
5816 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5817     /*
5818      * Kludge to ignore BEL characters
5819      */
5820     while (*message == '\007') message++;
5821
5822     /*
5823      * [HGM] engine debug message: ignore lines starting with '#' character
5824      */
5825     if(cps->debug && *message == '#') return;
5826
5827     /*
5828      * Look for book output
5829      */
5830     if (cps == &first && bookRequested) {
5831         if (message[0] == '\t' || message[0] == ' ') {
5832             /* Part of the book output is here; append it */
5833             strcat(bookOutput, message);
5834             strcat(bookOutput, "  \n");
5835             return;
5836         } else if (bookOutput[0] != NULLCHAR) {
5837             /* All of book output has arrived; display it */
5838             char *p = bookOutput;
5839             while (*p != NULLCHAR) {
5840                 if (*p == '\t') *p = ' ';
5841                 p++;
5842             }
5843             DisplayInformation(bookOutput);
5844             bookRequested = FALSE;
5845             /* Fall through to parse the current output */
5846         }
5847     }
5848
5849     /*
5850      * Look for machine move.
5851      */
5852     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5853         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5854     {
5855         /* This method is only useful on engines that support ping */
5856         if (cps->lastPing != cps->lastPong) {
5857           if (gameMode == BeginningOfGame) {
5858             /* Extra move from before last new; ignore */
5859             if (appData.debugMode) {
5860                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5861             }
5862           } else {
5863             if (appData.debugMode) {
5864                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5865                         cps->which, gameMode);
5866             }
5867
5868             SendToProgram("undo\n", cps);
5869           }
5870           return;
5871         }
5872
5873         switch (gameMode) {
5874           case BeginningOfGame:
5875             /* Extra move from before last reset; ignore */
5876             if (appData.debugMode) {
5877                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5878             }
5879             return;
5880
5881           case EndOfGame:
5882           case IcsIdle:
5883           default:
5884             /* Extra move after we tried to stop.  The mode test is
5885                not a reliable way of detecting this problem, but it's
5886                the best we can do on engines that don't support ping.
5887             */
5888             if (appData.debugMode) {
5889                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5890                         cps->which, gameMode);
5891             }
5892             SendToProgram("undo\n", cps);
5893             return;
5894
5895           case MachinePlaysWhite:
5896           case IcsPlayingWhite:
5897             machineWhite = TRUE;
5898             break;
5899
5900           case MachinePlaysBlack:
5901           case IcsPlayingBlack:
5902             machineWhite = FALSE;
5903             break;
5904
5905           case TwoMachinesPlay:
5906             machineWhite = (cps->twoMachinesColor[0] == 'w');
5907             break;
5908         }
5909         if (WhiteOnMove(forwardMostMove) != machineWhite) {
5910             if (appData.debugMode) {
5911                 fprintf(debugFP,
5912                         "Ignoring move out of turn by %s, gameMode %d"
5913                         ", forwardMost %d\n",
5914                         cps->which, gameMode, forwardMostMove);
5915             }
5916             return;
5917         }
5918
5919     if (appData.debugMode) { int f = forwardMostMove;
5920         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5921                 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5922     }
5923         if(cps->alphaRank) AlphaRank(machineMove, 4);
5924         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5925                               &fromX, &fromY, &toX, &toY, &promoChar)) {
5926             /* Machine move could not be parsed; ignore it. */
5927             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5928                     machineMove, cps->which);
5929             DisplayError(buf1, 0);
5930             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5931                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5932             if (gameMode == TwoMachinesPlay) {
5933               GameEnds(machineWhite ? BlackWins : WhiteWins,
5934                        buf1, GE_XBOARD);
5935             }
5936             return;
5937         }
5938
5939         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5940         /* So we have to redo legality test with true e.p. status here,  */
5941         /* to make sure an illegal e.p. capture does not slip through,   */
5942         /* to cause a forfeit on a justified illegal-move complaint      */
5943         /* of the opponent.                                              */
5944         if( gameMode==TwoMachinesPlay && appData.testLegality
5945             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5946                                                               ) {
5947            ChessMove moveType;
5948            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5949                         epStatus[forwardMostMove], castlingRights[forwardMostMove],
5950                              fromY, fromX, toY, toX, promoChar);
5951             if (appData.debugMode) {
5952                 int i;
5953                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5954                     castlingRights[forwardMostMove][i], castlingRank[i]);
5955                 fprintf(debugFP, "castling rights\n");
5956             }
5957             if(moveType == IllegalMove) {
5958                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5959                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5960                 GameEnds(machineWhite ? BlackWins : WhiteWins,
5961                            buf1, GE_XBOARD);
5962                 return;
5963            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5964            /* [HGM] Kludge to handle engines that send FRC-style castling
5965               when they shouldn't (like TSCP-Gothic) */
5966            switch(moveType) {
5967              case WhiteASideCastleFR:
5968              case BlackASideCastleFR:
5969                toX+=2;
5970                currentMoveString[2]++;
5971                break;
5972              case WhiteHSideCastleFR:
5973              case BlackHSideCastleFR:
5974                toX--;
5975                currentMoveString[2]--;
5976                break;
5977              default: ; // nothing to do, but suppresses warning of pedantic compilers
5978            }
5979         }
5980         hintRequested = FALSE;
5981         lastHint[0] = NULLCHAR;
5982         bookRequested = FALSE;
5983         /* Program may be pondering now */
5984         cps->maybeThinking = TRUE;
5985         if (cps->sendTime == 2) cps->sendTime = 1;
5986         if (cps->offeredDraw) cps->offeredDraw--;
5987
5988 #if ZIPPY
5989         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5990             first.initDone) {
5991           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5992           ics_user_moved = 1;
5993           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5994                 char buf[3*MSG_SIZ];
5995
5996                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5997                         programStats.score / 100.,
5998                         programStats.depth,
5999                         programStats.time / 100.,
6000                         (unsigned int)programStats.nodes,
6001                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6002                         programStats.movelist);
6003                 SendToICS(buf);
6004 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6005           }
6006         }
6007 #endif
6008         /* currentMoveString is set as a side-effect of ParseOneMove */
6009         strcpy(machineMove, currentMoveString);
6010         strcat(machineMove, "\n");
6011         strcpy(moveList[forwardMostMove], machineMove);
6012
6013         /* [AS] Save move info and clear stats for next move */
6014         pvInfoList[ forwardMostMove ].score = programStats.score;
6015         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6016         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6017         ClearProgramStats();
6018         thinkOutput[0] = NULLCHAR;
6019         hiddenThinkOutputState = 0;
6020
6021         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6022
6023         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6024         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6025             int count = 0;
6026
6027             while( count < adjudicateLossPlies ) {
6028                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6029
6030                 if( count & 1 ) {
6031                     score = -score; /* Flip score for winning side */
6032                 }
6033
6034                 if( score > adjudicateLossThreshold ) {
6035                     break;
6036                 }
6037
6038                 count++;
6039             }
6040
6041             if( count >= adjudicateLossPlies ) {
6042                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6043
6044                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6045                     "Xboard adjudication",
6046                     GE_XBOARD );
6047
6048                 return;
6049             }
6050         }
6051
6052         if( gameMode == TwoMachinesPlay ) {
6053           // [HGM] some adjudications useful with buggy engines
6054             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6055           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6056
6057
6058             if( appData.testLegality )
6059             {   /* [HGM] Some more adjudications for obstinate engines */
6060                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6061                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6062                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6063                 static int moveCount = 6;
6064                 ChessMove result;
6065                 char *reason = NULL;
6066
6067                 /* Count what is on board. */
6068                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6069                 {   ChessSquare p = boards[forwardMostMove][i][j];
6070                     int m=i;
6071
6072                     switch((int) p)
6073                     {   /* count B,N,R and other of each side */
6074                         case WhiteKing:
6075                         case BlackKing:
6076                              NrK++; break; // [HGM] atomic: count Kings
6077                         case WhiteKnight:
6078                              NrWN++; break;
6079                         case WhiteBishop:
6080                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6081                              bishopsColor |= 1 << ((i^j)&1);
6082                              NrWB++; break;
6083                         case BlackKnight:
6084                              NrBN++; break;
6085                         case BlackBishop:
6086                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6087                              bishopsColor |= 1 << ((i^j)&1);
6088                              NrBB++; break;
6089                         case WhiteRook:
6090                              NrWR++; break;
6091                         case BlackRook:
6092                              NrBR++; break;
6093                         case WhiteQueen:
6094                              NrWQ++; break;
6095                         case BlackQueen:
6096                              NrBQ++; break;
6097                         case EmptySquare:
6098                              break;
6099                         case BlackPawn:
6100                              m = 7-i;
6101                         case WhitePawn:
6102                              PawnAdvance += m; NrPawns++;
6103                     }
6104                     NrPieces += (p != EmptySquare);
6105                     NrW += ((int)p < (int)BlackPawn);
6106                     if(gameInfo.variant == VariantXiangqi &&
6107                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6108                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6109                         NrW -= ((int)p < (int)BlackPawn);
6110                     }
6111                 }
6112
6113                 /* Some material-based adjudications that have to be made before stalemate test */
6114                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6115                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6116                      epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6117                      if(appData.checkMates) {
6118                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6119                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6120                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6121                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6122                          return;
6123                      }
6124                 }
6125
6126                 /* Bare King in Shatranj (loses) or Losers (wins) */
6127                 if( NrW == 1 || NrPieces - NrW == 1) {
6128                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6129                      epStatus[forwardMostMove] = EP_WINS;  // mark as win, so it becomes claimable
6130                      if(appData.checkMates) {
6131                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6132                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6133                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6134                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6135                          return;
6136                      }
6137                   } else
6138                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6139                   {    /* bare King */
6140                         epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6141                         if(appData.checkMates) {
6142                             /* but only adjudicate if adjudication enabled */
6143                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6144                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6145                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6146                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6147                             return;
6148                         }
6149                   }
6150                 } else bare = 1;
6151
6152
6153             // don't wait for engine to announce game end if we can judge ourselves
6154             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6155                                        castlingRights[forwardMostMove]) ) {
6156               case MT_CHECK:
6157                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6158                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6159                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6160                         if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6161                             checkCnt++;
6162                         if(checkCnt >= 2) {
6163                             reason = "Xboard adjudication: 3rd check";
6164                             epStatus[forwardMostMove] = EP_CHECKMATE;
6165                             break;
6166                         }
6167                     }
6168                 }
6169               case MT_NONE:
6170               default:
6171                 break;
6172               case MT_STALEMATE:
6173               case MT_STAINMATE:
6174                 reason = "Xboard adjudication: Stalemate";
6175                 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6176                     epStatus[forwardMostMove] = EP_STALEMATE;   // default result for stalemate is draw
6177                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6178                         epStatus[forwardMostMove] = EP_WINS;    // in these variants stalemated is always a win
6179                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6180                         epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6181                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6182                                                                         EP_CHECKMATE : EP_WINS);
6183                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6184                         epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6185                 }
6186                 break;
6187               case MT_CHECKMATE:
6188                 reason = "Xboard adjudication: Checkmate";
6189                 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6190                 break;
6191             }
6192
6193                 switch(i = epStatus[forwardMostMove]) {
6194                     case EP_STALEMATE:
6195                         result = GameIsDrawn; break;
6196                     case EP_CHECKMATE:
6197                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6198                     case EP_WINS:
6199                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6200                     default:
6201                         result = (ChessMove) 0;
6202                 }
6203                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6204                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6205                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6206                     GameEnds( result, reason, GE_XBOARD );
6207                     return;
6208                 }
6209
6210                 /* Next absolutely insufficient mating material. */
6211                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6212                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6213                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6214                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6215                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6216
6217                      /* always flag draws, for judging claims */
6218                      epStatus[forwardMostMove] = EP_INSUF_DRAW;
6219
6220                      if(appData.materialDraws) {
6221                          /* but only adjudicate them if adjudication enabled */
6222                          SendToProgram("force\n", cps->other); // suppress reply
6223                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6224                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6225                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6226                          return;
6227                      }
6228                 }
6229
6230                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6231                 if(NrPieces == 4 &&
6232                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6233                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6234                    || NrWN==2 || NrBN==2     /* KNNK */
6235                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6236                   ) ) {
6237                      if(--moveCount < 0 && appData.trivialDraws)
6238                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6239                           SendToProgram("force\n", cps->other); // suppress reply
6240                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6241                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6242                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6243                           return;
6244                      }
6245                 } else moveCount = 6;
6246             }
6247           }
6248           
6249           if (appData.debugMode) { int i;
6250             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6251                     forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6252                     appData.drawRepeats);
6253             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6254               fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6255             
6256           }
6257
6258                 /* Check for rep-draws */
6259                 count = 0;
6260                 for(k = forwardMostMove-2;
6261                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6262                         epStatus[k] < EP_UNKNOWN &&
6263                         epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6264                     k-=2)
6265                 {   int rights=0;
6266                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6267                         /* compare castling rights */
6268                         if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6269                              (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6270                                 rights++; /* King lost rights, while rook still had them */
6271                         if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6272                             if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6273                                 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6274                                    rights++; /* but at least one rook lost them */
6275                         }
6276                         if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6277                              (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6278                                 rights++;
6279                         if( castlingRights[forwardMostMove][5] >= 0 ) {
6280                             if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6281                                 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6282                                    rights++;
6283                         }
6284                         if( rights == 0 && ++count > appData.drawRepeats-2
6285                             && appData.drawRepeats > 1) {
6286                              /* adjudicate after user-specified nr of repeats */
6287                              SendToProgram("force\n", cps->other); // suppress reply
6288                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6289                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6290                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6291                                 // [HGM] xiangqi: check for forbidden perpetuals
6292                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6293                                 for(m=forwardMostMove; m>k; m-=2) {
6294                                     if(MateTest(boards[m], PosFlags(m),
6295                                                         EP_NONE, castlingRights[m]) != MT_CHECK)
6296                                         ourPerpetual = 0; // the current mover did not always check
6297                                     if(MateTest(boards[m-1], PosFlags(m-1),
6298                                                         EP_NONE, castlingRights[m-1]) != MT_CHECK)
6299                                         hisPerpetual = 0; // the opponent did not always check
6300                                 }
6301                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6302                                                                         ourPerpetual, hisPerpetual);
6303                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6304                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6305                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6306                                     return;
6307                                 }
6308                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6309                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6310                                 // Now check for perpetual chases
6311                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6312                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6313                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6314                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6315                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6316                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6317                                         return;
6318                                     }
6319                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6320                                         break; // Abort repetition-checking loop.
6321                                 }
6322                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6323                              }
6324                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6325                              return;
6326                         }
6327                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6328                              epStatus[forwardMostMove] = EP_REP_DRAW;
6329                     }
6330                 }
6331
6332                 /* Now we test for 50-move draws. Determine ply count */
6333                 count = forwardMostMove;
6334                 /* look for last irreversble move */
6335                 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6336                     count--;
6337                 /* if we hit starting position, add initial plies */
6338                 if( count == backwardMostMove )
6339                     count -= initialRulePlies;
6340                 count = forwardMostMove - count;
6341                 if( count >= 100)
6342                          epStatus[forwardMostMove] = EP_RULE_DRAW;
6343                          /* this is used to judge if draw claims are legal */
6344                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6345                          SendToProgram("force\n", cps->other); // suppress reply
6346                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6347                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6348                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6349                          return;
6350                 }
6351
6352                 /* if draw offer is pending, treat it as a draw claim
6353                  * when draw condition present, to allow engines a way to
6354                  * claim draws before making their move to avoid a race
6355                  * condition occurring after their move
6356                  */
6357                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6358                          char *p = NULL;
6359                          if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6360                              p = "Draw claim: 50-move rule";
6361                          if(epStatus[forwardMostMove] == EP_REP_DRAW)
6362                              p = "Draw claim: 3-fold repetition";
6363                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6364                              p = "Draw claim: insufficient mating material";
6365                          if( p != NULL ) {
6366                              SendToProgram("force\n", cps->other); // suppress reply
6367                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6368                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6369                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6370                              return;
6371                          }
6372                 }
6373
6374
6375                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6376                     SendToProgram("force\n", cps->other); // suppress reply
6377                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6378                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379
6380                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6381
6382                     return;
6383                 }
6384         }
6385
6386         bookHit = NULL;
6387         if (gameMode == TwoMachinesPlay) {
6388             /* [HGM] relaying draw offers moved to after reception of move */
6389             /* and interpreting offer as claim if it brings draw condition */
6390             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6391                 SendToProgram("draw\n", cps->other);
6392             }
6393             if (cps->other->sendTime) {
6394                 SendTimeRemaining(cps->other,
6395                                   cps->other->twoMachinesColor[0] == 'w');
6396             }
6397             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6398             if (firstMove && !bookHit) {
6399                 firstMove = FALSE;
6400                 if (cps->other->useColors) {
6401                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6402                 }
6403                 SendToProgram("go\n", cps->other);
6404             }
6405             cps->other->maybeThinking = TRUE;
6406         }
6407
6408         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6409
6410         if (!pausing && appData.ringBellAfterMoves) {
6411             RingBell();
6412         }
6413
6414         /*
6415          * Reenable menu items that were disabled while
6416          * machine was thinking
6417          */
6418         if (gameMode != TwoMachinesPlay)
6419             SetUserThinkingEnables();
6420
6421         // [HGM] book: after book hit opponent has received move and is now in force mode
6422         // force the book reply into it, and then fake that it outputted this move by jumping
6423         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6424         if(bookHit) {
6425                 static char bookMove[MSG_SIZ]; // a bit generous?
6426
6427                 strcpy(bookMove, "move ");
6428                 strcat(bookMove, bookHit);
6429                 message = bookMove;
6430                 cps = cps->other;
6431                 programStats.nodes = programStats.depth = programStats.time =
6432                 programStats.score = programStats.got_only_move = 0;
6433                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6434
6435                 if(cps->lastPing != cps->lastPong) {
6436                     savedMessage = message; // args for deferred call
6437                     savedState = cps;
6438                     ScheduleDelayedEvent(DeferredBookMove, 10);
6439                     return;
6440                 }
6441                 goto FakeBookMove;
6442         }
6443
6444         return;
6445     }
6446
6447     /* Set special modes for chess engines.  Later something general
6448      *  could be added here; for now there is just one kludge feature,
6449      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6450      *  when "xboard" is given as an interactive command.
6451      */
6452     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6453         cps->useSigint = FALSE;
6454         cps->useSigterm = FALSE;
6455     }
6456     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6457       ParseFeatures(message+8, cps);
6458       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6459     }
6460
6461     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6462      * want this, I was asked to put it in, and obliged.
6463      */
6464     if (!strncmp(message, "setboard ", 9)) {
6465         Board initial_position; int i;
6466
6467         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6468
6469         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6470             DisplayError(_("Bad FEN received from engine"), 0);
6471             return ;
6472         } else {
6473            Reset(FALSE, FALSE);
6474            CopyBoard(boards[0], initial_position);
6475            initialRulePlies = FENrulePlies;
6476            epStatus[0] = FENepStatus;
6477            for( i=0; i<nrCastlingRights; i++ )
6478                 castlingRights[0][i] = FENcastlingRights[i];
6479            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6480            else gameMode = MachinePlaysBlack;
6481            DrawPosition(FALSE, boards[currentMove]);
6482         }
6483         return;
6484     }
6485
6486     /*
6487      * Look for communication commands
6488      */
6489     if (!strncmp(message, "telluser ", 9)) {
6490         DisplayNote(message + 9);
6491         return;
6492     }
6493     if (!strncmp(message, "tellusererror ", 14)) {
6494         DisplayError(message + 14, 0);
6495         return;
6496     }
6497     if (!strncmp(message, "tellopponent ", 13)) {
6498       if (appData.icsActive) {
6499         if (loggedOn) {
6500           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6501           SendToICS(buf1);
6502         }
6503       } else {
6504         DisplayNote(message + 13);
6505       }
6506       return;
6507     }
6508     if (!strncmp(message, "tellothers ", 11)) {
6509       if (appData.icsActive) {
6510         if (loggedOn) {
6511           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6512           SendToICS(buf1);
6513         }
6514       }
6515       return;
6516     }
6517     if (!strncmp(message, "tellall ", 8)) {
6518       if (appData.icsActive) {
6519         if (loggedOn) {
6520           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6521           SendToICS(buf1);
6522         }
6523       } else {
6524         DisplayNote(message + 8);
6525       }
6526       return;
6527     }
6528     if (strncmp(message, "warning", 7) == 0) {
6529         /* Undocumented feature, use tellusererror in new code */
6530         DisplayError(message, 0);
6531         return;
6532     }
6533     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6534         strcpy(realname, cps->tidy);
6535         strcat(realname, " query");
6536         AskQuestion(realname, buf2, buf1, cps->pr);
6537         return;
6538     }
6539     /* Commands from the engine directly to ICS.  We don't allow these to be
6540      *  sent until we are logged on. Crafty kibitzes have been known to
6541      *  interfere with the login process.
6542      */
6543     if (loggedOn) {
6544         if (!strncmp(message, "tellics ", 8)) {
6545             SendToICS(message + 8);
6546             SendToICS("\n");
6547             return;
6548         }
6549         if (!strncmp(message, "tellicsnoalias ", 15)) {
6550             SendToICS(ics_prefix);
6551             SendToICS(message + 15);
6552             SendToICS("\n");
6553             return;
6554         }
6555         /* The following are for backward compatibility only */
6556         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6557             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6558             SendToICS(ics_prefix);
6559             SendToICS(message);
6560             SendToICS("\n");
6561             return;
6562         }
6563     }
6564     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6565         return;
6566     }
6567     /*
6568      * If the move is illegal, cancel it and redraw the board.
6569      * Also deal with other error cases.  Matching is rather loose
6570      * here to accommodate engines written before the spec.
6571      */
6572     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6573         strncmp(message, "Error", 5) == 0) {
6574         if (StrStr(message, "name") ||
6575             StrStr(message, "rating") || StrStr(message, "?") ||
6576             StrStr(message, "result") || StrStr(message, "board") ||
6577             StrStr(message, "bk") || StrStr(message, "computer") ||
6578             StrStr(message, "variant") || StrStr(message, "hint") ||
6579             StrStr(message, "random") || StrStr(message, "depth") ||
6580             StrStr(message, "accepted")) {
6581             return;
6582         }
6583         if (StrStr(message, "protover")) {
6584           /* Program is responding to input, so it's apparently done
6585              initializing, and this error message indicates it is
6586              protocol version 1.  So we don't need to wait any longer
6587              for it to initialize and send feature commands. */
6588           FeatureDone(cps, 1);
6589           cps->protocolVersion = 1;
6590           return;
6591         }
6592         cps->maybeThinking = FALSE;
6593
6594         if (StrStr(message, "draw")) {
6595             /* Program doesn't have "draw" command */
6596             cps->sendDrawOffers = 0;
6597             return;
6598         }
6599         if (cps->sendTime != 1 &&
6600             (StrStr(message, "time") || StrStr(message, "otim"))) {
6601           /* Program apparently doesn't have "time" or "otim" command */
6602           cps->sendTime = 0;
6603           return;
6604         }
6605         if (StrStr(message, "analyze")) {
6606             cps->analysisSupport = FALSE;
6607             cps->analyzing = FALSE;
6608             Reset(FALSE, TRUE);
6609             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6610             DisplayError(buf2, 0);
6611             return;
6612         }
6613         if (StrStr(message, "(no matching move)st")) {
6614           /* Special kludge for GNU Chess 4 only */
6615           cps->stKludge = TRUE;
6616           SendTimeControl(cps, movesPerSession, timeControl,
6617                           timeIncrement, appData.searchDepth,
6618                           searchTime);
6619           return;
6620         }
6621         if (StrStr(message, "(no matching move)sd")) {
6622           /* Special kludge for GNU Chess 4 only */
6623           cps->sdKludge = TRUE;
6624           SendTimeControl(cps, movesPerSession, timeControl,
6625                           timeIncrement, appData.searchDepth,
6626                           searchTime);
6627           return;
6628         }
6629         if (!StrStr(message, "llegal")) {
6630             return;
6631         }
6632         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6633             gameMode == IcsIdle) return;
6634         if (forwardMostMove <= backwardMostMove) return;
6635         if (pausing) PauseEvent();
6636       if(appData.forceIllegal) {
6637             // [HGM] illegal: machine refused move; force position after move into it
6638           SendToProgram("force\n", cps);
6639           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6640                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6641                 // when black is to move, while there might be nothing on a2 or black
6642                 // might already have the move. So send the board as if white has the move.
6643                 // But first we must change the stm of the engine, as it refused the last move
6644                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6645                 if(WhiteOnMove(forwardMostMove)) {
6646                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6647                     SendBoard(cps, forwardMostMove); // kludgeless board
6648                 } else {
6649                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6650                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6651                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6652                 }
6653           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6654             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6655                  gameMode == TwoMachinesPlay)
6656               SendToProgram("go\n", cps);
6657             return;
6658       } else
6659         if (gameMode == PlayFromGameFile) {
6660             /* Stop reading this game file */
6661             gameMode = EditGame;
6662             ModeHighlight();
6663         }
6664         currentMove = --forwardMostMove;
6665         DisplayMove(currentMove-1); /* before DisplayMoveError */
6666         SwitchClocks();
6667         DisplayBothClocks();
6668         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6669                 parseList[currentMove], cps->which);
6670         DisplayMoveError(buf1);
6671         DrawPosition(FALSE, boards[currentMove]);
6672
6673         /* [HGM] illegal-move claim should forfeit game when Xboard */
6674         /* only passes fully legal moves                            */
6675         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6676             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6677                                 "False illegal-move claim", GE_XBOARD );
6678         }
6679         return;
6680     }
6681     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6682         /* Program has a broken "time" command that
6683            outputs a string not ending in newline.
6684            Don't use it. */
6685         cps->sendTime = 0;
6686     }
6687
6688     /*
6689      * If chess program startup fails, exit with an error message.
6690      * Attempts to recover here are futile.
6691      */
6692     if ((StrStr(message, "unknown host") != NULL)
6693         || (StrStr(message, "No remote directory") != NULL)
6694         || (StrStr(message, "not found") != NULL)
6695         || (StrStr(message, "No such file") != NULL)
6696         || (StrStr(message, "can't alloc") != NULL)
6697         || (StrStr(message, "Permission denied") != NULL)) {
6698
6699         cps->maybeThinking = FALSE;
6700         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6701                 cps->which, cps->program, cps->host, message);
6702         RemoveInputSource(cps->isr);
6703         DisplayFatalError(buf1, 0, 1);
6704         return;
6705     }
6706
6707     /*
6708      * Look for hint output
6709      */
6710     if (sscanf(message, "Hint: %s", buf1) == 1) {
6711         if (cps == &first && hintRequested) {
6712             hintRequested = FALSE;
6713             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6714                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6715                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6716                                     PosFlags(forwardMostMove), EP_UNKNOWN,
6717                                     fromY, fromX, toY, toX, promoChar, buf1);
6718                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6719                 DisplayInformation(buf2);
6720             } else {
6721                 /* Hint move could not be parsed!? */
6722               snprintf(buf2, sizeof(buf2),
6723                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6724                         buf1, cps->which);
6725                 DisplayError(buf2, 0);
6726             }
6727         } else {
6728             strcpy(lastHint, buf1);
6729         }
6730         return;
6731     }
6732
6733     /*
6734      * Ignore other messages if game is not in progress
6735      */
6736     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6737         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6738
6739     /*
6740      * look for win, lose, draw, or draw offer
6741      */
6742     if (strncmp(message, "1-0", 3) == 0) {
6743         char *p, *q, *r = "";
6744         p = strchr(message, '{');
6745         if (p) {
6746             q = strchr(p, '}');
6747             if (q) {
6748                 *q = NULLCHAR;
6749                 r = p + 1;
6750             }
6751         }
6752         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6753         return;
6754     } else if (strncmp(message, "0-1", 3) == 0) {
6755         char *p, *q, *r = "";
6756         p = strchr(message, '{');
6757         if (p) {
6758             q = strchr(p, '}');
6759             if (q) {
6760                 *q = NULLCHAR;
6761                 r = p + 1;
6762             }
6763         }
6764         /* Kludge for Arasan 4.1 bug */
6765         if (strcmp(r, "Black resigns") == 0) {
6766             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6767             return;
6768         }
6769         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6770         return;
6771     } else if (strncmp(message, "1/2", 3) == 0) {
6772         char *p, *q, *r = "";
6773         p = strchr(message, '{');
6774         if (p) {
6775             q = strchr(p, '}');
6776             if (q) {
6777                 *q = NULLCHAR;
6778                 r = p + 1;
6779             }
6780         }
6781
6782         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6783         return;
6784
6785     } else if (strncmp(message, "White resign", 12) == 0) {
6786         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6787         return;
6788     } else if (strncmp(message, "Black resign", 12) == 0) {
6789         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6790         return;
6791     } else if (strncmp(message, "White matches", 13) == 0 ||
6792                strncmp(message, "Black matches", 13) == 0   ) {
6793         /* [HGM] ignore GNUShogi noises */
6794         return;
6795     } else if (strncmp(message, "White", 5) == 0 &&
6796                message[5] != '(' &&
6797                StrStr(message, "Black") == NULL) {
6798         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6799         return;
6800     } else if (strncmp(message, "Black", 5) == 0 &&
6801                message[5] != '(') {
6802         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6803         return;
6804     } else if (strcmp(message, "resign") == 0 ||
6805                strcmp(message, "computer resigns") == 0) {
6806         switch (gameMode) {
6807           case MachinePlaysBlack:
6808           case IcsPlayingBlack:
6809             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6810             break;
6811           case MachinePlaysWhite:
6812           case IcsPlayingWhite:
6813             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6814             break;
6815           case TwoMachinesPlay:
6816             if (cps->twoMachinesColor[0] == 'w')
6817               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6818             else
6819               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6820             break;
6821           default:
6822             /* can't happen */
6823             break;
6824         }
6825         return;
6826     } else if (strncmp(message, "opponent mates", 14) == 0) {
6827         switch (gameMode) {
6828           case MachinePlaysBlack:
6829           case IcsPlayingBlack:
6830             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6831             break;
6832           case MachinePlaysWhite:
6833           case IcsPlayingWhite:
6834             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6835             break;
6836           case TwoMachinesPlay:
6837             if (cps->twoMachinesColor[0] == 'w')
6838               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839             else
6840               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6841             break;
6842           default:
6843             /* can't happen */
6844             break;
6845         }
6846         return;
6847     } else if (strncmp(message, "computer mates", 14) == 0) {
6848         switch (gameMode) {
6849           case MachinePlaysBlack:
6850           case IcsPlayingBlack:
6851             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6852             break;
6853           case MachinePlaysWhite:
6854           case IcsPlayingWhite:
6855             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6856             break;
6857           case TwoMachinesPlay:
6858             if (cps->twoMachinesColor[0] == 'w')
6859               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6860             else
6861               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6862             break;
6863           default:
6864             /* can't happen */
6865             break;
6866         }
6867         return;
6868     } else if (strncmp(message, "checkmate", 9) == 0) {
6869         if (WhiteOnMove(forwardMostMove)) {
6870             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6871         } else {
6872             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6873         }
6874         return;
6875     } else if (strstr(message, "Draw") != NULL ||
6876                strstr(message, "game is a draw") != NULL) {
6877         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6878         return;
6879     } else if (strstr(message, "offer") != NULL &&
6880                strstr(message, "draw") != NULL) {
6881 #if ZIPPY
6882         if (appData.zippyPlay && first.initDone) {
6883             /* Relay offer to ICS */
6884             SendToICS(ics_prefix);
6885             SendToICS("draw\n");
6886         }
6887 #endif
6888         cps->offeredDraw = 2; /* valid until this engine moves twice */
6889         if (gameMode == TwoMachinesPlay) {
6890             if (cps->other->offeredDraw) {
6891                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6892             /* [HGM] in two-machine mode we delay relaying draw offer      */
6893             /* until after we also have move, to see if it is really claim */
6894             }
6895         } else if (gameMode == MachinePlaysWhite ||
6896                    gameMode == MachinePlaysBlack) {
6897           if (userOfferedDraw) {
6898             DisplayInformation(_("Machine accepts your draw offer"));
6899             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6900           } else {
6901             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6902           }
6903         }
6904     }
6905
6906
6907     /*
6908      * Look for thinking output
6909      */
6910     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6911           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6912                                 ) {
6913         int plylev, mvleft, mvtot, curscore, time;
6914         char mvname[MOVE_LEN];
6915         u64 nodes; // [DM]
6916         char plyext;
6917         int ignore = FALSE;
6918         int prefixHint = FALSE;
6919         mvname[0] = NULLCHAR;
6920
6921         switch (gameMode) {
6922           case MachinePlaysBlack:
6923           case IcsPlayingBlack:
6924             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6925             break;
6926           case MachinePlaysWhite:
6927           case IcsPlayingWhite:
6928             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6929             break;
6930           case AnalyzeMode:
6931           case AnalyzeFile:
6932             break;
6933           case IcsObserving: /* [DM] icsEngineAnalyze */
6934             if (!appData.icsEngineAnalyze) ignore = TRUE;
6935             break;
6936           case TwoMachinesPlay:
6937             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6938                 ignore = TRUE;
6939             }
6940             break;
6941           default:
6942             ignore = TRUE;
6943             break;
6944         }
6945
6946         if (!ignore) {
6947             buf1[0] = NULLCHAR;
6948             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6949                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6950
6951                 if (plyext != ' ' && plyext != '\t') {
6952                     time *= 100;
6953                 }
6954
6955                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6956                 if( cps->scoreIsAbsolute &&
6957                     ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6958                 {
6959                     curscore = -curscore;
6960                 }
6961
6962
6963                 programStats.depth = plylev;
6964                 programStats.nodes = nodes;
6965                 programStats.time = time;
6966                 programStats.score = curscore;
6967                 programStats.got_only_move = 0;
6968
6969                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6970                         int ticklen;
6971
6972                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
6973                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6974                         if(WhiteOnMove(forwardMostMove))
6975                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6976                         else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6977                 }
6978
6979                 /* Buffer overflow protection */
6980                 if (buf1[0] != NULLCHAR) {
6981                     if (strlen(buf1) >= sizeof(programStats.movelist)
6982                         && appData.debugMode) {
6983                         fprintf(debugFP,
6984                                 "PV is too long; using the first %d bytes.\n",
6985                                 sizeof(programStats.movelist) - 1);
6986                     }
6987
6988                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6989                 } else {
6990                     sprintf(programStats.movelist, " no PV\n");
6991                 }
6992
6993                 if (programStats.seen_stat) {
6994                     programStats.ok_to_send = 1;
6995                 }
6996
6997                 if (strchr(programStats.movelist, '(') != NULL) {
6998                     programStats.line_is_book = 1;
6999                     programStats.nr_moves = 0;
7000                     programStats.moves_left = 0;
7001                 } else {
7002                     programStats.line_is_book = 0;
7003                 }
7004
7005                 SendProgramStatsToFrontend( cps, &programStats );
7006
7007                 /*
7008                     [AS] Protect the thinkOutput buffer from overflow... this
7009                     is only useful if buf1 hasn't overflowed first!
7010                 */
7011                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7012                         plylev,
7013                         (gameMode == TwoMachinesPlay ?
7014                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7015                         ((double) curscore) / 100.0,
7016                         prefixHint ? lastHint : "",
7017                         prefixHint ? " " : "" );
7018
7019                 if( buf1[0] != NULLCHAR ) {
7020                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7021
7022                     if( strlen(buf1) > max_len ) {
7023                         if( appData.debugMode) {
7024                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7025                         }
7026                         buf1[max_len+1] = '\0';
7027                     }
7028
7029                     strcat( thinkOutput, buf1 );
7030                 }
7031
7032                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7033                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7034                     DisplayMove(currentMove - 1);
7035                 }
7036                 return;
7037
7038             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7039                 /* crafty (9.25+) says "(only move) <move>"
7040                  * if there is only 1 legal move
7041                  */
7042                 sscanf(p, "(only move) %s", buf1);
7043                 sprintf(thinkOutput, "%s (only move)", buf1);
7044                 sprintf(programStats.movelist, "%s (only move)", buf1);
7045                 programStats.depth = 1;
7046                 programStats.nr_moves = 1;
7047                 programStats.moves_left = 1;
7048                 programStats.nodes = 1;
7049                 programStats.time = 1;
7050                 programStats.got_only_move = 1;
7051
7052                 /* Not really, but we also use this member to
7053                    mean "line isn't going to change" (Crafty
7054                    isn't searching, so stats won't change) */
7055                 programStats.line_is_book = 1;
7056
7057                 SendProgramStatsToFrontend( cps, &programStats );
7058
7059                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7060                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7061                     DisplayMove(currentMove - 1);
7062                 }
7063                 return;
7064             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7065                               &time, &nodes, &plylev, &mvleft,
7066                               &mvtot, mvname) >= 5) {
7067                 /* The stat01: line is from Crafty (9.29+) in response
7068                    to the "." command */
7069                 programStats.seen_stat = 1;
7070                 cps->maybeThinking = TRUE;
7071
7072                 if (programStats.got_only_move || !appData.periodicUpdates)
7073                   return;
7074
7075                 programStats.depth = plylev;
7076                 programStats.time = time;
7077                 programStats.nodes = nodes;
7078                 programStats.moves_left = mvleft;
7079                 programStats.nr_moves = mvtot;
7080                 strcpy(programStats.move_name, mvname);
7081                 programStats.ok_to_send = 1;
7082                 programStats.movelist[0] = '\0';
7083
7084                 SendProgramStatsToFrontend( cps, &programStats );
7085
7086                 return;
7087
7088             } else if (strncmp(message,"++",2) == 0) {
7089                 /* Crafty 9.29+ outputs this */
7090                 programStats.got_fail = 2;
7091                 return;
7092
7093             } else if (strncmp(message,"--",2) == 0) {
7094                 /* Crafty 9.29+ outputs this */
7095                 programStats.got_fail = 1;
7096                 return;
7097
7098             } else if (thinkOutput[0] != NULLCHAR &&
7099                        strncmp(message, "    ", 4) == 0) {
7100                 unsigned message_len;
7101
7102                 p = message;
7103                 while (*p && *p == ' ') p++;
7104
7105                 message_len = strlen( p );
7106
7107                 /* [AS] Avoid buffer overflow */
7108                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7109                     strcat(thinkOutput, " ");
7110                     strcat(thinkOutput, p);
7111                 }
7112
7113                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7114                     strcat(programStats.movelist, " ");
7115                     strcat(programStats.movelist, p);
7116                 }
7117
7118                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7119                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7120                     DisplayMove(currentMove - 1);
7121                 }
7122                 return;
7123             }
7124         }
7125         else {
7126             buf1[0] = NULLCHAR;
7127
7128             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7129                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7130             {
7131                 ChessProgramStats cpstats;
7132
7133                 if (plyext != ' ' && plyext != '\t') {
7134                     time *= 100;
7135                 }
7136
7137                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7138                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7139                     curscore = -curscore;
7140                 }
7141
7142                 cpstats.depth = plylev;
7143                 cpstats.nodes = nodes;
7144                 cpstats.time = time;
7145                 cpstats.score = curscore;
7146                 cpstats.got_only_move = 0;
7147                 cpstats.movelist[0] = '\0';
7148
7149                 if (buf1[0] != NULLCHAR) {
7150                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7151                 }
7152
7153                 cpstats.ok_to_send = 0;
7154                 cpstats.line_is_book = 0;
7155                 cpstats.nr_moves = 0;
7156                 cpstats.moves_left = 0;
7157
7158                 SendProgramStatsToFrontend( cps, &cpstats );
7159             }
7160         }
7161     }
7162 }
7163
7164
7165 /* Parse a game score from the character string "game", and
7166    record it as the history of the current game.  The game
7167    score is NOT assumed to start from the standard position.
7168    The display is not updated in any way.
7169    */
7170 void
7171 ParseGameHistory(game)
7172      char *game;
7173 {
7174     ChessMove moveType;
7175     int fromX, fromY, toX, toY, boardIndex;
7176     char promoChar;
7177     char *p, *q;
7178     char buf[MSG_SIZ];
7179
7180     if (appData.debugMode)
7181       fprintf(debugFP, "Parsing game history: %s\n", game);
7182
7183     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7184     gameInfo.site = StrSave(appData.icsHost);
7185     gameInfo.date = PGNDate();
7186     gameInfo.round = StrSave("-");
7187
7188     /* Parse out names of players */
7189     while (*game == ' ') game++;
7190     p = buf;
7191     while (*game != ' ') *p++ = *game++;
7192     *p = NULLCHAR;
7193     gameInfo.white = StrSave(buf);
7194     while (*game == ' ') game++;
7195     p = buf;
7196     while (*game != ' ' && *game != '\n') *p++ = *game++;
7197     *p = NULLCHAR;
7198     gameInfo.black = StrSave(buf);
7199
7200     /* Parse moves */
7201     boardIndex = blackPlaysFirst ? 1 : 0;
7202     yynewstr(game);
7203     for (;;) {
7204         yyboardindex = boardIndex;
7205         moveType = (ChessMove) yylex();
7206         switch (moveType) {
7207           case IllegalMove:             /* maybe suicide chess, etc. */
7208   if (appData.debugMode) {
7209     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7210     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7211     setbuf(debugFP, NULL);
7212   }
7213           case WhitePromotionChancellor:
7214           case BlackPromotionChancellor:
7215           case WhitePromotionArchbishop:
7216           case BlackPromotionArchbishop:
7217           case WhitePromotionQueen:
7218           case BlackPromotionQueen:
7219           case WhitePromotionRook:
7220           case BlackPromotionRook:
7221           case WhitePromotionBishop:
7222           case BlackPromotionBishop:
7223           case WhitePromotionKnight:
7224           case BlackPromotionKnight:
7225           case WhitePromotionKing:
7226           case BlackPromotionKing:
7227           case NormalMove:
7228           case WhiteCapturesEnPassant:
7229           case BlackCapturesEnPassant:
7230           case WhiteKingSideCastle:
7231           case WhiteQueenSideCastle:
7232           case BlackKingSideCastle:
7233           case BlackQueenSideCastle:
7234           case WhiteKingSideCastleWild:
7235           case WhiteQueenSideCastleWild:
7236           case BlackKingSideCastleWild:
7237           case BlackQueenSideCastleWild:
7238           /* PUSH Fabien */
7239           case WhiteHSideCastleFR:
7240           case WhiteASideCastleFR:
7241           case BlackHSideCastleFR:
7242           case BlackASideCastleFR:
7243           /* POP Fabien */
7244             fromX = currentMoveString[0] - AAA;
7245             fromY = currentMoveString[1] - ONE;
7246             toX = currentMoveString[2] - AAA;
7247             toY = currentMoveString[3] - ONE;
7248             promoChar = currentMoveString[4];
7249             break;
7250           case WhiteDrop:
7251           case BlackDrop:
7252             fromX = moveType == WhiteDrop ?
7253               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7254             (int) CharToPiece(ToLower(currentMoveString[0]));
7255             fromY = DROP_RANK;
7256             toX = currentMoveString[2] - AAA;
7257             toY = currentMoveString[3] - ONE;
7258             promoChar = NULLCHAR;
7259             break;
7260           case AmbiguousMove:
7261             /* bug? */
7262             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7263   if (appData.debugMode) {
7264     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7265     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7266     setbuf(debugFP, NULL);
7267   }
7268             DisplayError(buf, 0);
7269             return;
7270           case ImpossibleMove:
7271             /* bug? */
7272             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7273   if (appData.debugMode) {
7274     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7275     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7276     setbuf(debugFP, NULL);
7277   }
7278             DisplayError(buf, 0);
7279             return;
7280           case (ChessMove) 0:   /* end of file */
7281             if (boardIndex < backwardMostMove) {
7282                 /* Oops, gap.  How did that happen? */
7283                 DisplayError(_("Gap in move list"), 0);
7284                 return;
7285             }
7286             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7287             if (boardIndex > forwardMostMove) {
7288                 forwardMostMove = boardIndex;
7289             }
7290             return;
7291           case ElapsedTime:
7292             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7293                 strcat(parseList[boardIndex-1], " ");
7294                 strcat(parseList[boardIndex-1], yy_text);
7295             }
7296             continue;
7297           case Comment:
7298           case PGNTag:
7299           case NAG:
7300           default:
7301             /* ignore */
7302             continue;
7303           case WhiteWins:
7304           case BlackWins:
7305           case GameIsDrawn:
7306           case GameUnfinished:
7307             if (gameMode == IcsExamining) {
7308                 if (boardIndex < backwardMostMove) {
7309                     /* Oops, gap.  How did that happen? */
7310                     return;
7311                 }
7312                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7313                 return;
7314             }
7315             gameInfo.result = moveType;
7316             p = strchr(yy_text, '{');
7317             if (p == NULL) p = strchr(yy_text, '(');
7318             if (p == NULL) {
7319                 p = yy_text;
7320                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7321             } else {
7322                 q = strchr(p, *p == '{' ? '}' : ')');
7323                 if (q != NULL) *q = NULLCHAR;
7324                 p++;
7325             }
7326             gameInfo.resultDetails = StrSave(p);
7327             continue;
7328         }
7329         if (boardIndex >= forwardMostMove &&
7330             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7331             backwardMostMove = blackPlaysFirst ? 1 : 0;
7332             return;
7333         }
7334         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7335                                  EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7336                                  parseList[boardIndex]);
7337         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7338         {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7339         /* currentMoveString is set as a side-effect of yylex */
7340         strcpy(moveList[boardIndex], currentMoveString);
7341         strcat(moveList[boardIndex], "\n");
7342         boardIndex++;
7343         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7344                                         castlingRights[boardIndex], &epStatus[boardIndex]);
7345         switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7346                                  EP_UNKNOWN, castlingRights[boardIndex]) ) {
7347           case MT_NONE:
7348           case MT_STALEMATE:
7349           default:
7350             break;
7351           case MT_CHECK:
7352             if(gameInfo.variant != VariantShogi)
7353                 strcat(parseList[boardIndex - 1], "+");
7354             break;
7355           case MT_CHECKMATE:
7356           case MT_STAINMATE:
7357             strcat(parseList[boardIndex - 1], "#");
7358             break;
7359         }
7360     }
7361 }
7362
7363
7364 /* Apply a move to the given board  */
7365 void
7366 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7367      int fromX, fromY, toX, toY;
7368      int promoChar;
7369      Board board;
7370      char *castling;
7371      char *ep;
7372 {
7373   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7374
7375     /* [HGM] compute & store e.p. status and castling rights for new position */
7376     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7377     { int i;
7378
7379       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7380       oldEP = *ep;
7381       *ep = EP_NONE;
7382
7383       if( board[toY][toX] != EmptySquare )
7384            *ep = EP_CAPTURE;
7385
7386       if( board[fromY][fromX] == WhitePawn ) {
7387            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7388                *ep = EP_PAWN_MOVE;
7389            if( toY-fromY==2) {
7390                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7391                         gameInfo.variant != VariantBerolina || toX < fromX)
7392                       *ep = toX | berolina;
7393                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7394                         gameInfo.variant != VariantBerolina || toX > fromX)
7395                       *ep = toX;
7396            }
7397       } else
7398       if( board[fromY][fromX] == BlackPawn ) {
7399            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7400                *ep = EP_PAWN_MOVE;
7401            if( toY-fromY== -2) {
7402                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7403                         gameInfo.variant != VariantBerolina || toX < fromX)
7404                       *ep = toX | berolina;
7405                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7406                         gameInfo.variant != VariantBerolina || toX > fromX)
7407                       *ep = toX;
7408            }
7409        }
7410
7411        for(i=0; i<nrCastlingRights; i++) {
7412            if(castling[i] == fromX && castlingRank[i] == fromY ||
7413               castling[i] == toX   && castlingRank[i] == toY
7414              ) castling[i] = -1; // revoke for moved or captured piece
7415        }
7416
7417     }
7418
7419   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7420   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7421        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7422
7423   if (fromX == toX && fromY == toY) return;
7424
7425   if (fromY == DROP_RANK) {
7426         /* must be first */
7427         piece = board[toY][toX] = (ChessSquare) fromX;
7428   } else {
7429      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7430      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7431      if(gameInfo.variant == VariantKnightmate)
7432          king += (int) WhiteUnicorn - (int) WhiteKing;
7433
7434     /* Code added by Tord: */
7435     /* FRC castling assumed when king captures friendly rook. */
7436     if (board[fromY][fromX] == WhiteKing &&
7437              board[toY][toX] == WhiteRook) {
7438       board[fromY][fromX] = EmptySquare;
7439       board[toY][toX] = EmptySquare;
7440       if(toX > fromX) {
7441         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7442       } else {
7443         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7444       }
7445     } else if (board[fromY][fromX] == BlackKing &&
7446                board[toY][toX] == BlackRook) {
7447       board[fromY][fromX] = EmptySquare;
7448       board[toY][toX] = EmptySquare;
7449       if(toX > fromX) {
7450         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7451       } else {
7452         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7453       }
7454     /* End of code added by Tord */
7455
7456     } else if (board[fromY][fromX] == king
7457         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7458         && toY == fromY && toX > fromX+1) {
7459         board[fromY][fromX] = EmptySquare;
7460         board[toY][toX] = king;
7461         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7462         board[fromY][BOARD_RGHT-1] = EmptySquare;
7463     } else if (board[fromY][fromX] == king
7464         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7465                && toY == fromY && toX < fromX-1) {
7466         board[fromY][fromX] = EmptySquare;
7467         board[toY][toX] = king;
7468         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7469         board[fromY][BOARD_LEFT] = EmptySquare;
7470     } else if (board[fromY][fromX] == WhitePawn
7471                && toY == BOARD_HEIGHT-1
7472                && gameInfo.variant != VariantXiangqi
7473                ) {
7474         /* white pawn promotion */
7475         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7476         if (board[toY][toX] == EmptySquare) {
7477             board[toY][toX] = WhiteQueen;
7478         }
7479         if(gameInfo.variant==VariantBughouse ||
7480            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7481             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7482         board[fromY][fromX] = EmptySquare;
7483     } else if ((fromY == BOARD_HEIGHT-4)
7484                && (toX != fromX)
7485                && gameInfo.variant != VariantXiangqi
7486                && gameInfo.variant != VariantBerolina
7487                && (board[fromY][fromX] == WhitePawn)
7488                && (board[toY][toX] == EmptySquare)) {
7489         board[fromY][fromX] = EmptySquare;
7490         board[toY][toX] = WhitePawn;
7491         captured = board[toY - 1][toX];
7492         board[toY - 1][toX] = EmptySquare;
7493     } else if ((fromY == BOARD_HEIGHT-4)
7494                && (toX == fromX)
7495                && gameInfo.variant == VariantBerolina
7496                && (board[fromY][fromX] == WhitePawn)
7497                && (board[toY][toX] == EmptySquare)) {
7498         board[fromY][fromX] = EmptySquare;
7499         board[toY][toX] = WhitePawn;
7500         if(oldEP & EP_BEROLIN_A) {
7501                 captured = board[fromY][fromX-1];
7502                 board[fromY][fromX-1] = EmptySquare;
7503         }else{  captured = board[fromY][fromX+1];
7504                 board[fromY][fromX+1] = EmptySquare;
7505         }
7506     } else if (board[fromY][fromX] == king
7507         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7508                && toY == fromY && toX > fromX+1) {
7509         board[fromY][fromX] = EmptySquare;
7510         board[toY][toX] = king;
7511         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7512         board[fromY][BOARD_RGHT-1] = EmptySquare;
7513     } else if (board[fromY][fromX] == king
7514         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7515                && toY == fromY && toX < fromX-1) {
7516         board[fromY][fromX] = EmptySquare;
7517         board[toY][toX] = king;
7518         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7519         board[fromY][BOARD_LEFT] = EmptySquare;
7520     } else if (fromY == 7 && fromX == 3
7521                && board[fromY][fromX] == BlackKing
7522                && toY == 7 && toX == 5) {
7523         board[fromY][fromX] = EmptySquare;
7524         board[toY][toX] = BlackKing;
7525         board[fromY][7] = EmptySquare;
7526         board[toY][4] = BlackRook;
7527     } else if (fromY == 7 && fromX == 3
7528                && board[fromY][fromX] == BlackKing
7529                && toY == 7 && toX == 1) {
7530         board[fromY][fromX] = EmptySquare;
7531         board[toY][toX] = BlackKing;
7532         board[fromY][0] = EmptySquare;
7533         board[toY][2] = BlackRook;
7534     } else if (board[fromY][fromX] == BlackPawn
7535                && toY == 0
7536                && gameInfo.variant != VariantXiangqi
7537                ) {
7538         /* black pawn promotion */
7539         board[0][toX] = CharToPiece(ToLower(promoChar));
7540         if (board[0][toX] == EmptySquare) {
7541             board[0][toX] = BlackQueen;
7542         }
7543         if(gameInfo.variant==VariantBughouse ||
7544            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7545             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7546         board[fromY][fromX] = EmptySquare;
7547     } else if ((fromY == 3)
7548                && (toX != fromX)
7549                && gameInfo.variant != VariantXiangqi
7550                && gameInfo.variant != VariantBerolina
7551                && (board[fromY][fromX] == BlackPawn)
7552                && (board[toY][toX] == EmptySquare)) {
7553         board[fromY][fromX] = EmptySquare;
7554         board[toY][toX] = BlackPawn;
7555         captured = board[toY + 1][toX];
7556         board[toY + 1][toX] = EmptySquare;
7557     } else if ((fromY == 3)
7558                && (toX == fromX)
7559                && gameInfo.variant == VariantBerolina
7560                && (board[fromY][fromX] == BlackPawn)
7561                && (board[toY][toX] == EmptySquare)) {
7562         board[fromY][fromX] = EmptySquare;
7563         board[toY][toX] = BlackPawn;
7564         if(oldEP & EP_BEROLIN_A) {
7565                 captured = board[fromY][fromX-1];
7566                 board[fromY][fromX-1] = EmptySquare;
7567         }else{  captured = board[fromY][fromX+1];
7568                 board[fromY][fromX+1] = EmptySquare;
7569         }
7570     } else {
7571         board[toY][toX] = board[fromY][fromX];
7572         board[fromY][fromX] = EmptySquare;
7573     }
7574
7575     /* [HGM] now we promote for Shogi, if needed */
7576     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7577         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7578   }
7579
7580     if (gameInfo.holdingsWidth != 0) {
7581
7582       /* !!A lot more code needs to be written to support holdings  */
7583       /* [HGM] OK, so I have written it. Holdings are stored in the */
7584       /* penultimate board files, so they are automaticlly stored   */
7585       /* in the game history.                                       */
7586       if (fromY == DROP_RANK) {
7587         /* Delete from holdings, by decreasing count */
7588         /* and erasing image if necessary            */
7589         p = (int) fromX;
7590         if(p < (int) BlackPawn) { /* white drop */
7591              p -= (int)WhitePawn;
7592                  p = PieceToNumber((ChessSquare)p);
7593              if(p >= gameInfo.holdingsSize) p = 0;
7594              if(--board[p][BOARD_WIDTH-2] <= 0)
7595                   board[p][BOARD_WIDTH-1] = EmptySquare;
7596              if((int)board[p][BOARD_WIDTH-2] < 0)
7597                         board[p][BOARD_WIDTH-2] = 0;
7598         } else {                  /* black drop */
7599              p -= (int)BlackPawn;
7600                  p = PieceToNumber((ChessSquare)p);
7601              if(p >= gameInfo.holdingsSize) p = 0;
7602              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7603                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7604              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7605                         board[BOARD_HEIGHT-1-p][1] = 0;
7606         }
7607       }
7608       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7609           && gameInfo.variant != VariantBughouse        ) {
7610         /* [HGM] holdings: Add to holdings, if holdings exist */
7611         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7612                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7613                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7614         }
7615         p = (int) captured;
7616         if (p >= (int) BlackPawn) {
7617           p -= (int)BlackPawn;
7618           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7619                   /* in Shogi restore piece to its original  first */
7620                   captured = (ChessSquare) (DEMOTED captured);
7621                   p = DEMOTED p;
7622           }
7623           p = PieceToNumber((ChessSquare)p);
7624           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7625           board[p][BOARD_WIDTH-2]++;
7626           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7627         } else {
7628           p -= (int)WhitePawn;
7629           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7630                   captured = (ChessSquare) (DEMOTED captured);
7631                   p = DEMOTED p;
7632           }
7633           p = PieceToNumber((ChessSquare)p);
7634           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7635           board[BOARD_HEIGHT-1-p][1]++;
7636           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7637         }
7638       }
7639     } else if (gameInfo.variant == VariantAtomic) {
7640       if (captured != EmptySquare) {
7641         int y, x;
7642         for (y = toY-1; y <= toY+1; y++) {
7643           for (x = toX-1; x <= toX+1; x++) {
7644             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7645                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7646               board[y][x] = EmptySquare;
7647             }
7648           }
7649         }
7650         board[toY][toX] = EmptySquare;
7651       }
7652     }
7653     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7654         /* [HGM] Shogi promotions */
7655         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7656     }
7657
7658     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7659                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7660         // [HGM] superchess: take promotion piece out of holdings
7661         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7662         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7663             if(!--board[k][BOARD_WIDTH-2])
7664                 board[k][BOARD_WIDTH-1] = EmptySquare;
7665         } else {
7666             if(!--board[BOARD_HEIGHT-1-k][1])
7667                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7668         }
7669     }
7670
7671 }
7672
7673 /* Updates forwardMostMove */
7674 void
7675 MakeMove(fromX, fromY, toX, toY, promoChar)
7676      int fromX, fromY, toX, toY;
7677      int promoChar;
7678 {
7679 //    forwardMostMove++; // [HGM] bare: moved downstream
7680
7681     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7682         int timeLeft; static int lastLoadFlag=0; int king, piece;
7683         piece = boards[forwardMostMove][fromY][fromX];
7684         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7685         if(gameInfo.variant == VariantKnightmate)
7686             king += (int) WhiteUnicorn - (int) WhiteKing;
7687         if(forwardMostMove == 0) {
7688             if(blackPlaysFirst)
7689                 fprintf(serverMoves, "%s;", second.tidy);
7690             fprintf(serverMoves, "%s;", first.tidy);
7691             if(!blackPlaysFirst)
7692                 fprintf(serverMoves, "%s;", second.tidy);
7693         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7694         lastLoadFlag = loadFlag;
7695         // print base move
7696         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7697         // print castling suffix
7698         if( toY == fromY && piece == king ) {
7699             if(toX-fromX > 1)
7700                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7701             if(fromX-toX >1)
7702                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7703         }
7704         // e.p. suffix
7705         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7706              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7707              boards[forwardMostMove][toY][toX] == EmptySquare
7708              && fromX != toX )
7709                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7710         // promotion suffix
7711         if(promoChar != NULLCHAR)
7712                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7713         if(!loadFlag) {
7714             fprintf(serverMoves, "/%d/%d",
7715                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7716             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7717             else                      timeLeft = blackTimeRemaining/1000;
7718             fprintf(serverMoves, "/%d", timeLeft);
7719         }
7720         fflush(serverMoves);
7721     }
7722
7723     if (forwardMostMove+1 >= MAX_MOVES) {
7724       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7725                         0, 1);
7726       return;
7727     }
7728     if (commentList[forwardMostMove+1] != NULL) {
7729         free(commentList[forwardMostMove+1]);
7730         commentList[forwardMostMove+1] = NULL;
7731     }
7732     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7733     {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7734     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7735                                 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7736     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7737     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7738     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7739     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7740     gameInfo.result = GameUnfinished;
7741     if (gameInfo.resultDetails != NULL) {
7742         free(gameInfo.resultDetails);
7743         gameInfo.resultDetails = NULL;
7744     }
7745     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7746                               moveList[forwardMostMove - 1]);
7747     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7748                              PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7749                              fromY, fromX, toY, toX, promoChar,
7750                              parseList[forwardMostMove - 1]);
7751     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7752                        epStatus[forwardMostMove], /* [HGM] use true e.p. */
7753                             castlingRights[forwardMostMove]) ) {
7754       case MT_NONE:
7755       case MT_STALEMATE:
7756       default:
7757         break;
7758       case MT_CHECK:
7759         if(gameInfo.variant != VariantShogi)
7760             strcat(parseList[forwardMostMove - 1], "+");
7761         break;
7762       case MT_CHECKMATE:
7763       case MT_STAINMATE:
7764         strcat(parseList[forwardMostMove - 1], "#");
7765         break;
7766     }
7767     if (appData.debugMode) {
7768         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7769     }
7770
7771 }
7772
7773 /* Updates currentMove if not pausing */
7774 void
7775 ShowMove(fromX, fromY, toX, toY)
7776 {
7777     int instant = (gameMode == PlayFromGameFile) ?
7778         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7779
7780     if(appData.noGUI) return;
7781
7782     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7783       {
7784         if (!instant)
7785           {
7786             if (forwardMostMove == currentMove + 1)
7787               {
7788 //TODO
7789 //              AnimateMove(boards[forwardMostMove - 1],
7790 //                          fromX, fromY, toX, toY);
7791               }
7792             if (appData.highlightLastMove)
7793               {
7794                 SetHighlights(fromX, fromY, toX, toY);
7795               }
7796           }
7797         currentMove = forwardMostMove;
7798     }
7799
7800     if (instant) return;
7801
7802     DisplayMove(currentMove - 1);
7803     DrawPosition(FALSE, boards[currentMove]);
7804     DisplayBothClocks();
7805     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7806
7807     return;
7808 }
7809
7810 void SendEgtPath(ChessProgramState *cps)
7811 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7812         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7813
7814         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7815
7816         while(*p) {
7817             char c, *q = name+1, *r, *s;
7818
7819             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7820             while(*p && *p != ',') *q++ = *p++;
7821             *q++ = ':'; *q = 0;
7822             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7823                 strcmp(name, ",nalimov:") == 0 ) {
7824                 // take nalimov path from the menu-changeable option first, if it is defined
7825                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7826                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7827             } else
7828             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7829                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7830                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7831                 s = r = StrStr(s, ":") + 1; // beginning of path info
7832                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7833                 c = *r; *r = 0;             // temporarily null-terminate path info
7834                     *--q = 0;               // strip of trailig ':' from name
7835                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7836                 *r = c;
7837                 SendToProgram(buf,cps);     // send egtbpath command for this format
7838             }
7839             if(*p == ',') p++; // read away comma to position for next format name
7840         }
7841 }
7842
7843 void
7844 InitChessProgram(cps, setup)
7845      ChessProgramState *cps;
7846      int setup; /* [HGM] needed to setup FRC opening position */
7847 {
7848     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7849     if (appData.noChessProgram) return;
7850     hintRequested = FALSE;
7851     bookRequested = FALSE;
7852
7853     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7854     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7855     if(cps->memSize) { /* [HGM] memory */
7856         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7857         SendToProgram(buf, cps);
7858     }
7859     SendEgtPath(cps); /* [HGM] EGT */
7860     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7861         sprintf(buf, "cores %d\n", appData.smpCores);
7862         SendToProgram(buf, cps);
7863     }
7864
7865     SendToProgram(cps->initString, cps);
7866     if (gameInfo.variant != VariantNormal &&
7867         gameInfo.variant != VariantLoadable
7868         /* [HGM] also send variant if board size non-standard */
7869         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7870                                             ) {
7871       char *v = VariantName(gameInfo.variant);
7872       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7873         /* [HGM] in protocol 1 we have to assume all variants valid */
7874         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7875         DisplayFatalError(buf, 0, 1);
7876         return;
7877       }
7878
7879       /* [HGM] make prefix for non-standard board size. Awkward testing... */
7880       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7881       if( gameInfo.variant == VariantXiangqi )
7882            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7883       if( gameInfo.variant == VariantShogi )
7884            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7885       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7886            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7887       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7888                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
7889            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7890       if( gameInfo.variant == VariantCourier )
7891            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7892       if( gameInfo.variant == VariantSuper )
7893            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7894       if( gameInfo.variant == VariantGreat )
7895            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7896
7897       if(overruled) {
7898            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7899                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7900            /* [HGM] varsize: try first if this defiant size variant is specifically known */
7901            if(StrStr(cps->variants, b) == NULL) {
7902                // specific sized variant not known, check if general sizing allowed
7903                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7904                    if(StrStr(cps->variants, "boardsize") == NULL) {
7905                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
7906                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7907                        DisplayFatalError(buf, 0, 1);
7908                        return;
7909                    }
7910                    /* [HGM] here we really should compare with the maximum supported board size */
7911                }
7912            }
7913       } else sprintf(b, "%s", VariantName(gameInfo.variant));
7914       sprintf(buf, "variant %s\n", b);
7915       SendToProgram(buf, cps);
7916     }
7917     currentlyInitializedVariant = gameInfo.variant;
7918
7919     /* [HGM] send opening position in FRC to first engine */
7920     if(setup) {
7921           SendToProgram("force\n", cps);
7922           SendBoard(cps, 0);
7923           /* engine is now in force mode! Set flag to wake it up after first move. */
7924           setboardSpoiledMachineBlack = 1;
7925     }
7926
7927     if (cps->sendICS) {
7928       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7929       SendToProgram(buf, cps);
7930     }
7931     cps->maybeThinking = FALSE;
7932     cps->offeredDraw = 0;
7933     if (!appData.icsActive) {
7934         SendTimeControl(cps, movesPerSession, timeControl,
7935                         timeIncrement, appData.searchDepth,
7936                         searchTime);
7937     }
7938     if (appData.showThinking
7939         // [HGM] thinking: four options require thinking output to be sent
7940         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7941                                 ) {
7942         SendToProgram("post\n", cps);
7943     }
7944     SendToProgram("hard\n", cps);
7945     if (!appData.ponderNextMove) {
7946         /* Warning: "easy" is a toggle in GNU Chess, so don't send
7947            it without being sure what state we are in first.  "hard"
7948            is not a toggle, so that one is OK.
7949          */
7950         SendToProgram("easy\n", cps);
7951     }
7952     if (cps->usePing) {
7953       sprintf(buf, "ping %d\n", ++cps->lastPing);
7954       SendToProgram(buf, cps);
7955     }
7956     cps->initDone = TRUE;
7957 }
7958
7959
7960 void
7961 StartChessProgram(cps)
7962      ChessProgramState *cps;
7963 {
7964     char buf[MSG_SIZ];
7965     int err;
7966
7967     if (appData.noChessProgram) return;
7968     cps->initDone = FALSE;
7969
7970     if (strcmp(cps->host, "localhost") == 0) {
7971         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7972     } else if (*appData.remoteShell == NULLCHAR) {
7973         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7974     } else {
7975         if (*appData.remoteUser == NULLCHAR) {
7976           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7977                     cps->program);
7978         } else {
7979           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7980                     cps->host, appData.remoteUser, cps->program);
7981         }
7982         err = StartChildProcess(buf, "", &cps->pr);
7983     }
7984
7985     if (err != 0) {
7986         sprintf(buf, _("Startup failure on '%s'"), cps->program);
7987         DisplayFatalError(buf, err, 1);
7988         cps->pr = NoProc;
7989         cps->isr = NULL;
7990         return;
7991     }
7992
7993     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7994     if (cps->protocolVersion > 1) {
7995       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7996       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7997       cps->comboCnt = 0;  //                and values of combo boxes
7998       SendToProgram(buf, cps);
7999     } else {
8000       SendToProgram("xboard\n", cps);
8001     }
8002 }
8003
8004
8005 void
8006 TwoMachinesEventIfReady P((void))
8007 {
8008   if (first.lastPing != first.lastPong) {
8009     DisplayMessage("", _("Waiting for first chess program"));
8010     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8011     return;
8012   }
8013   if (second.lastPing != second.lastPong) {
8014     DisplayMessage("", _("Waiting for second chess program"));
8015     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8016     return;
8017   }
8018   ThawUI();
8019   TwoMachinesEvent();
8020 }
8021
8022 void
8023 NextMatchGame P((void))
8024 {
8025     int index; /* [HGM] autoinc: step lod index during match */
8026     Reset(FALSE, TRUE);
8027     if (*appData.loadGameFile != NULLCHAR) {
8028         index = appData.loadGameIndex;
8029         if(index < 0) { // [HGM] autoinc
8030             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8031             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8032         }
8033         LoadGameFromFile(appData.loadGameFile,
8034                          index,
8035                          appData.loadGameFile, FALSE);
8036     } else if (*appData.loadPositionFile != NULLCHAR) {
8037         index = appData.loadPositionIndex;
8038         if(index < 0) { // [HGM] autoinc
8039             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8040             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8041         }
8042         LoadPositionFromFile(appData.loadPositionFile,
8043                              index,
8044                              appData.loadPositionFile);
8045     }
8046     TwoMachinesEventIfReady();
8047 }
8048
8049 void UserAdjudicationEvent( int result )
8050 {
8051     ChessMove gameResult = GameIsDrawn;
8052
8053     if( result > 0 ) {
8054         gameResult = WhiteWins;
8055     }
8056     else if( result < 0 ) {
8057         gameResult = BlackWins;
8058     }
8059
8060     if( gameMode == TwoMachinesPlay ) {
8061         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8062     }
8063 }
8064
8065
8066 // [HGM] save: calculate checksum of game to make games easily identifiable
8067 int StringCheckSum(char *s)
8068 {
8069         int i = 0;
8070         if(s==NULL) return 0;
8071         while(*s) i = i*259 + *s++;
8072         return i;
8073 }
8074
8075 int GameCheckSum()
8076 {
8077         int i, sum=0;
8078         for(i=backwardMostMove; i<forwardMostMove; i++) {
8079                 sum += pvInfoList[i].depth;
8080                 sum += StringCheckSum(parseList[i]);
8081                 sum += StringCheckSum(commentList[i]);
8082                 sum *= 261;
8083         }
8084         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8085         return sum + StringCheckSum(commentList[i]);
8086 } // end of save patch
8087
8088 void
8089 GameEnds(result, resultDetails, whosays)
8090      ChessMove result;
8091      char *resultDetails;
8092      int whosays;
8093 {
8094     GameMode nextGameMode;
8095     int isIcsGame;
8096     char buf[MSG_SIZ];
8097
8098     if(endingGame) return; /* [HGM] crash: forbid recursion */
8099     endingGame = 1;
8100
8101     if (appData.debugMode) {
8102       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8103               result, resultDetails ? resultDetails : "(null)", whosays);
8104     }
8105
8106     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8107         /* If we are playing on ICS, the server decides when the
8108            game is over, but the engine can offer to draw, claim
8109            a draw, or resign.
8110          */
8111 #if ZIPPY
8112         if (appData.zippyPlay && first.initDone) {
8113             if (result == GameIsDrawn) {
8114                 /* In case draw still needs to be claimed */
8115                 SendToICS(ics_prefix);
8116                 SendToICS("draw\n");
8117             } else if (StrCaseStr(resultDetails, "resign")) {
8118                 SendToICS(ics_prefix);
8119                 SendToICS("resign\n");
8120             }
8121         }
8122 #endif
8123         endingGame = 0; /* [HGM] crash */
8124         return;
8125     }
8126
8127     /* If we're loading the game from a file, stop */
8128     if (whosays == GE_FILE) {
8129       (void) StopLoadGameTimer();
8130       gameFileFP = NULL;
8131     }
8132
8133     /* Cancel draw offers */
8134     first.offeredDraw = second.offeredDraw = 0;
8135
8136     /* If this is an ICS game, only ICS can really say it's done;
8137        if not, anyone can. */
8138     isIcsGame = (gameMode == IcsPlayingWhite ||
8139                  gameMode == IcsPlayingBlack ||
8140                  gameMode == IcsObserving    ||
8141                  gameMode == IcsExamining);
8142
8143     if (!isIcsGame || whosays == GE_ICS) {
8144         /* OK -- not an ICS game, or ICS said it was done */
8145         StopClocks();
8146         if (!isIcsGame && !appData.noChessProgram)
8147           SetUserThinkingEnables();
8148
8149         /* [HGM] if a machine claims the game end we verify this claim */
8150         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8151             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8152                 char claimer;
8153                 ChessMove trueResult = (ChessMove) -1;
8154
8155                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8156                                             first.twoMachinesColor[0] :
8157                                             second.twoMachinesColor[0] ;
8158
8159                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8160                 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8161                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8162                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8163                 } else
8164                 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8165                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8166                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8167                 } else
8168                 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8169                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8170                 }
8171
8172                 // now verify win claims, but not in drop games, as we don't understand those yet
8173                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8174                                                  || gameInfo.variant == VariantGreat) &&
8175                     (result == WhiteWins && claimer == 'w' ||
8176                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8177                       if (appData.debugMode) {
8178                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8179                                 result, epStatus[forwardMostMove], forwardMostMove);
8180                       }
8181                       if(result != trueResult) {
8182                               sprintf(buf, "False win claim: '%s'", resultDetails);
8183                               result = claimer == 'w' ? BlackWins : WhiteWins;
8184                               resultDetails = buf;
8185                       }
8186                 } else
8187                 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8188                     && (forwardMostMove <= backwardMostMove ||
8189                         epStatus[forwardMostMove-1] > EP_DRAWS ||
8190                         (claimer=='b')==(forwardMostMove&1))
8191                                                                                   ) {
8192                       /* [HGM] verify: draws that were not flagged are false claims */
8193                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8194                       result = claimer == 'w' ? BlackWins : WhiteWins;
8195                       resultDetails = buf;
8196                 }
8197                 /* (Claiming a loss is accepted no questions asked!) */
8198             }
8199
8200             /* [HGM] bare: don't allow bare King to win */
8201             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8202                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8203                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8204                && result != GameIsDrawn)
8205             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8206                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8207                         int p = (int)boards[forwardMostMove][i][j] - color;
8208                         if(p >= 0 && p <= (int)WhiteKing) k++;
8209                 }
8210                 if (appData.debugMode) {
8211                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8212                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8213                 }
8214                 if(k <= 1) {
8215                         result = GameIsDrawn;
8216                         sprintf(buf, "%s but bare king", resultDetails);
8217                         resultDetails = buf;
8218                 }
8219             }
8220         }
8221
8222         if(serverMoves != NULL && !loadFlag) { char c = '=';
8223             if(result==WhiteWins) c = '+';
8224             if(result==BlackWins) c = '-';
8225             if(resultDetails != NULL)
8226                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8227         }
8228         if (resultDetails != NULL) {
8229             gameInfo.result = result;
8230             gameInfo.resultDetails = StrSave(resultDetails);
8231
8232             /* display last move only if game was not loaded from file */
8233             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8234                 DisplayMove(currentMove - 1);
8235
8236             if (forwardMostMove != 0) {
8237                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8238                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8239                                                                 ) {
8240                     if (*appData.saveGameFile != NULLCHAR) {
8241                         SaveGameToFile(appData.saveGameFile, TRUE);
8242                     } else if (appData.autoSaveGames) {
8243                         AutoSaveGame();
8244                     }
8245                     if (*appData.savePositionFile != NULLCHAR) {
8246                         SavePositionToFile(appData.savePositionFile);
8247                     }
8248                 }
8249             }
8250
8251             /* Tell program how game ended in case it is learning */
8252             /* [HGM] Moved this to after saving the PGN, just in case */
8253             /* engine died and we got here through time loss. In that */
8254             /* case we will get a fatal error writing the pipe, which */
8255             /* would otherwise lose us the PGN.                       */
8256             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8257             /* output during GameEnds should never be fatal anymore   */
8258             if (gameMode == MachinePlaysWhite ||
8259                 gameMode == MachinePlaysBlack ||
8260                 gameMode == TwoMachinesPlay ||
8261                 gameMode == IcsPlayingWhite ||
8262                 gameMode == IcsPlayingBlack ||
8263                 gameMode == BeginningOfGame) {
8264                 char buf[MSG_SIZ];
8265                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8266                         resultDetails);
8267                 if (first.pr != NoProc) {
8268                     SendToProgram(buf, &first);
8269                 }
8270                 if (second.pr != NoProc &&
8271                     gameMode == TwoMachinesPlay) {
8272                     SendToProgram(buf, &second);
8273                 }
8274             }
8275         }
8276
8277         if (appData.icsActive) {
8278             if (appData.quietPlay &&
8279                 (gameMode == IcsPlayingWhite ||
8280                  gameMode == IcsPlayingBlack)) {
8281                 SendToICS(ics_prefix);
8282                 SendToICS("set shout 1\n");
8283             }
8284             nextGameMode = IcsIdle;
8285             ics_user_moved = FALSE;
8286             /* clean up premove.  It's ugly when the game has ended and the
8287              * premove highlights are still on the board.
8288              */
8289             if (gotPremove) {
8290               gotPremove = FALSE;
8291               ClearPremoveHighlights();
8292               DrawPosition(FALSE, boards[currentMove]);
8293             }
8294             if (whosays == GE_ICS) {
8295                 switch (result) {
8296                 case WhiteWins:
8297                     if (gameMode == IcsPlayingWhite)
8298                         PlayIcsWinSound();
8299                     else if(gameMode == IcsPlayingBlack)
8300                         PlayIcsLossSound();
8301                     break;
8302                 case BlackWins:
8303                     if (gameMode == IcsPlayingBlack)
8304                         PlayIcsWinSound();
8305                     else if(gameMode == IcsPlayingWhite)
8306                         PlayIcsLossSound();
8307                     break;
8308                 case GameIsDrawn:
8309                     PlayIcsDrawSound();
8310                     break;
8311                 default:
8312                     PlayIcsUnfinishedSound();
8313                 }
8314             }
8315         } else if (gameMode == EditGame ||
8316                    gameMode == PlayFromGameFile ||
8317                    gameMode == AnalyzeMode ||
8318                    gameMode == AnalyzeFile) {
8319             nextGameMode = gameMode;
8320         } else {
8321             nextGameMode = EndOfGame;
8322         }
8323         pausing = FALSE;
8324         ModeHighlight();
8325     } else {
8326         nextGameMode = gameMode;
8327     }
8328
8329     if (appData.noChessProgram) {
8330         gameMode = nextGameMode;
8331         ModeHighlight();
8332         endingGame = 0; /* [HGM] crash */
8333         return;
8334     }
8335
8336     if (first.reuse) {
8337         /* Put first chess program into idle state */
8338         if (first.pr != NoProc &&
8339             (gameMode == MachinePlaysWhite ||
8340              gameMode == MachinePlaysBlack ||
8341              gameMode == TwoMachinesPlay ||
8342              gameMode == IcsPlayingWhite ||
8343              gameMode == IcsPlayingBlack ||
8344              gameMode == BeginningOfGame)) {
8345             SendToProgram("force\n", &first);
8346             if (first.usePing) {
8347               char buf[MSG_SIZ];
8348               sprintf(buf, "ping %d\n", ++first.lastPing);
8349               SendToProgram(buf, &first);
8350             }
8351         }
8352     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8353         /* Kill off first chess program */
8354         if (first.isr != NULL)
8355           RemoveInputSource(first.isr);
8356         first.isr = NULL;
8357
8358         if (first.pr != NoProc) {
8359             ExitAnalyzeMode();
8360             DoSleep( appData.delayBeforeQuit );
8361             SendToProgram("quit\n", &first);
8362             DoSleep( appData.delayAfterQuit );
8363             DestroyChildProcess(first.pr, first.useSigterm);
8364         }
8365         first.pr = NoProc;
8366     }
8367     if (second.reuse) {
8368         /* Put second chess program into idle state */
8369         if (second.pr != NoProc &&
8370             gameMode == TwoMachinesPlay) {
8371             SendToProgram("force\n", &second);
8372             if (second.usePing) {
8373               char buf[MSG_SIZ];
8374               sprintf(buf, "ping %d\n", ++second.lastPing);
8375               SendToProgram(buf, &second);
8376             }
8377         }
8378     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8379         /* Kill off second chess program */
8380         if (second.isr != NULL)
8381           RemoveInputSource(second.isr);
8382         second.isr = NULL;
8383
8384         if (second.pr != NoProc) {
8385             DoSleep( appData.delayBeforeQuit );
8386             SendToProgram("quit\n", &second);
8387             DoSleep( appData.delayAfterQuit );
8388             DestroyChildProcess(second.pr, second.useSigterm);
8389         }
8390         second.pr = NoProc;
8391     }
8392
8393     if (matchMode && gameMode == TwoMachinesPlay) {
8394         switch (result) {
8395         case WhiteWins:
8396           if (first.twoMachinesColor[0] == 'w') {
8397             first.matchWins++;
8398           } else {
8399             second.matchWins++;
8400           }
8401           break;
8402         case BlackWins:
8403           if (first.twoMachinesColor[0] == 'b') {
8404             first.matchWins++;
8405           } else {
8406             second.matchWins++;
8407           }
8408           break;
8409         default:
8410           break;
8411         }
8412         if (matchGame < appData.matchGames) {
8413             char *tmp;
8414             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8415                 tmp = first.twoMachinesColor;
8416                 first.twoMachinesColor = second.twoMachinesColor;
8417                 second.twoMachinesColor = tmp;
8418             }
8419             gameMode = nextGameMode;
8420             matchGame++;
8421             if(appData.matchPause>10000 || appData.matchPause<10)
8422                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8423             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8424             endingGame = 0; /* [HGM] crash */
8425             return;
8426         } else {
8427             char buf[MSG_SIZ];
8428             gameMode = nextGameMode;
8429             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8430                     first.tidy, second.tidy,
8431                     first.matchWins, second.matchWins,
8432                     appData.matchGames - (first.matchWins + second.matchWins));
8433             DisplayFatalError(buf, 0, 0);
8434         }
8435     }
8436     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8437         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8438       ExitAnalyzeMode();
8439     gameMode = nextGameMode;
8440     ModeHighlight();
8441     endingGame = 0;  /* [HGM] crash */
8442 }
8443
8444 /* Assumes program was just initialized (initString sent).
8445    Leaves program in force mode. */
8446 void
8447 FeedMovesToProgram(cps, upto)
8448      ChessProgramState *cps;
8449      int upto;
8450 {
8451     int i;
8452
8453     if (appData.debugMode)
8454       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8455               startedFromSetupPosition ? "position and " : "",
8456               backwardMostMove, upto, cps->which);
8457     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8458         // [HGM] variantswitch: make engine aware of new variant
8459         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8460                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8461         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8462         SendToProgram(buf, cps);
8463         currentlyInitializedVariant = gameInfo.variant;
8464     }
8465     SendToProgram("force\n", cps);
8466     if (startedFromSetupPosition) {
8467         SendBoard(cps, backwardMostMove);
8468     if (appData.debugMode) {
8469         fprintf(debugFP, "feedMoves\n");
8470     }
8471     }
8472     for (i = backwardMostMove; i < upto; i++) {
8473         SendMoveToProgram(i, cps);
8474     }
8475 }
8476
8477
8478 void
8479 ResurrectChessProgram()
8480 {
8481      /* The chess program may have exited.
8482         If so, restart it and feed it all the moves made so far. */
8483
8484     if (appData.noChessProgram || first.pr != NoProc) return;
8485
8486     StartChessProgram(&first);
8487     InitChessProgram(&first, FALSE);
8488     FeedMovesToProgram(&first, currentMove);
8489
8490     if (!first.sendTime) {
8491         /* can't tell gnuchess what its clock should read,
8492            so we bow to its notion. */
8493         ResetClocks();
8494         timeRemaining[0][currentMove] = whiteTimeRemaining;
8495         timeRemaining[1][currentMove] = blackTimeRemaining;
8496     }
8497
8498     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8499                 appData.icsEngineAnalyze) && first.analysisSupport) {
8500       SendToProgram("analyze\n", &first);
8501       first.analyzing = TRUE;
8502     }
8503 }
8504
8505 /*
8506  * Button procedures
8507  */
8508 void
8509 Reset(redraw, init)
8510      int redraw, init;
8511 {
8512     int i;
8513
8514     if (appData.debugMode) {
8515         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8516                 redraw, init, gameMode);
8517     }
8518     pausing = pauseExamInvalid = FALSE;
8519     startedFromSetupPosition = blackPlaysFirst = FALSE;
8520     firstMove = TRUE;
8521     whiteFlag = blackFlag = FALSE;
8522     userOfferedDraw = FALSE;
8523     hintRequested = bookRequested = FALSE;
8524     first.maybeThinking = FALSE;
8525     second.maybeThinking = FALSE;
8526     first.bookSuspend = FALSE; // [HGM] book
8527     second.bookSuspend = FALSE;
8528     thinkOutput[0] = NULLCHAR;
8529     lastHint[0] = NULLCHAR;
8530     ClearGameInfo(&gameInfo);
8531     gameInfo.variant = StringToVariant(appData.variant);
8532     ics_user_moved = ics_clock_paused = FALSE;
8533     ics_getting_history = H_FALSE;
8534     ics_gamenum = -1;
8535     white_holding[0] = black_holding[0] = NULLCHAR;
8536     ClearProgramStats();
8537     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8538
8539     ResetFrontEnd();
8540     ClearHighlights();
8541     flipView = appData.flipView;
8542     ClearPremoveHighlights();
8543     gotPremove = FALSE;
8544     alarmSounded = FALSE;
8545
8546     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8547     if(appData.serverMovesName != NULL) {
8548         /* [HGM] prepare to make moves file for broadcasting */
8549         clock_t t = clock();
8550         if(serverMoves != NULL) fclose(serverMoves);
8551         serverMoves = fopen(appData.serverMovesName, "r");
8552         if(serverMoves != NULL) {
8553             fclose(serverMoves);
8554             /* delay 15 sec before overwriting, so all clients can see end */
8555             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8556         }
8557         serverMoves = fopen(appData.serverMovesName, "w");
8558     }
8559
8560     ExitAnalyzeMode();
8561     gameMode = BeginningOfGame;
8562     ModeHighlight();
8563
8564     if(appData.icsActive) gameInfo.variant = VariantNormal;
8565     currentMove = forwardMostMove = backwardMostMove = 0;
8566     InitPosition(redraw);
8567     for (i = 0; i < MAX_MOVES; i++) {
8568         if (commentList[i] != NULL) {
8569             free(commentList[i]);
8570             commentList[i] = NULL;
8571         }
8572     }
8573
8574     ResetClocks();
8575     timeRemaining[0][0] = whiteTimeRemaining;
8576     timeRemaining[1][0] = blackTimeRemaining;
8577     if (first.pr == NULL) {
8578         StartChessProgram(&first);
8579     }
8580     if (init) {
8581             InitChessProgram(&first, startedFromSetupPosition);
8582     }
8583
8584     DisplayTitle("");
8585     DisplayMessage("", "");
8586     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8587     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8588     return;
8589 }
8590
8591 void
8592 AutoPlayGameLoop()
8593 {
8594     for (;;) {
8595         if (!AutoPlayOneMove())
8596           return;
8597         if (matchMode || appData.timeDelay == 0)
8598           continue;
8599         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8600           return;
8601         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8602         break;
8603     }
8604 }
8605
8606
8607 int
8608 AutoPlayOneMove()
8609 {
8610     int fromX, fromY, toX, toY;
8611
8612     if (appData.debugMode) {
8613       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8614     }
8615
8616     if (gameMode != PlayFromGameFile)
8617       return FALSE;
8618
8619     if (currentMove >= forwardMostMove) {
8620       gameMode = EditGame;
8621       ModeHighlight();
8622
8623       /* [AS] Clear current move marker at the end of a game */
8624       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8625
8626       return FALSE;
8627     }
8628
8629     toX = moveList[currentMove][2] - AAA;
8630     toY = moveList[currentMove][3] - ONE;
8631
8632     if (moveList[currentMove][1] == '@') {
8633         if (appData.highlightLastMove) {
8634             SetHighlights(-1, -1, toX, toY);
8635         }
8636     } else {
8637         fromX = moveList[currentMove][0] - AAA;
8638         fromY = moveList[currentMove][1] - ONE;
8639
8640         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8641
8642         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8643
8644         if (appData.highlightLastMove) {
8645             SetHighlights(fromX, fromY, toX, toY);
8646         }
8647     }
8648     DisplayMove(currentMove);
8649     SendMoveToProgram(currentMove++, &first);
8650     DisplayBothClocks();
8651     DrawPosition(FALSE, boards[currentMove]);
8652     // [HGM] PV info: always display, routine tests if empty
8653     DisplayComment(currentMove - 1, commentList[currentMove]);
8654     return TRUE;
8655 }
8656
8657
8658 int
8659 LoadGameOneMove(readAhead)
8660      ChessMove readAhead;
8661 {
8662     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8663     char promoChar = NULLCHAR;
8664     ChessMove moveType;
8665     char move[MSG_SIZ];
8666     char *p, *q;
8667
8668     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8669         gameMode != AnalyzeMode && gameMode != Training) {
8670         gameFileFP = NULL;
8671         return FALSE;
8672     }
8673
8674     yyboardindex = forwardMostMove;
8675     if (readAhead != (ChessMove)0) {
8676       moveType = readAhead;
8677     } else {
8678       if (gameFileFP == NULL)
8679           return FALSE;
8680       moveType = (ChessMove) yylex();
8681     }
8682
8683     done = FALSE;
8684     switch (moveType) {
8685       case Comment:
8686         if (appData.debugMode)
8687           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8688         p = yy_text;
8689         if (*p == '{' || *p == '[' || *p == '(') {
8690             p[strlen(p) - 1] = NULLCHAR;
8691             p++;
8692         }
8693
8694         /* append the comment but don't display it */
8695         while (*p == '\n') p++;
8696         AppendComment(currentMove, p);
8697         return TRUE;
8698
8699       case WhiteCapturesEnPassant:
8700       case BlackCapturesEnPassant:
8701       case WhitePromotionChancellor:
8702       case BlackPromotionChancellor:
8703       case WhitePromotionArchbishop:
8704       case BlackPromotionArchbishop:
8705       case WhitePromotionCentaur:
8706       case BlackPromotionCentaur:
8707       case WhitePromotionQueen:
8708       case BlackPromotionQueen:
8709       case WhitePromotionRook:
8710       case BlackPromotionRook:
8711       case WhitePromotionBishop:
8712       case BlackPromotionBishop:
8713       case WhitePromotionKnight:
8714       case BlackPromotionKnight:
8715       case WhitePromotionKing:
8716       case BlackPromotionKing:
8717       case NormalMove:
8718       case WhiteKingSideCastle:
8719       case WhiteQueenSideCastle:
8720       case BlackKingSideCastle:
8721       case BlackQueenSideCastle:
8722       case WhiteKingSideCastleWild:
8723       case WhiteQueenSideCastleWild:
8724       case BlackKingSideCastleWild:
8725       case BlackQueenSideCastleWild:
8726       /* PUSH Fabien */
8727       case WhiteHSideCastleFR:
8728       case WhiteASideCastleFR:
8729       case BlackHSideCastleFR:
8730       case BlackASideCastleFR:
8731       /* POP Fabien */
8732         if (appData.debugMode)
8733           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8734         fromX = currentMoveString[0] - AAA;
8735         fromY = currentMoveString[1] - ONE;
8736         toX = currentMoveString[2] - AAA;
8737         toY = currentMoveString[3] - ONE;
8738         promoChar = currentMoveString[4];
8739         break;
8740
8741       case WhiteDrop:
8742       case BlackDrop:
8743         if (appData.debugMode)
8744           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8745         fromX = moveType == WhiteDrop ?
8746           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8747         (int) CharToPiece(ToLower(currentMoveString[0]));
8748         fromY = DROP_RANK;
8749         toX = currentMoveString[2] - AAA;
8750         toY = currentMoveString[3] - ONE;
8751         break;
8752
8753       case WhiteWins:
8754       case BlackWins:
8755       case GameIsDrawn:
8756       case GameUnfinished:
8757         if (appData.debugMode)
8758           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8759         p = strchr(yy_text, '{');
8760         if (p == NULL) p = strchr(yy_text, '(');
8761         if (p == NULL) {
8762             p = yy_text;
8763             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8764         } else {
8765             q = strchr(p, *p == '{' ? '}' : ')');
8766             if (q != NULL) *q = NULLCHAR;
8767             p++;
8768         }
8769         GameEnds(moveType, p, GE_FILE);
8770         done = TRUE;
8771         if (cmailMsgLoaded) {
8772             ClearHighlights();
8773             flipView = WhiteOnMove(currentMove);
8774             if (moveType == GameUnfinished) flipView = !flipView;
8775             if (appData.debugMode)
8776               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8777         }
8778         break;
8779
8780       case (ChessMove) 0:       /* end of file */
8781         if (appData.debugMode)
8782           fprintf(debugFP, "Parser hit end of file\n");
8783         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8784                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8785           case MT_NONE:
8786           case MT_CHECK:
8787             break;
8788           case MT_CHECKMATE:
8789           case MT_STAINMATE:
8790             if (WhiteOnMove(currentMove)) {
8791                 GameEnds(BlackWins, "Black mates", GE_FILE);
8792             } else {
8793                 GameEnds(WhiteWins, "White mates", GE_FILE);
8794             }
8795             break;
8796           case MT_STALEMATE:
8797             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8798             break;
8799         }
8800         done = TRUE;
8801         break;
8802
8803       case MoveNumberOne:
8804         if (lastLoadGameStart == GNUChessGame) {
8805             /* GNUChessGames have numbers, but they aren't move numbers */
8806             if (appData.debugMode)
8807               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8808                       yy_text, (int) moveType);
8809             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8810         }
8811         /* else fall thru */
8812
8813       case XBoardGame:
8814       case GNUChessGame:
8815       case PGNTag:
8816         /* Reached start of next game in file */
8817         if (appData.debugMode)
8818           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8819         switch (MateTest(boards[currentMove], PosFlags(currentMove),
8820                          EP_UNKNOWN, castlingRights[currentMove]) ) {
8821           case MT_NONE:
8822           case MT_CHECK:
8823             break;
8824           case MT_CHECKMATE:
8825           case MT_STAINMATE:
8826             if (WhiteOnMove(currentMove)) {
8827                 GameEnds(BlackWins, "Black mates", GE_FILE);
8828             } else {
8829                 GameEnds(WhiteWins, "White mates", GE_FILE);
8830             }
8831             break;
8832           case MT_STALEMATE:
8833             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8834             break;
8835         }
8836         done = TRUE;
8837         break;
8838
8839       case PositionDiagram:     /* should not happen; ignore */
8840       case ElapsedTime:         /* ignore */
8841       case NAG:                 /* ignore */
8842         if (appData.debugMode)
8843           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8844                   yy_text, (int) moveType);
8845         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8846
8847       case IllegalMove:
8848         if (appData.testLegality) {
8849             if (appData.debugMode)
8850               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8851             sprintf(move, _("Illegal move: %d.%s%s"),
8852                     (forwardMostMove / 2) + 1,
8853                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8854             DisplayError(move, 0);
8855             done = TRUE;
8856         } else {
8857             if (appData.debugMode)
8858               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8859                       yy_text, currentMoveString);
8860             fromX = currentMoveString[0] - AAA;
8861             fromY = currentMoveString[1] - ONE;
8862             toX = currentMoveString[2] - AAA;
8863             toY = currentMoveString[3] - ONE;
8864             promoChar = currentMoveString[4];
8865         }
8866         break;
8867
8868       case AmbiguousMove:
8869         if (appData.debugMode)
8870           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8871         sprintf(move, _("Ambiguous move: %d.%s%s"),
8872                 (forwardMostMove / 2) + 1,
8873                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8874         DisplayError(move, 0);
8875         done = TRUE;
8876         break;
8877
8878       default:
8879       case ImpossibleMove:
8880         if (appData.debugMode)
8881           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8882         sprintf(move, _("Illegal move: %d.%s%s"),
8883                 (forwardMostMove / 2) + 1,
8884                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8885         DisplayError(move, 0);
8886         done = TRUE;
8887         break;
8888     }
8889
8890     if (done) {
8891         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8892             DrawPosition(FALSE, boards[currentMove]);
8893             DisplayBothClocks();
8894             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8895               DisplayComment(currentMove - 1, commentList[currentMove]);
8896         }
8897         (void) StopLoadGameTimer();
8898         gameFileFP = NULL;
8899         cmailOldMove = forwardMostMove;
8900         return FALSE;
8901     } else {
8902         /* currentMoveString is set as a side-effect of yylex */
8903         strcat(currentMoveString, "\n");
8904         strcpy(moveList[forwardMostMove], currentMoveString);
8905
8906         thinkOutput[0] = NULLCHAR;
8907         MakeMove(fromX, fromY, toX, toY, promoChar);
8908         currentMove = forwardMostMove;
8909         return TRUE;
8910     }
8911 }
8912
8913 /* Load the nth game from the given file */
8914 int
8915 LoadGameFromFile(filename, n, title, useList)
8916      char *filename;
8917      int n;
8918      char *title;
8919      /*Boolean*/ int useList;
8920 {
8921     FILE *f;
8922     char buf[MSG_SIZ];
8923
8924     if (strcmp(filename, "-") == 0) {
8925         f = stdin;
8926         title = "stdin";
8927     } else {
8928         f = fopen(filename, "rb");
8929         if (f == NULL) {
8930           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
8931             DisplayError(buf, errno);
8932             return FALSE;
8933         }
8934     }
8935     if (fseek(f, 0, 0) == -1) {
8936         /* f is not seekable; probably a pipe */
8937         useList = FALSE;
8938     }
8939     if (useList && n == 0) {
8940         int error = GameListBuild(f);
8941         if (error) {
8942             DisplayError(_("Cannot build game list"), error);
8943         } else if (!ListEmpty(&gameList) &&
8944                    ((ListGame *) gameList.tailPred)->number > 1) {
8945           // TODO convert to GTK
8946           //        GameListPopUp(f, title);
8947             return TRUE;
8948         }
8949         GameListDestroy();
8950         n = 1;
8951     }
8952     if (n == 0) n = 1;
8953     return LoadGame(f, n, title, FALSE);
8954 }
8955
8956
8957 void
8958 MakeRegisteredMove()
8959 {
8960     int fromX, fromY, toX, toY;
8961     char promoChar;
8962     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8963         switch (cmailMoveType[lastLoadGameNumber - 1]) {
8964           case CMAIL_MOVE:
8965           case CMAIL_DRAW:
8966             if (appData.debugMode)
8967               fprintf(debugFP, "Restoring %s for game %d\n",
8968                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8969
8970             thinkOutput[0] = NULLCHAR;
8971             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8972             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8973             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8974             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8975             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8976             promoChar = cmailMove[lastLoadGameNumber - 1][4];
8977             MakeMove(fromX, fromY, toX, toY, promoChar);
8978             ShowMove(fromX, fromY, toX, toY);
8979
8980             switch (MateTest(boards[currentMove], PosFlags(currentMove),
8981                              EP_UNKNOWN, castlingRights[currentMove]) ) {
8982               case MT_NONE:
8983               case MT_CHECK:
8984                 break;
8985
8986               case MT_CHECKMATE:
8987               case MT_STAINMATE:
8988                 if (WhiteOnMove(currentMove)) {
8989                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
8990                 } else {
8991                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
8992                 }
8993                 break;
8994
8995               case MT_STALEMATE:
8996                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8997                 break;
8998             }
8999
9000             break;
9001
9002           case CMAIL_RESIGN:
9003             if (WhiteOnMove(currentMove)) {
9004                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9005             } else {
9006                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9007             }
9008             break;
9009
9010           case CMAIL_ACCEPT:
9011             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9012             break;
9013
9014           default:
9015             break;
9016         }
9017     }
9018
9019     return;
9020 }
9021
9022 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9023 int
9024 CmailLoadGame(f, gameNumber, title, useList)
9025      FILE *f;
9026      int gameNumber;
9027      char *title;
9028      int useList;
9029 {
9030     int retVal;
9031
9032     if (gameNumber > nCmailGames) {
9033         DisplayError(_("No more games in this message"), 0);
9034         return FALSE;
9035     }
9036     if (f == lastLoadGameFP) {
9037         int offset = gameNumber - lastLoadGameNumber;
9038         if (offset == 0) {
9039             cmailMsg[0] = NULLCHAR;
9040             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9041                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9042                 nCmailMovesRegistered--;
9043             }
9044             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9045             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9046                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9047             }
9048         } else {
9049             if (! RegisterMove()) return FALSE;
9050         }
9051     }
9052
9053     retVal = LoadGame(f, gameNumber, title, useList);
9054
9055     /* Make move registered during previous look at this game, if any */
9056     MakeRegisteredMove();
9057
9058     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9059         commentList[currentMove]
9060           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9061         DisplayComment(currentMove - 1, commentList[currentMove]);
9062     }
9063
9064     return retVal;
9065 }
9066
9067 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9068 int
9069 ReloadGame(offset)
9070      int offset;
9071 {
9072     int gameNumber = lastLoadGameNumber + offset;
9073     if (lastLoadGameFP == NULL) {
9074         DisplayError(_("No game has been loaded yet"), 0);
9075         return FALSE;
9076     }
9077     if (gameNumber <= 0) {
9078         DisplayError(_("Can't back up any further"), 0);
9079         return FALSE;
9080     }
9081     if (cmailMsgLoaded) {
9082         return CmailLoadGame(lastLoadGameFP, gameNumber,
9083                              lastLoadGameTitle, lastLoadGameUseList);
9084     } else {
9085         return LoadGame(lastLoadGameFP, gameNumber,
9086                         lastLoadGameTitle, lastLoadGameUseList);
9087     }
9088 }
9089
9090
9091
9092 /* Load the nth game from open file f */
9093 int
9094 LoadGame(f, gameNumber, title, useList)
9095      FILE *f;
9096      int gameNumber;
9097      char *title;
9098      int useList;
9099 {
9100     ChessMove cm;
9101     char buf[MSG_SIZ];
9102     int gn = gameNumber;
9103     ListGame *lg = NULL;
9104     int numPGNTags = 0;
9105     int err;
9106     GameMode oldGameMode;
9107     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9108
9109     if (appData.debugMode)
9110         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9111
9112     if (gameMode == Training )
9113         SetTrainingModeOff();
9114
9115     oldGameMode = gameMode;
9116     if (gameMode != BeginningOfGame) 
9117       {
9118         Reset(FALSE, TRUE);
9119       };
9120
9121     gameFileFP = f;
9122     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9123       {
9124         fclose(lastLoadGameFP);
9125       };
9126
9127     if (useList) 
9128       {
9129         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9130         
9131         if (lg) 
9132           {
9133             fseek(f, lg->offset, 0);
9134             GameListHighlight(gameNumber);
9135             gn = 1;
9136           }
9137         else 
9138           {
9139             DisplayError(_("Game number out of range"), 0);
9140             return FALSE;
9141           };
9142       } 
9143     else 
9144       {
9145         GameListDestroy();
9146         if (fseek(f, 0, 0) == -1) 
9147           {
9148             if (f == lastLoadGameFP ?
9149                 gameNumber == lastLoadGameNumber + 1 :
9150                 gameNumber == 1) 
9151               {
9152                 gn = 1;
9153               } 
9154             else 
9155               {
9156                 DisplayError(_("Can't seek on game file"), 0);
9157                 return FALSE;
9158               };
9159           };
9160       };
9161
9162     lastLoadGameFP      = f;
9163     lastLoadGameNumber  = gameNumber;
9164     strcpy(lastLoadGameTitle, title);
9165     lastLoadGameUseList = useList;
9166
9167     yynewfile(f);
9168
9169     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9170       {
9171         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9172                  lg->gameInfo.black);
9173         DisplayTitle(buf);
9174       } 
9175     else if (*title != NULLCHAR) 
9176       {
9177         if (gameNumber > 1) 
9178           {
9179             sprintf(buf, "%s %d", title, gameNumber);
9180             DisplayTitle(buf);
9181           } 
9182         else 
9183           {
9184             DisplayTitle(title);
9185           };
9186       };
9187
9188     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9189       {
9190         gameMode = PlayFromGameFile;
9191         ModeHighlight();
9192       };
9193
9194     currentMove = forwardMostMove = backwardMostMove = 0;
9195     CopyBoard(boards[0], initialPosition);
9196     StopClocks();
9197
9198     /*
9199      * Skip the first gn-1 games in the file.
9200      * Also skip over anything that precedes an identifiable
9201      * start of game marker, to avoid being confused by
9202      * garbage at the start of the file.  Currently
9203      * recognized start of game markers are the move number "1",
9204      * the pattern "gnuchess .* game", the pattern
9205      * "^[#;%] [^ ]* game file", and a PGN tag block.
9206      * A game that starts with one of the latter two patterns
9207      * will also have a move number 1, possibly
9208      * following a position diagram.
9209      * 5-4-02: Let's try being more lenient and allowing a game to
9210      * start with an unnumbered move.  Does that break anything?
9211      */
9212     cm = lastLoadGameStart = (ChessMove) 0;
9213     while (gn > 0) {
9214         yyboardindex = forwardMostMove;
9215         cm = (ChessMove) yylex();
9216         switch (cm) {
9217           case (ChessMove) 0:
9218             if (cmailMsgLoaded) {
9219                 nCmailGames = CMAIL_MAX_GAMES - gn;
9220             } else {
9221                 Reset(TRUE, TRUE);
9222                 DisplayError(_("Game not found in file"), 0);
9223             }
9224             return FALSE;
9225
9226           case GNUChessGame:
9227           case XBoardGame:
9228             gn--;
9229             lastLoadGameStart = cm;
9230             break;
9231
9232           case MoveNumberOne:
9233             switch (lastLoadGameStart) {
9234               case GNUChessGame:
9235               case XBoardGame:
9236               case PGNTag:
9237                 break;
9238               case MoveNumberOne:
9239               case (ChessMove) 0:
9240                 gn--;           /* count this game */
9241                 lastLoadGameStart = cm;
9242                 break;
9243               default:
9244                 /* impossible */
9245                 break;
9246             }
9247             break;
9248
9249           case PGNTag:
9250             switch (lastLoadGameStart) {
9251               case GNUChessGame:
9252               case PGNTag:
9253               case MoveNumberOne:
9254               case (ChessMove) 0:
9255                 gn--;           /* count this game */
9256                 lastLoadGameStart = cm;
9257                 break;
9258               case XBoardGame:
9259                 lastLoadGameStart = cm; /* game counted already */
9260                 break;
9261               default:
9262                 /* impossible */
9263                 break;
9264             }
9265             if (gn > 0) {
9266                 do {
9267                     yyboardindex = forwardMostMove;
9268                     cm = (ChessMove) yylex();
9269                 } while (cm == PGNTag || cm == Comment);
9270             }
9271             break;
9272
9273           case WhiteWins:
9274           case BlackWins:
9275           case GameIsDrawn:
9276             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9277                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9278                     != CMAIL_OLD_RESULT) {
9279                     nCmailResults ++ ;
9280                     cmailResult[  CMAIL_MAX_GAMES
9281                                 - gn - 1] = CMAIL_OLD_RESULT;
9282                 }
9283             }
9284             break;
9285
9286           case NormalMove:
9287             /* Only a NormalMove can be at the start of a game
9288              * without a position diagram. */
9289             if (lastLoadGameStart == (ChessMove) 0) {
9290               gn--;
9291               lastLoadGameStart = MoveNumberOne;
9292             }
9293             break;
9294
9295           default:
9296             break;
9297         }
9298     }
9299
9300     if (appData.debugMode)
9301       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9302
9303     if (cm == XBoardGame) {
9304         /* Skip any header junk before position diagram and/or move 1 */
9305         for (;;) {
9306             yyboardindex = forwardMostMove;
9307             cm = (ChessMove) yylex();
9308
9309             if (cm == (ChessMove) 0 ||
9310                 cm == GNUChessGame || cm == XBoardGame) {
9311                 /* Empty game; pretend end-of-file and handle later */
9312                 cm = (ChessMove) 0;
9313                 break;
9314             }
9315
9316             if (cm == MoveNumberOne || cm == PositionDiagram ||
9317                 cm == PGNTag || cm == Comment)
9318               break;
9319         }
9320     } else if (cm == GNUChessGame) {
9321         if (gameInfo.event != NULL) {
9322             free(gameInfo.event);
9323         }
9324         gameInfo.event = StrSave(yy_text);
9325     }
9326
9327     startedFromSetupPosition = FALSE;
9328     while (cm == PGNTag) {
9329         if (appData.debugMode)
9330           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9331         err = ParsePGNTag(yy_text, &gameInfo);
9332         if (!err) numPGNTags++;
9333
9334         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9335         if(gameInfo.variant != oldVariant) {
9336             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9337             InitPosition(TRUE);
9338             oldVariant = gameInfo.variant;
9339             if (appData.debugMode)
9340               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9341         }
9342
9343
9344         if (gameInfo.fen != NULL) {
9345           Board initial_position;
9346           startedFromSetupPosition = TRUE;
9347           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9348             Reset(TRUE, TRUE);
9349             DisplayError(_("Bad FEN position in file"), 0);
9350             return FALSE;
9351           }
9352           CopyBoard(boards[0], initial_position);
9353           if (blackPlaysFirst) {
9354             currentMove = forwardMostMove = backwardMostMove = 1;
9355             CopyBoard(boards[1], initial_position);
9356             strcpy(moveList[0], "");
9357             strcpy(parseList[0], "");
9358             timeRemaining[0][1] = whiteTimeRemaining;
9359             timeRemaining[1][1] = blackTimeRemaining;
9360             if (commentList[0] != NULL) {
9361               commentList[1] = commentList[0];
9362               commentList[0] = NULL;
9363             }
9364           } else {
9365             currentMove = forwardMostMove = backwardMostMove = 0;
9366           }
9367           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9368           {   int i;
9369               initialRulePlies = FENrulePlies;
9370               epStatus[forwardMostMove] = FENepStatus;
9371               for( i=0; i< nrCastlingRights; i++ )
9372                   initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9373           }
9374           yyboardindex = forwardMostMove;
9375           free(gameInfo.fen);
9376           gameInfo.fen = NULL;
9377         }
9378
9379         yyboardindex = forwardMostMove;
9380         cm = (ChessMove) yylex();
9381
9382         /* Handle comments interspersed among the tags */
9383         while (cm == Comment) {
9384             char *p;
9385             if (appData.debugMode)
9386               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9387             p = yy_text;
9388             if (*p == '{' || *p == '[' || *p == '(') {
9389                 p[strlen(p) - 1] = NULLCHAR;
9390                 p++;
9391             }
9392             while (*p == '\n') p++;
9393             AppendComment(currentMove, p);
9394             yyboardindex = forwardMostMove;
9395             cm = (ChessMove) yylex();
9396         }
9397     }
9398
9399     /* don't rely on existence of Event tag since if game was
9400      * pasted from clipboard the Event tag may not exist
9401      */
9402     if (numPGNTags > 0){
9403         char *tags;
9404         if (gameInfo.variant == VariantNormal) {
9405           gameInfo.variant = StringToVariant(gameInfo.event);
9406         }
9407         if (!matchMode) {
9408           if( appData.autoDisplayTags ) {
9409             tags = PGNTags(&gameInfo);
9410             TagsPopUp(tags, CmailMsg());
9411             free(tags);
9412           }
9413         }
9414     } else {
9415         /* Make something up, but don't display it now */
9416         SetGameInfo();
9417         TagsPopDown();
9418     }
9419
9420     if (cm == PositionDiagram) {
9421         int i, j;
9422         char *p;
9423         Board initial_position;
9424
9425         if (appData.debugMode)
9426           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9427
9428         if (!startedFromSetupPosition) {
9429             p = yy_text;
9430             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9431               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9432                 switch (*p) {
9433                   case '[':
9434                   case '-':
9435                   case ' ':
9436                   case '\t':
9437                   case '\n':
9438                   case '\r':
9439                     break;
9440                   default:
9441                     initial_position[i][j++] = CharToPiece(*p);
9442                     break;
9443                 }
9444             while (*p == ' ' || *p == '\t' ||
9445                    *p == '\n' || *p == '\r') p++;
9446
9447             if (strncmp(p, "black", strlen("black"))==0)
9448               blackPlaysFirst = TRUE;
9449             else
9450               blackPlaysFirst = FALSE;
9451             startedFromSetupPosition = TRUE;
9452
9453             CopyBoard(boards[0], initial_position);
9454             if (blackPlaysFirst) {
9455                 currentMove = forwardMostMove = backwardMostMove = 1;
9456                 CopyBoard(boards[1], initial_position);
9457                 strcpy(moveList[0], "");
9458                 strcpy(parseList[0], "");
9459                 timeRemaining[0][1] = whiteTimeRemaining;
9460                 timeRemaining[1][1] = blackTimeRemaining;
9461                 if (commentList[0] != NULL) {
9462                     commentList[1] = commentList[0];
9463                     commentList[0] = NULL;
9464                 }
9465             } else {
9466                 currentMove = forwardMostMove = backwardMostMove = 0;
9467             }
9468         }
9469         yyboardindex = forwardMostMove;
9470         cm = (ChessMove) yylex();
9471     }
9472
9473     if (first.pr == NoProc) {
9474         StartChessProgram(&first);
9475     }
9476     InitChessProgram(&first, FALSE);
9477     SendToProgram("force\n", &first);
9478     if (startedFromSetupPosition) {
9479         SendBoard(&first, forwardMostMove);
9480     if (appData.debugMode) {
9481         fprintf(debugFP, "Load Game\n");
9482     }
9483         DisplayBothClocks();
9484     }
9485
9486     /* [HGM] server: flag to write setup moves in broadcast file as one */
9487     loadFlag = appData.suppressLoadMoves;
9488
9489     while (cm == Comment) {
9490         char *p;
9491         if (appData.debugMode)
9492           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9493         p = yy_text;
9494         if (*p == '{' || *p == '[' || *p == '(') {
9495             p[strlen(p) - 1] = NULLCHAR;
9496             p++;
9497         }
9498         while (*p == '\n') p++;
9499         AppendComment(currentMove, p);
9500         yyboardindex = forwardMostMove;
9501         cm = (ChessMove) yylex();
9502     }
9503
9504     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9505         cm == WhiteWins || cm == BlackWins ||
9506         cm == GameIsDrawn || cm == GameUnfinished) {
9507         DisplayMessage("", _("No moves in game"));
9508         if (cmailMsgLoaded) {
9509             if (appData.debugMode)
9510               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9511             ClearHighlights();
9512             flipView = FALSE;
9513         }
9514         DrawPosition(FALSE, boards[currentMove]);
9515         DisplayBothClocks();
9516         gameMode = EditGame;
9517         ModeHighlight();
9518         gameFileFP = NULL;
9519         cmailOldMove = 0;
9520         return TRUE;
9521     }
9522
9523     // [HGM] PV info: routine tests if comment empty
9524     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9525         DisplayComment(currentMove - 1, commentList[currentMove]);
9526     }
9527     if (!matchMode && appData.timeDelay != 0)
9528       DrawPosition(FALSE, boards[currentMove]);
9529
9530     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9531       programStats.ok_to_send = 1;
9532     }
9533
9534     /* if the first token after the PGN tags is a move
9535      * and not move number 1, retrieve it from the parser
9536      */
9537     if (cm != MoveNumberOne)
9538         LoadGameOneMove(cm);
9539
9540     /* load the remaining moves from the file */
9541     while (LoadGameOneMove((ChessMove)0)) {
9542       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9543       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9544     }
9545
9546     /* rewind to the start of the game */
9547     currentMove = backwardMostMove;
9548
9549     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9550
9551     if (oldGameMode == AnalyzeFile ||
9552         oldGameMode == AnalyzeMode) {
9553       AnalyzeFileEvent();
9554     }
9555
9556     if (matchMode || appData.timeDelay == 0) {
9557       ToEndEvent();
9558       gameMode = EditGame;
9559       ModeHighlight();
9560     } else if (appData.timeDelay > 0) {
9561       AutoPlayGameLoop();
9562     }
9563
9564     if (appData.debugMode)
9565         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9566
9567     loadFlag = 0; /* [HGM] true game starts */
9568     return TRUE;
9569 }
9570
9571 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9572 int
9573 ReloadPosition(offset)
9574      int offset;
9575 {
9576     int positionNumber = lastLoadPositionNumber + offset;
9577     if (lastLoadPositionFP == NULL) {
9578         DisplayError(_("No position has been loaded yet"), 0);
9579         return FALSE;
9580     }
9581     if (positionNumber <= 0) {
9582         DisplayError(_("Can't back up any further"), 0);
9583         return FALSE;
9584     }
9585     return LoadPosition(lastLoadPositionFP, positionNumber,
9586                         lastLoadPositionTitle);
9587 }
9588
9589 /* Load the nth position from the given file */
9590 int
9591 LoadPositionFromFile(filename, n, title)
9592      char *filename;
9593      int n;
9594      char *title;
9595 {
9596     FILE *f;
9597     char buf[MSG_SIZ];
9598
9599     if (strcmp(filename, "-") == 0) {
9600         return LoadPosition(stdin, n, "stdin");
9601     } else {
9602         f = fopen(filename, "rb");
9603         if (f == NULL) {
9604             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9605             DisplayError(buf, errno);
9606             return FALSE;
9607         } else {
9608             return LoadPosition(f, n, title);
9609         }
9610     }
9611 }
9612
9613 /* Load the nth position from the given open file, and close it */
9614 int
9615 LoadPosition(f, positionNumber, title)
9616      FILE *f;
9617      int positionNumber;
9618      char *title;
9619 {
9620     char *p, line[MSG_SIZ];
9621     Board initial_position;
9622     int i, j, fenMode, pn;
9623
9624     if (gameMode == Training )
9625         SetTrainingModeOff();
9626
9627     if (gameMode != BeginningOfGame) {
9628         Reset(FALSE, TRUE);
9629     }
9630     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9631         fclose(lastLoadPositionFP);
9632     }
9633     if (positionNumber == 0) positionNumber = 1;
9634     lastLoadPositionFP = f;
9635     lastLoadPositionNumber = positionNumber;
9636     strcpy(lastLoadPositionTitle, title);
9637     if (first.pr == NoProc) {
9638       StartChessProgram(&first);
9639       InitChessProgram(&first, FALSE);
9640     }
9641     pn = positionNumber;
9642     if (positionNumber < 0) {
9643         /* Negative position number means to seek to that byte offset */
9644         if (fseek(f, -positionNumber, 0) == -1) {
9645             DisplayError(_("Can't seek on position file"), 0);
9646             return FALSE;
9647         };
9648         pn = 1;
9649     } else {
9650         if (fseek(f, 0, 0) == -1) {
9651             if (f == lastLoadPositionFP ?
9652                 positionNumber == lastLoadPositionNumber + 1 :
9653                 positionNumber == 1) {
9654                 pn = 1;
9655             } else {
9656                 DisplayError(_("Can't seek on position file"), 0);
9657                 return FALSE;
9658             }
9659         }
9660     }
9661     /* See if this file is FEN or old-style xboard */
9662     if (fgets(line, MSG_SIZ, f) == NULL) {
9663         DisplayError(_("Position not found in file"), 0);
9664         return FALSE;
9665     }
9666     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9667     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9668
9669     if (pn >= 2) {
9670         if (fenMode || line[0] == '#') pn--;
9671         while (pn > 0) {
9672             /* skip positions before number pn */
9673             if (fgets(line, MSG_SIZ, f) == NULL) {
9674                 Reset(TRUE, TRUE);
9675                 DisplayError(_("Position not found in file"), 0);
9676                 return FALSE;
9677             }
9678             if (fenMode || line[0] == '#') pn--;
9679         }
9680     }
9681
9682     if (fenMode) {
9683         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9684             DisplayError(_("Bad FEN position in file"), 0);
9685             return FALSE;
9686         }
9687     } else {
9688         (void) fgets(line, MSG_SIZ, f);
9689         (void) fgets(line, MSG_SIZ, f);
9690
9691         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9692             (void) fgets(line, MSG_SIZ, f);
9693             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9694                 if (*p == ' ')
9695                   continue;
9696                 initial_position[i][j++] = CharToPiece(*p);
9697             }
9698         }
9699
9700         blackPlaysFirst = FALSE;
9701         if (!feof(f)) {
9702             (void) fgets(line, MSG_SIZ, f);
9703             if (strncmp(line, "black", strlen("black"))==0)
9704               blackPlaysFirst = TRUE;
9705         }
9706     }
9707     startedFromSetupPosition = TRUE;
9708
9709     SendToProgram("force\n", &first);
9710     CopyBoard(boards[0], initial_position);
9711     if (blackPlaysFirst) {
9712         currentMove = forwardMostMove = backwardMostMove = 1;
9713         strcpy(moveList[0], "");
9714         strcpy(parseList[0], "");
9715         CopyBoard(boards[1], initial_position);
9716         DisplayMessage("", _("Black to play"));
9717     } else {
9718         currentMove = forwardMostMove = backwardMostMove = 0;
9719         DisplayMessage("", _("White to play"));
9720     }
9721           /* [HGM] copy FEN attributes as well */
9722           {   int i;
9723               initialRulePlies = FENrulePlies;
9724               epStatus[forwardMostMove] = FENepStatus;
9725               for( i=0; i< nrCastlingRights; i++ )
9726                   castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9727           }
9728     SendBoard(&first, forwardMostMove);
9729     if (appData.debugMode) {
9730 int i, j;
9731   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9732   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9733         fprintf(debugFP, "Load Position\n");
9734     }
9735
9736     if (positionNumber > 1) {
9737         sprintf(line, "%s %d", title, positionNumber);
9738         DisplayTitle(line);
9739     } else {
9740         DisplayTitle(title);
9741     }
9742     gameMode = EditGame;
9743     ModeHighlight();
9744     ResetClocks();
9745     timeRemaining[0][1] = whiteTimeRemaining;
9746     timeRemaining[1][1] = blackTimeRemaining;
9747     DrawPosition(FALSE, boards[currentMove]);
9748
9749     return TRUE;
9750 }
9751
9752
9753 void
9754 CopyPlayerNameIntoFileName(dest, src)
9755      char **dest, *src;
9756 {
9757     while (*src != NULLCHAR && *src != ',') {
9758         if (*src == ' ') {
9759             *(*dest)++ = '_';
9760             src++;
9761         } else {
9762             *(*dest)++ = *src++;
9763         }
9764     }
9765 }
9766
9767 char *DefaultFileName(ext)
9768      char *ext;
9769 {
9770     static char def[MSG_SIZ];
9771     char *p;
9772
9773     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9774         p = def;
9775         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9776         *p++ = '-';
9777         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9778         *p++ = '.';
9779         strcpy(p, ext);
9780     } else {
9781         def[0] = NULLCHAR;
9782     }
9783     return def;
9784 }
9785
9786 /* Save the current game to the given file */
9787 int
9788 SaveGameToFile(filename, append)
9789      char *filename;
9790      int append;
9791 {
9792     FILE *f;
9793     char buf[MSG_SIZ];
9794
9795     if (strcmp(filename, "-") == 0) {
9796         return SaveGame(stdout, 0, NULL);
9797     } else {
9798         f = fopen(filename, append ? "a" : "w");
9799         if (f == NULL) {
9800             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9801             DisplayError(buf, errno);
9802             return FALSE;
9803         } else {
9804             return SaveGame(f, 0, NULL);
9805         }
9806     }
9807 }
9808
9809 char *
9810 SavePart(str)
9811      char *str;
9812 {
9813     static char buf[MSG_SIZ];
9814     char *p;
9815
9816     p = strchr(str, ' ');
9817     if (p == NULL) return str;
9818     strncpy(buf, str, p - str);
9819     buf[p - str] = NULLCHAR;
9820     return buf;
9821 }
9822
9823 #define PGN_MAX_LINE 75
9824
9825 #define PGN_SIDE_WHITE  0
9826 #define PGN_SIDE_BLACK  1
9827
9828 /* [AS] */
9829 static int FindFirstMoveOutOfBook( int side )
9830 {
9831     int result = -1;
9832
9833     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9834         int index = backwardMostMove;
9835         int has_book_hit = 0;
9836
9837         if( (index % 2) != side ) {
9838             index++;
9839         }
9840
9841         while( index < forwardMostMove ) {
9842             /* Check to see if engine is in book */
9843             int depth = pvInfoList[index].depth;
9844             int score = pvInfoList[index].score;
9845             int in_book = 0;
9846
9847             if( depth <= 2 ) {
9848                 in_book = 1;
9849             }
9850             else if( score == 0 && depth == 63 ) {
9851                 in_book = 1; /* Zappa */
9852             }
9853             else if( score == 2 && depth == 99 ) {
9854                 in_book = 1; /* Abrok */
9855             }
9856
9857             has_book_hit += in_book;
9858
9859             if( ! in_book ) {
9860                 result = index;
9861
9862                 break;
9863             }
9864
9865             index += 2;
9866         }
9867     }
9868
9869     return result;
9870 }
9871
9872 /* [AS] */
9873 void GetOutOfBookInfo( char * buf )
9874 {
9875     int oob[2];
9876     int i;
9877     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9878
9879     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9880     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9881
9882     *buf = '\0';
9883
9884     if( oob[0] >= 0 || oob[1] >= 0 ) {
9885         for( i=0; i<2; i++ ) {
9886             int idx = oob[i];
9887
9888             if( idx >= 0 ) {
9889                 if( i > 0 && oob[0] >= 0 ) {
9890                     strcat( buf, "   " );
9891                 }
9892
9893                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9894                 sprintf( buf+strlen(buf), "%s%.2f",
9895                     pvInfoList[idx].score >= 0 ? "+" : "",
9896                     pvInfoList[idx].score / 100.0 );
9897             }
9898         }
9899     }
9900 }
9901
9902 /* Save game in PGN style and close the file */
9903 int
9904 SaveGamePGN(f)
9905      FILE *f;
9906 {
9907     int i, offset, linelen, newblock;
9908     time_t tm;
9909 //    char *movetext;
9910     char numtext[32];
9911     int movelen, numlen, blank;
9912     char move_buffer[100]; /* [AS] Buffer for move+PV info */
9913
9914     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9915
9916     tm = time((time_t *) NULL);
9917
9918     PrintPGNTags(f, &gameInfo);
9919
9920     if (backwardMostMove > 0 || startedFromSetupPosition) {
9921         char *fen = PositionToFEN(backwardMostMove, NULL);
9922         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9923         fprintf(f, "\n{--------------\n");
9924         PrintPosition(f, backwardMostMove);
9925         fprintf(f, "--------------}\n");
9926         free(fen);
9927     }
9928     else {
9929         /* [AS] Out of book annotation */
9930         if( appData.saveOutOfBookInfo ) {
9931             char buf[64];
9932
9933             GetOutOfBookInfo( buf );
9934
9935             if( buf[0] != '\0' ) {
9936                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9937             }
9938         }
9939
9940         fprintf(f, "\n");
9941     }
9942
9943     i = backwardMostMove;
9944     linelen = 0;
9945     newblock = TRUE;
9946
9947     while (i < forwardMostMove) {
9948         /* Print comments preceding this move */
9949         if (commentList[i] != NULL) {
9950             if (linelen > 0) fprintf(f, "\n");
9951             fprintf(f, "{\n%s}\n", commentList[i]);
9952             linelen = 0;
9953             newblock = TRUE;
9954         }
9955
9956         /* Format move number */
9957         if ((i % 2) == 0) {
9958             sprintf(numtext, "%d.", (i - offset)/2 + 1);
9959         } else {
9960             if (newblock) {
9961                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9962             } else {
9963                 numtext[0] = NULLCHAR;
9964             }
9965         }
9966         numlen = strlen(numtext);
9967         newblock = FALSE;
9968
9969         /* Print move number */
9970         blank = linelen > 0 && numlen > 0;
9971         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9972             fprintf(f, "\n");
9973             linelen = 0;
9974             blank = 0;
9975         }
9976         if (blank) {
9977             fprintf(f, " ");
9978             linelen++;
9979         }
9980         fprintf(f, "%s", numtext);
9981         linelen += numlen;
9982
9983         /* Get move */
9984         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9985         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9986
9987         /* Print move */
9988         blank = linelen > 0 && movelen > 0;
9989         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9990             fprintf(f, "\n");
9991             linelen = 0;
9992             blank = 0;
9993         }
9994         if (blank) {
9995             fprintf(f, " ");
9996             linelen++;
9997         }
9998         fprintf(f, "%s", move_buffer);
9999         linelen += movelen;
10000
10001         /* [AS] Add PV info if present */
10002         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10003             /* [HGM] add time */
10004             char buf[MSG_SIZ]; int seconds = 0;
10005
10006             if(i >= backwardMostMove) {
10007                 if(WhiteOnMove(i))
10008                         seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
10009                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
10010                 else
10011                         seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
10012                                   + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
10013             }
10014             seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
10015
10016             if( seconds <= 0) buf[0] = 0; else
10017             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10018                 seconds = (seconds + 4)/10; // round to full seconds
10019                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10020                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10021             }
10022
10023             sprintf( move_buffer, "{%s%.2f/%d%s}",
10024                 pvInfoList[i].score >= 0 ? "+" : "",
10025                 pvInfoList[i].score / 100.0,
10026                 pvInfoList[i].depth,
10027                 buf );
10028
10029             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10030
10031             /* Print score/depth */
10032             blank = linelen > 0 && movelen > 0;
10033             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10034                 fprintf(f, "\n");
10035                 linelen = 0;
10036                 blank = 0;
10037             }
10038             if (blank) {
10039                 fprintf(f, " ");
10040                 linelen++;
10041             }
10042             fprintf(f, "%s", move_buffer);
10043             linelen += movelen;
10044         }
10045
10046         i++;
10047     }
10048
10049     /* Start a new line */
10050     if (linelen > 0) fprintf(f, "\n");
10051
10052     /* Print comments after last move */
10053     if (commentList[i] != NULL) {
10054         fprintf(f, "{\n%s}\n", commentList[i]);
10055     }
10056
10057     /* Print result */
10058     if (gameInfo.resultDetails != NULL &&
10059         gameInfo.resultDetails[0] != NULLCHAR) {
10060         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10061                 PGNResult(gameInfo.result));
10062     } else {
10063         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10064     }
10065
10066     fclose(f);
10067     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10068     return TRUE;
10069 }
10070
10071 /* Save game in old style and close the file */
10072 int
10073 SaveGameOldStyle(f)
10074      FILE *f;
10075 {
10076     int i, offset;
10077     time_t tm;
10078
10079     tm = time((time_t *) NULL);
10080
10081     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10082     PrintOpponents(f);
10083
10084     if (backwardMostMove > 0 || startedFromSetupPosition) {
10085         fprintf(f, "\n[--------------\n");
10086         PrintPosition(f, backwardMostMove);
10087         fprintf(f, "--------------]\n");
10088     } else {
10089         fprintf(f, "\n");
10090     }
10091
10092     i = backwardMostMove;
10093     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10094
10095     while (i < forwardMostMove) {
10096         if (commentList[i] != NULL) {
10097             fprintf(f, "[%s]\n", commentList[i]);
10098         }
10099
10100         if ((i % 2) == 1) {
10101             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10102             i++;
10103         } else {
10104             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10105             i++;
10106             if (commentList[i] != NULL) {
10107                 fprintf(f, "\n");
10108                 continue;
10109             }
10110             if (i >= forwardMostMove) {
10111                 fprintf(f, "\n");
10112                 break;
10113             }
10114             fprintf(f, "%s\n", parseList[i]);
10115             i++;
10116         }
10117     }
10118
10119     if (commentList[i] != NULL) {
10120         fprintf(f, "[%s]\n", commentList[i]);
10121     }
10122
10123     /* This isn't really the old style, but it's close enough */
10124     if (gameInfo.resultDetails != NULL &&
10125         gameInfo.resultDetails[0] != NULLCHAR) {
10126         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10127                 gameInfo.resultDetails);
10128     } else {
10129         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10130     }
10131
10132     fclose(f);
10133     return TRUE;
10134 }
10135
10136 /* Save the current game to open file f and close the file */
10137 int
10138 SaveGame(f, dummy, dummy2)
10139      FILE *f;
10140      int dummy;
10141      char *dummy2;
10142 {
10143     if (gameMode == EditPosition) EditPositionDone();
10144     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10145     if (appData.oldSaveStyle)
10146       return SaveGameOldStyle(f);
10147     else
10148       return SaveGamePGN(f);
10149 }
10150
10151 /* Save the current position to the given file */
10152 int
10153 SavePositionToFile(filename)
10154      char *filename;
10155 {
10156     FILE *f;
10157     char buf[MSG_SIZ];
10158
10159     if (strcmp(filename, "-") == 0) {
10160         return SavePosition(stdout, 0, NULL);
10161     } else {
10162         f = fopen(filename, "a");
10163         if (f == NULL) {
10164             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10165             DisplayError(buf, errno);
10166             return FALSE;
10167         } else {
10168             SavePosition(f, 0, NULL);
10169             return TRUE;
10170         }
10171     }
10172 }
10173
10174 /* Save the current position to the given open file and close the file */
10175 int
10176 SavePosition(f, dummy, dummy2)
10177      FILE *f;
10178      int dummy;
10179      char *dummy2;
10180 {
10181     time_t tm;
10182     char *fen;
10183
10184     if (appData.oldSaveStyle) {
10185         tm = time((time_t *) NULL);
10186
10187         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10188         PrintOpponents(f);
10189         fprintf(f, "[--------------\n");
10190         PrintPosition(f, currentMove);
10191         fprintf(f, "--------------]\n");
10192     } else {
10193         fen = PositionToFEN(currentMove, NULL);
10194         fprintf(f, "%s\n", fen);
10195         free(fen);
10196     }
10197     fclose(f);
10198     return TRUE;
10199 }
10200
10201 void
10202 ReloadCmailMsgEvent(unregister)
10203      int unregister;
10204 {
10205 #if !WIN32
10206     static char *inFilename = NULL;
10207     static char *outFilename;
10208     int i;
10209     struct stat inbuf, outbuf;
10210     int status;
10211
10212     /* Any registered moves are unregistered if unregister is set, */
10213     /* i.e. invoked by the signal handler */
10214     if (unregister) {
10215         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10216             cmailMoveRegistered[i] = FALSE;
10217             if (cmailCommentList[i] != NULL) {
10218                 free(cmailCommentList[i]);
10219                 cmailCommentList[i] = NULL;
10220             }
10221         }
10222         nCmailMovesRegistered = 0;
10223     }
10224
10225     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10226         cmailResult[i] = CMAIL_NOT_RESULT;
10227     }
10228     nCmailResults = 0;
10229
10230     if (inFilename == NULL) {
10231         /* Because the filenames are static they only get malloced once  */
10232         /* and they never get freed                                      */
10233         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10234         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10235
10236         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10237         sprintf(outFilename, "%s.out", appData.cmailGameName);
10238     }
10239
10240     status = stat(outFilename, &outbuf);
10241     if (status < 0) {
10242         cmailMailedMove = FALSE;
10243     } else {
10244         status = stat(inFilename, &inbuf);
10245         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10246     }
10247
10248     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10249        counts the games, notes how each one terminated, etc.
10250
10251        It would be nice to remove this kludge and instead gather all
10252        the information while building the game list.  (And to keep it
10253        in the game list nodes instead of having a bunch of fixed-size
10254        parallel arrays.)  Note this will require getting each game's
10255        termination from the PGN tags, as the game list builder does
10256        not process the game moves.  --mann
10257        */
10258     cmailMsgLoaded = TRUE;
10259     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10260
10261     /* Load first game in the file or popup game menu */
10262     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10263
10264 #endif /* !WIN32 */
10265     return;
10266 }
10267
10268 int
10269 RegisterMove()
10270 {
10271     FILE *f;
10272     char string[MSG_SIZ];
10273
10274     if (   cmailMailedMove
10275         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10276         return TRUE;            /* Allow free viewing  */
10277     }
10278
10279     /* Unregister move to ensure that we don't leave RegisterMove        */
10280     /* with the move registered when the conditions for registering no   */
10281     /* longer hold                                                       */
10282     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10283         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10284         nCmailMovesRegistered --;
10285
10286         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10287           {
10288               free(cmailCommentList[lastLoadGameNumber - 1]);
10289               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10290           }
10291     }
10292
10293     if (cmailOldMove == -1) {
10294         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10295         return FALSE;
10296     }
10297
10298     if (currentMove > cmailOldMove + 1) {
10299         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10300         return FALSE;
10301     }
10302
10303     if (currentMove < cmailOldMove) {
10304         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10305         return FALSE;
10306     }
10307
10308     if (forwardMostMove > currentMove) {
10309         /* Silently truncate extra moves */
10310         TruncateGame();
10311     }
10312
10313     if (   (currentMove == cmailOldMove + 1)
10314         || (   (currentMove == cmailOldMove)
10315             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10316                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10317         if (gameInfo.result != GameUnfinished) {
10318             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10319         }
10320
10321         if (commentList[currentMove] != NULL) {
10322             cmailCommentList[lastLoadGameNumber - 1]
10323               = StrSave(commentList[currentMove]);
10324         }
10325         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10326
10327         if (appData.debugMode)
10328           fprintf(debugFP, "Saving %s for game %d\n",
10329                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10330
10331         sprintf(string,
10332                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10333
10334         f = fopen(string, "w");
10335         if (appData.oldSaveStyle) {
10336             SaveGameOldStyle(f); /* also closes the file */
10337
10338             sprintf(string, "%s.pos.out", appData.cmailGameName);
10339             f = fopen(string, "w");
10340             SavePosition(f, 0, NULL); /* also closes the file */
10341         } else {
10342             fprintf(f, "{--------------\n");
10343             PrintPosition(f, currentMove);
10344             fprintf(f, "--------------}\n\n");
10345
10346             SaveGame(f, 0, NULL); /* also closes the file*/
10347         }
10348
10349         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10350         nCmailMovesRegistered ++;
10351     } else if (nCmailGames == 1) {
10352         DisplayError(_("You have not made a move yet"), 0);
10353         return FALSE;
10354     }
10355
10356     return TRUE;
10357 }
10358
10359 void
10360 MailMoveEvent()
10361 {
10362 #if !WIN32
10363     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10364     FILE *commandOutput;
10365     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10366     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10367     int nBuffers;
10368     int i;
10369     int archived;
10370     char *arcDir;
10371
10372     if (! cmailMsgLoaded) {
10373         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10374         return;
10375     }
10376
10377     if (nCmailGames == nCmailResults) {
10378         DisplayError(_("No unfinished games"), 0);
10379         return;
10380     }
10381
10382 #if CMAIL_PROHIBIT_REMAIL
10383     if (cmailMailedMove) {
10384         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);
10385         DisplayError(msg, 0);
10386         return;
10387     }
10388 #endif
10389
10390     if (! (cmailMailedMove || RegisterMove())) return;
10391
10392     if (   cmailMailedMove
10393         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10394         sprintf(string, partCommandString,
10395                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10396         commandOutput = popen(string, "r");
10397
10398         if (commandOutput == NULL) {
10399             DisplayError(_("Failed to invoke cmail"), 0);
10400         } else {
10401             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10402                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10403             }
10404             if (nBuffers > 1) {
10405                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10406                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10407                 nBytes = MSG_SIZ - 1;
10408             } else {
10409                 (void) memcpy(msg, buffer, nBytes);
10410             }
10411             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10412
10413             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10414                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10415
10416                 archived = TRUE;
10417                 for (i = 0; i < nCmailGames; i ++) {
10418                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10419                         archived = FALSE;
10420                     }
10421                 }
10422                 if (   archived
10423                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10424                         != NULL)) {
10425                     sprintf(buffer, "%s/%s.%s.archive",
10426                             arcDir,
10427                             appData.cmailGameName,
10428                             gameInfo.date);
10429                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10430                     cmailMsgLoaded = FALSE;
10431                 }
10432             }
10433
10434             DisplayInformation(msg);
10435             pclose(commandOutput);
10436         }
10437     } else {
10438         if ((*cmailMsg) != '\0') {
10439             DisplayInformation(cmailMsg);
10440         }
10441     }
10442
10443     return;
10444 #endif /* !WIN32 */
10445 }
10446
10447 char *
10448 CmailMsg()
10449 {
10450 #if WIN32
10451     return NULL;
10452 #else
10453     int  prependComma = 0;
10454     char number[5];
10455     char string[MSG_SIZ];       /* Space for game-list */
10456     int  i;
10457
10458     if (!cmailMsgLoaded) return "";
10459
10460     if (cmailMailedMove) {
10461         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10462     } else {
10463         /* Create a list of games left */
10464         sprintf(string, "[");
10465         for (i = 0; i < nCmailGames; i ++) {
10466             if (! (   cmailMoveRegistered[i]
10467                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10468                 if (prependComma) {
10469                     sprintf(number, ",%d", i + 1);
10470                 } else {
10471                     sprintf(number, "%d", i + 1);
10472                     prependComma = 1;
10473                 }
10474
10475                 strcat(string, number);
10476             }
10477         }
10478         strcat(string, "]");
10479
10480         if (nCmailMovesRegistered + nCmailResults == 0) {
10481             switch (nCmailGames) {
10482               case 1:
10483                 sprintf(cmailMsg,
10484                         _("Still need to make move for game\n"));
10485                 break;
10486
10487               case 2:
10488                 sprintf(cmailMsg,
10489                         _("Still need to make moves for both games\n"));
10490                 break;
10491
10492               default:
10493                 sprintf(cmailMsg,
10494                         _("Still need to make moves for all %d games\n"),
10495                         nCmailGames);
10496                 break;
10497             }
10498         } else {
10499             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10500               case 1:
10501                 sprintf(cmailMsg,
10502                         _("Still need to make a move for game %s\n"),
10503                         string);
10504                 break;
10505
10506               case 0:
10507                 if (nCmailResults == nCmailGames) {
10508                     sprintf(cmailMsg, _("No unfinished games\n"));
10509                 } else {
10510                     sprintf(cmailMsg, _("Ready to send mail\n"));
10511                 }
10512                 break;
10513
10514               default:
10515                 sprintf(cmailMsg,
10516                         _("Still need to make moves for games %s\n"),
10517                         string);
10518             }
10519         }
10520     }
10521     return cmailMsg;
10522 #endif /* WIN32 */
10523 }
10524
10525 void
10526 ResetGameEvent()
10527 {
10528     if (gameMode == Training)
10529       SetTrainingModeOff();
10530
10531     Reset(TRUE, TRUE);
10532     cmailMsgLoaded = FALSE;
10533     if (appData.icsActive) {
10534       SendToICS(ics_prefix);
10535       SendToICS("refresh\n");
10536     }
10537 }
10538
10539 void
10540 ExitEvent(status)
10541      int status;
10542 {
10543     exiting++;
10544     if (exiting > 2) {
10545       /* Give up on clean exit */
10546       exit(status);
10547     }
10548     if (exiting > 1) {
10549       /* Keep trying for clean exit */
10550       return;
10551     }
10552
10553     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10554
10555     if (telnetISR != NULL) {
10556       RemoveInputSource(telnetISR);
10557     }
10558     if (icsPR != NoProc) {
10559       DestroyChildProcess(icsPR, TRUE);
10560     }
10561
10562     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10563     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10564
10565     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10566     /* make sure this other one finishes before killing it!                  */
10567     if(endingGame) { int count = 0;
10568         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10569         while(endingGame && count++ < 10) DoSleep(1);
10570         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10571     }
10572
10573     /* Kill off chess programs */
10574     if (first.pr != NoProc) {
10575         ExitAnalyzeMode();
10576
10577         DoSleep( appData.delayBeforeQuit );
10578         SendToProgram("quit\n", &first);
10579         DoSleep( appData.delayAfterQuit );
10580         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10581     }
10582     if (second.pr != NoProc) {
10583         DoSleep( appData.delayBeforeQuit );
10584         SendToProgram("quit\n", &second);
10585         DoSleep( appData.delayAfterQuit );
10586         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10587     }
10588     if (first.isr != NULL) {
10589         RemoveInputSource(first.isr);
10590     }
10591     if (second.isr != NULL) {
10592         RemoveInputSource(second.isr);
10593     }
10594
10595     ShutDownFrontEnd();
10596     exit(status);
10597 }
10598
10599 void
10600 PauseEvent()
10601 {
10602     if (appData.debugMode)
10603         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10604     if (pausing) {
10605         pausing = FALSE;
10606         ModeHighlight();
10607         if (gameMode == MachinePlaysWhite ||
10608             gameMode == MachinePlaysBlack) {
10609             StartClocks();
10610         } else {
10611             DisplayBothClocks();
10612         }
10613         if (gameMode == PlayFromGameFile) {
10614             if (appData.timeDelay >= 0)
10615                 AutoPlayGameLoop();
10616         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10617             Reset(FALSE, TRUE);
10618             SendToICS(ics_prefix);
10619             SendToICS("refresh\n");
10620         } else if (currentMove < forwardMostMove) {
10621             ForwardInner(forwardMostMove);
10622         }
10623         pauseExamInvalid = FALSE;
10624     } else {
10625         switch (gameMode) {
10626           default:
10627             return;
10628           case IcsExamining:
10629             pauseExamForwardMostMove = forwardMostMove;
10630             pauseExamInvalid = FALSE;
10631             /* fall through */
10632           case IcsObserving:
10633           case IcsPlayingWhite:
10634           case IcsPlayingBlack:
10635             pausing = TRUE;
10636             ModeHighlight();
10637             return;
10638           case PlayFromGameFile:
10639             (void) StopLoadGameTimer();
10640             pausing = TRUE;
10641             ModeHighlight();
10642             break;
10643           case BeginningOfGame:
10644             if (appData.icsActive) return;
10645             /* else fall through */
10646           case MachinePlaysWhite:
10647           case MachinePlaysBlack:
10648           case TwoMachinesPlay:
10649             if (forwardMostMove == 0)
10650               return;           /* don't pause if no one has moved */
10651             if ((gameMode == MachinePlaysWhite &&
10652                  !WhiteOnMove(forwardMostMove)) ||
10653                 (gameMode == MachinePlaysBlack &&
10654                  WhiteOnMove(forwardMostMove))) {
10655                 StopClocks();
10656             }
10657             pausing = TRUE;
10658             ModeHighlight();
10659             break;
10660         }
10661     }
10662 }
10663
10664 void
10665 EditCommentEvent()
10666 {
10667     char title[MSG_SIZ];
10668
10669     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10670         strcpy(title, _("Edit comment"));
10671     } else {
10672         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10673                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10674                 parseList[currentMove - 1]);
10675     }
10676
10677     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10678 }
10679
10680
10681 void
10682 EditTagsEvent()
10683 {
10684     char *tags = PGNTags(&gameInfo);
10685     EditTagsPopUp(tags);
10686     free(tags);
10687 }
10688
10689 void
10690 AnalyzeModeEvent()
10691 {
10692     if (appData.noChessProgram || gameMode == AnalyzeMode)
10693       return;
10694
10695     if (gameMode != AnalyzeFile) {
10696         if (!appData.icsEngineAnalyze) {
10697                EditGameEvent();
10698                if (gameMode != EditGame) return;
10699         }
10700         ResurrectChessProgram();
10701         SendToProgram("analyze\n", &first);
10702         first.analyzing = TRUE;
10703         /*first.maybeThinking = TRUE;*/
10704         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10705         EngineOutputPopUp();
10706     }
10707     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10708     pausing = FALSE;
10709     ModeHighlight();
10710     SetGameInfo();
10711
10712     StartAnalysisClock();
10713     GetTimeMark(&lastNodeCountTime);
10714     lastNodeCount = 0;
10715 }
10716
10717 void
10718 AnalyzeFileEvent()
10719 {
10720     if (appData.noChessProgram || gameMode == AnalyzeFile)
10721       return;
10722
10723     if (gameMode != AnalyzeMode) {
10724         EditGameEvent();
10725         if (gameMode != EditGame) return;
10726         ResurrectChessProgram();
10727         SendToProgram("analyze\n", &first);
10728         first.analyzing = TRUE;
10729         /*first.maybeThinking = TRUE;*/
10730         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10731         EngineOutputPopUp();
10732     }
10733     gameMode = AnalyzeFile;
10734     pausing = FALSE;
10735     ModeHighlight();
10736     SetGameInfo();
10737
10738     StartAnalysisClock();
10739     GetTimeMark(&lastNodeCountTime);
10740     lastNodeCount = 0;
10741 }
10742
10743 void
10744 MachineWhiteEvent()
10745 {
10746     char buf[MSG_SIZ];
10747     char *bookHit = NULL;
10748
10749     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10750       return;
10751
10752
10753     if (gameMode == PlayFromGameFile ||
10754         gameMode == TwoMachinesPlay  ||
10755         gameMode == Training         ||
10756         gameMode == AnalyzeMode      ||
10757         gameMode == EndOfGame)
10758         EditGameEvent();
10759
10760     if (gameMode == EditPosition)
10761         EditPositionDone();
10762
10763     if (!WhiteOnMove(currentMove)) {
10764         DisplayError(_("It is not White's turn"), 0);
10765         return;
10766     }
10767
10768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10769       ExitAnalyzeMode();
10770
10771     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10772         gameMode == AnalyzeFile)
10773         TruncateGame();
10774
10775     ResurrectChessProgram();    /* in case it isn't running */
10776     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10777         gameMode = MachinePlaysWhite;
10778         ResetClocks();
10779     } else
10780     gameMode = MachinePlaysWhite;
10781     pausing = FALSE;
10782     ModeHighlight();
10783     SetGameInfo();
10784     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10785     DisplayTitle(buf);
10786     if (first.sendName) {
10787       sprintf(buf, "name %s\n", gameInfo.black);
10788       SendToProgram(buf, &first);
10789     }
10790     if (first.sendTime) {
10791       if (first.useColors) {
10792         SendToProgram("black\n", &first); /*gnu kludge*/
10793       }
10794       SendTimeRemaining(&first, TRUE);
10795     }
10796     if (first.useColors) {
10797       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10798     }
10799     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10800     SetMachineThinkingEnables();
10801     first.maybeThinking = TRUE;
10802     StartClocks();
10803     firstMove = FALSE;
10804
10805     if (appData.autoFlipView && !flipView) {
10806       flipView = !flipView;
10807       DrawPosition(FALSE, NULL);
10808       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10809     }
10810
10811     if(bookHit) { // [HGM] book: simulate book reply
10812         static char bookMove[MSG_SIZ]; // a bit generous?
10813
10814         programStats.nodes = programStats.depth = programStats.time =
10815         programStats.score = programStats.got_only_move = 0;
10816         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10817
10818         strcpy(bookMove, "move ");
10819         strcat(bookMove, bookHit);
10820         HandleMachineMove(bookMove, &first);
10821     }
10822 }
10823
10824 void
10825 MachineBlackEvent()
10826 {
10827     char buf[MSG_SIZ];
10828    char *bookHit = NULL;
10829
10830     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10831         return;
10832
10833
10834     if (gameMode == PlayFromGameFile ||
10835         gameMode == TwoMachinesPlay  ||
10836         gameMode == Training         ||
10837         gameMode == AnalyzeMode      ||
10838         gameMode == EndOfGame)
10839         EditGameEvent();
10840
10841     if (gameMode == EditPosition)
10842         EditPositionDone();
10843
10844     if (WhiteOnMove(currentMove)) {
10845         DisplayError(_("It is not Black's turn"), 0);
10846         return;
10847     }
10848
10849     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10850       ExitAnalyzeMode();
10851
10852     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10853         gameMode == AnalyzeFile)
10854         TruncateGame();
10855
10856     ResurrectChessProgram();    /* in case it isn't running */
10857     gameMode = MachinePlaysBlack;
10858     pausing = FALSE;
10859     ModeHighlight();
10860     SetGameInfo();
10861     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10862     DisplayTitle(buf);
10863     if (first.sendName) {
10864       sprintf(buf, "name %s\n", gameInfo.white);
10865       SendToProgram(buf, &first);
10866     }
10867     if (first.sendTime) {
10868       if (first.useColors) {
10869         SendToProgram("white\n", &first); /*gnu kludge*/
10870       }
10871       SendTimeRemaining(&first, FALSE);
10872     }
10873     if (first.useColors) {
10874       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10875     }
10876     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10877     SetMachineThinkingEnables();
10878     first.maybeThinking = TRUE;
10879     StartClocks();
10880
10881     if (appData.autoFlipView && flipView) {
10882       flipView = !flipView;
10883       DrawPosition(FALSE, NULL);
10884       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10885     }
10886     if(bookHit) { // [HGM] book: simulate book reply
10887         static char bookMove[MSG_SIZ]; // a bit generous?
10888
10889         programStats.nodes = programStats.depth = programStats.time =
10890         programStats.score = programStats.got_only_move = 0;
10891         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10892
10893         strcpy(bookMove, "move ");
10894         strcat(bookMove, bookHit);
10895         HandleMachineMove(bookMove, &first);
10896     }
10897 }
10898
10899
10900 void
10901 DisplayTwoMachinesTitle()
10902 {
10903     char buf[MSG_SIZ];
10904     if (appData.matchGames > 0) {
10905         if (first.twoMachinesColor[0] == 'w') {
10906             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10907                     gameInfo.white, gameInfo.black,
10908                     first.matchWins, second.matchWins,
10909                     matchGame - 1 - (first.matchWins + second.matchWins));
10910         } else {
10911             sprintf(buf, "%s vs. %s (%d-%d-%d)",
10912                     gameInfo.white, gameInfo.black,
10913                     second.matchWins, first.matchWins,
10914                     matchGame - 1 - (first.matchWins + second.matchWins));
10915         }
10916     } else {
10917         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10918     }
10919     DisplayTitle(buf);
10920 }
10921
10922 void
10923 TwoMachinesEvent P((void))
10924 {
10925     int i;
10926     char buf[MSG_SIZ];
10927     ChessProgramState *onmove;
10928     char *bookHit = NULL;
10929
10930     if (appData.noChessProgram) return;
10931
10932     switch (gameMode) {
10933       case TwoMachinesPlay:
10934         return;
10935       case MachinePlaysWhite:
10936       case MachinePlaysBlack:
10937         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10938             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10939             return;
10940         }
10941         /* fall through */
10942       case BeginningOfGame:
10943       case PlayFromGameFile:
10944       case EndOfGame:
10945         EditGameEvent();
10946         if (gameMode != EditGame) return;
10947         break;
10948       case EditPosition:
10949         EditPositionDone();
10950         break;
10951       case AnalyzeMode:
10952       case AnalyzeFile:
10953         ExitAnalyzeMode();
10954         break;
10955       case EditGame:
10956       default:
10957         break;
10958     }
10959
10960     forwardMostMove = currentMove;
10961     ResurrectChessProgram();    /* in case first program isn't running */
10962
10963     if (second.pr == NULL) {
10964         StartChessProgram(&second);
10965         if (second.protocolVersion == 1) {
10966           TwoMachinesEventIfReady();
10967         } else {
10968           /* kludge: allow timeout for initial "feature" command */
10969           FreezeUI();
10970           DisplayMessage("", _("Starting second chess program"));
10971           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10972         }
10973         return;
10974     }
10975     DisplayMessage("", "");
10976     InitChessProgram(&second, FALSE);
10977     SendToProgram("force\n", &second);
10978     if (startedFromSetupPosition) {
10979         SendBoard(&second, backwardMostMove);
10980     if (appData.debugMode) {
10981         fprintf(debugFP, "Two Machines\n");
10982     }
10983     }
10984     for (i = backwardMostMove; i < forwardMostMove; i++) {
10985         SendMoveToProgram(i, &second);
10986     }
10987
10988     gameMode = TwoMachinesPlay;
10989     pausing = FALSE;
10990     ModeHighlight();
10991     SetGameInfo();
10992     DisplayTwoMachinesTitle();
10993     firstMove = TRUE;
10994     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10995         onmove = &first;
10996     } else {
10997         onmove = &second;
10998     }
10999
11000     SendToProgram(first.computerString, &first);
11001     if (first.sendName) {
11002       sprintf(buf, "name %s\n", second.tidy);
11003       SendToProgram(buf, &first);
11004     }
11005     SendToProgram(second.computerString, &second);
11006     if (second.sendName) {
11007       sprintf(buf, "name %s\n", first.tidy);
11008       SendToProgram(buf, &second);
11009     }
11010
11011     ResetClocks();
11012     if (!first.sendTime || !second.sendTime) {
11013         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11014         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11015     }
11016     if (onmove->sendTime) {
11017       if (onmove->useColors) {
11018         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11019       }
11020       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11021     }
11022     if (onmove->useColors) {
11023       SendToProgram(onmove->twoMachinesColor, onmove);
11024     }
11025     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11026 //    SendToProgram("go\n", onmove);
11027     onmove->maybeThinking = TRUE;
11028     SetMachineThinkingEnables();
11029
11030     StartClocks();
11031
11032     if(bookHit) { // [HGM] book: simulate book reply
11033         static char bookMove[MSG_SIZ]; // a bit generous?
11034
11035         programStats.nodes = programStats.depth = programStats.time =
11036         programStats.score = programStats.got_only_move = 0;
11037         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11038
11039         strcpy(bookMove, "move ");
11040         strcat(bookMove, bookHit);
11041         savedMessage = bookMove; // args for deferred call
11042         savedState = onmove;
11043         ScheduleDelayedEvent(DeferredBookMove, 1);
11044     }
11045 }
11046
11047 void
11048 TrainingEvent()
11049 {
11050     if (gameMode == Training) {
11051       SetTrainingModeOff();
11052       gameMode = PlayFromGameFile;
11053       DisplayMessage("", _("Training mode off"));
11054     } else {
11055       gameMode = Training;
11056       animateTraining = appData.animate;
11057
11058       /* make sure we are not already at the end of the game */
11059       if (currentMove < forwardMostMove) {
11060         SetTrainingModeOn();
11061         DisplayMessage("", _("Training mode on"));
11062       } else {
11063         gameMode = PlayFromGameFile;
11064         DisplayError(_("Already at end of game"), 0);
11065       }
11066     }
11067     ModeHighlight();
11068 }
11069
11070 void
11071 IcsClientEvent()
11072 {
11073     if (!appData.icsActive) return;
11074     switch (gameMode) {
11075       case IcsPlayingWhite:
11076       case IcsPlayingBlack:
11077       case IcsObserving:
11078       case IcsIdle:
11079       case BeginningOfGame:
11080       case IcsExamining:
11081         return;
11082
11083       case EditGame:
11084         break;
11085
11086       case EditPosition:
11087         EditPositionDone();
11088         break;
11089
11090       case AnalyzeMode:
11091       case AnalyzeFile:
11092         ExitAnalyzeMode();
11093         break;
11094
11095       default:
11096         EditGameEvent();
11097         break;
11098     }
11099
11100     gameMode = IcsIdle;
11101     ModeHighlight();
11102     return;
11103 }
11104
11105
11106 void
11107 EditGameEvent()
11108 {
11109     int i;
11110
11111     switch (gameMode) {
11112       case Training:
11113         SetTrainingModeOff();
11114         break;
11115       case MachinePlaysWhite:
11116       case MachinePlaysBlack:
11117       case BeginningOfGame:
11118         SendToProgram("force\n", &first);
11119         SetUserThinkingEnables();
11120         break;
11121       case PlayFromGameFile:
11122         (void) StopLoadGameTimer();
11123         if (gameFileFP != NULL) {
11124             gameFileFP = NULL;
11125         }
11126         break;
11127       case EditPosition:
11128         EditPositionDone();
11129         break;
11130       case AnalyzeMode:
11131       case AnalyzeFile:
11132         ExitAnalyzeMode();
11133         SendToProgram("force\n", &first);
11134         break;
11135       case TwoMachinesPlay:
11136         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11137         ResurrectChessProgram();
11138         SetUserThinkingEnables();
11139         break;
11140       case EndOfGame:
11141         ResurrectChessProgram();
11142         break;
11143       case IcsPlayingBlack:
11144       case IcsPlayingWhite:
11145         DisplayError(_("Warning: You are still playing a game"), 0);
11146         break;
11147       case IcsObserving:
11148         DisplayError(_("Warning: You are still observing a game"), 0);
11149         break;
11150       case IcsExamining:
11151         DisplayError(_("Warning: You are still examining a game"), 0);
11152         break;
11153       case IcsIdle:
11154         break;
11155       case EditGame:
11156       default:
11157         return;
11158     }
11159
11160     pausing = FALSE;
11161     StopClocks();
11162     first.offeredDraw = second.offeredDraw = 0;
11163
11164     if (gameMode == PlayFromGameFile) {
11165         whiteTimeRemaining = timeRemaining[0][currentMove];
11166         blackTimeRemaining = timeRemaining[1][currentMove];
11167         DisplayTitle("");
11168     }
11169
11170     if (gameMode == MachinePlaysWhite ||
11171         gameMode == MachinePlaysBlack ||
11172         gameMode == TwoMachinesPlay ||
11173         gameMode == EndOfGame) {
11174         i = forwardMostMove;
11175         while (i > currentMove) {
11176             SendToProgram("undo\n", &first);
11177             i--;
11178         }
11179         whiteTimeRemaining = timeRemaining[0][currentMove];
11180         blackTimeRemaining = timeRemaining[1][currentMove];
11181         DisplayBothClocks();
11182         if (whiteFlag || blackFlag) {
11183             whiteFlag = blackFlag = 0;
11184         }
11185         DisplayTitle("");
11186     }
11187
11188     gameMode = EditGame;
11189     ModeHighlight();
11190     SetGameInfo();
11191 }
11192
11193
11194 void
11195 EditPositionEvent()
11196 {
11197     if (gameMode == EditPosition) {
11198         EditGameEvent();
11199         return;
11200     }
11201
11202     EditGameEvent();
11203     if (gameMode != EditGame) return;
11204
11205     gameMode = EditPosition;
11206     ModeHighlight();
11207     SetGameInfo();
11208     if (currentMove > 0)
11209       CopyBoard(boards[0], boards[currentMove]);
11210
11211     blackPlaysFirst = !WhiteOnMove(currentMove);
11212     ResetClocks();
11213     currentMove = forwardMostMove = backwardMostMove = 0;
11214     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11215     DisplayMove(-1);
11216 }
11217
11218 void
11219 ExitAnalyzeMode()
11220 {
11221     /* [DM] icsEngineAnalyze - possible call from other functions */
11222     if (appData.icsEngineAnalyze) {
11223         appData.icsEngineAnalyze = FALSE;
11224
11225         DisplayMessage("",_("Close ICS engine analyze..."));
11226     }
11227     if (first.analysisSupport && first.analyzing) {
11228       SendToProgram("exit\n", &first);
11229       first.analyzing = FALSE;
11230     }
11231     thinkOutput[0] = NULLCHAR;
11232 }
11233
11234 void
11235 EditPositionDone()
11236 {
11237     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11238
11239     startedFromSetupPosition = TRUE;
11240     InitChessProgram(&first, FALSE);
11241     castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11242     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11243         castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11244         castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11245     } else castlingRights[0][2] = -1;
11246     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11247         castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11248         castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11249     } else castlingRights[0][5] = -1;
11250     SendToProgram("force\n", &first);
11251     if (blackPlaysFirst) {
11252         strcpy(moveList[0], "");
11253         strcpy(parseList[0], "");
11254         currentMove = forwardMostMove = backwardMostMove = 1;
11255         CopyBoard(boards[1], boards[0]);
11256         /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11257         { int i;
11258           epStatus[1] = epStatus[0];
11259           for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11260         }
11261     } else {
11262         currentMove = forwardMostMove = backwardMostMove = 0;
11263     }
11264     SendBoard(&first, forwardMostMove);
11265     if (appData.debugMode) {
11266         fprintf(debugFP, "EditPosDone\n");
11267     }
11268     DisplayTitle("");
11269     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11270     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11271     gameMode = EditGame;
11272     ModeHighlight();
11273     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11274     ClearHighlights(); /* [AS] */
11275 }
11276
11277 /* Pause for `ms' milliseconds */
11278 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11279 void
11280 TimeDelay(ms)
11281      long ms;
11282 {
11283     TimeMark m1, m2;
11284
11285     GetTimeMark(&m1);
11286     do {
11287         GetTimeMark(&m2);
11288     } while (SubtractTimeMarks(&m2, &m1) < ms);
11289 }
11290
11291 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11292 void
11293 SendMultiLineToICS(buf)
11294      char *buf;
11295 {
11296     char temp[MSG_SIZ+1], *p;
11297     int len;
11298
11299     len = strlen(buf);
11300     if (len > MSG_SIZ)
11301       len = MSG_SIZ;
11302
11303     strncpy(temp, buf, len);
11304     temp[len] = 0;
11305
11306     p = temp;
11307     while (*p) {
11308         if (*p == '\n' || *p == '\r')
11309           *p = ' ';
11310         ++p;
11311     }
11312
11313     strcat(temp, "\n");
11314     SendToICS(temp);
11315     SendToPlayer(temp, strlen(temp));
11316 }
11317
11318 void
11319 SetWhiteToPlayEvent()
11320 {
11321     if (gameMode == EditPosition) {
11322         blackPlaysFirst = FALSE;
11323         DisplayBothClocks();    /* works because currentMove is 0 */
11324     } else if (gameMode == IcsExamining) {
11325         SendToICS(ics_prefix);
11326         SendToICS("tomove white\n");
11327     }
11328 }
11329
11330 void
11331 SetBlackToPlayEvent()
11332 {
11333     if (gameMode == EditPosition) {
11334         blackPlaysFirst = TRUE;
11335         currentMove = 1;        /* kludge */
11336         DisplayBothClocks();
11337         currentMove = 0;
11338     } else if (gameMode == IcsExamining) {
11339         SendToICS(ics_prefix);
11340         SendToICS("tomove black\n");
11341     }
11342 }
11343
11344 void
11345 EditPositionMenuEvent(selection, x, y)
11346      ChessSquare selection;
11347      int x, y;
11348 {
11349     char buf[MSG_SIZ];
11350     ChessSquare piece = boards[0][y][x];
11351
11352     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11353
11354     switch (selection) {
11355       case ClearBoard:
11356         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11357             SendToICS(ics_prefix);
11358             SendToICS("bsetup clear\n");
11359         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11360             SendToICS(ics_prefix);
11361             SendToICS("clearboard\n");
11362         } else {
11363             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11364                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11365                 for (y = 0; y < BOARD_HEIGHT; y++) {
11366                     if (gameMode == IcsExamining) {
11367                         if (boards[currentMove][y][x] != EmptySquare) {
11368                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11369                                     AAA + x, ONE + y);
11370                             SendToICS(buf);
11371                         }
11372                     } else {
11373                         boards[0][y][x] = p;
11374                     }
11375                 }
11376             }
11377         }
11378         if (gameMode == EditPosition) {
11379             DrawPosition(FALSE, boards[0]);
11380         }
11381         break;
11382
11383       case WhitePlay:
11384         SetWhiteToPlayEvent();
11385         break;
11386
11387       case BlackPlay:
11388         SetBlackToPlayEvent();
11389         break;
11390
11391       case EmptySquare:
11392         if (gameMode == IcsExamining) {
11393             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11394             SendToICS(buf);
11395         } else {
11396             boards[0][y][x] = EmptySquare;
11397             DrawPosition(FALSE, boards[0]);
11398         }
11399         break;
11400
11401       case PromotePiece:
11402         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11403            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11404             selection = (ChessSquare) (PROMOTED piece);
11405         } else if(piece == EmptySquare) selection = WhiteSilver;
11406         else selection = (ChessSquare)((int)piece - 1);
11407         goto defaultlabel;
11408
11409       case DemotePiece:
11410         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11411            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11412             selection = (ChessSquare) (DEMOTED piece);
11413         } else if(piece == EmptySquare) selection = BlackSilver;
11414         else selection = (ChessSquare)((int)piece + 1);
11415         goto defaultlabel;
11416
11417       case WhiteQueen:
11418       case BlackQueen:
11419         if(gameInfo.variant == VariantShatranj ||
11420            gameInfo.variant == VariantXiangqi  ||
11421            gameInfo.variant == VariantCourier    )
11422             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11423         goto defaultlabel;
11424
11425       case WhiteKing:
11426       case BlackKing:
11427         if(gameInfo.variant == VariantXiangqi)
11428             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11429         if(gameInfo.variant == VariantKnightmate)
11430             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11431       default:
11432         defaultlabel:
11433         if (gameMode == IcsExamining) {
11434             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11435                     PieceToChar(selection), AAA + x, ONE + y);
11436             SendToICS(buf);
11437         } else {
11438             boards[0][y][x] = selection;
11439             DrawPosition(FALSE, boards[0]);
11440         }
11441         break;
11442     }
11443 }
11444
11445
11446 void
11447 DropMenuEvent(selection, x, y)
11448      ChessSquare selection;
11449      int x, y;
11450 {
11451     ChessMove moveType;
11452
11453     switch (gameMode) {
11454       case IcsPlayingWhite:
11455       case MachinePlaysBlack:
11456         if (!WhiteOnMove(currentMove)) {
11457             DisplayMoveError(_("It is Black's turn"));
11458             return;
11459         }
11460         moveType = WhiteDrop;
11461         break;
11462       case IcsPlayingBlack:
11463       case MachinePlaysWhite:
11464         if (WhiteOnMove(currentMove)) {
11465             DisplayMoveError(_("It is White's turn"));
11466             return;
11467         }
11468         moveType = BlackDrop;
11469         break;
11470       case EditGame:
11471         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11472         break;
11473       default:
11474         return;
11475     }
11476
11477     if (moveType == BlackDrop && selection < BlackPawn) {
11478       selection = (ChessSquare) ((int) selection
11479                                  + (int) BlackPawn - (int) WhitePawn);
11480     }
11481     if (boards[currentMove][y][x] != EmptySquare) {
11482         DisplayMoveError(_("That square is occupied"));
11483         return;
11484     }
11485
11486     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11487 }
11488
11489 void
11490 AcceptEvent()
11491 {
11492     /* Accept a pending offer of any kind from opponent */
11493
11494     if (appData.icsActive) {
11495         SendToICS(ics_prefix);
11496         SendToICS("accept\n");
11497     } else if (cmailMsgLoaded) {
11498         if (currentMove == cmailOldMove &&
11499             commentList[cmailOldMove] != NULL &&
11500             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11501                    "Black offers a draw" : "White offers a draw")) {
11502             TruncateGame();
11503             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11504             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11505         } else {
11506             DisplayError(_("There is no pending offer on this move"), 0);
11507             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11508         }
11509     } else {
11510         /* Not used for offers from chess program */
11511     }
11512 }
11513
11514 void
11515 DeclineEvent()
11516 {
11517     /* Decline a pending offer of any kind from opponent */
11518
11519     if (appData.icsActive) {
11520         SendToICS(ics_prefix);
11521         SendToICS("decline\n");
11522     } else if (cmailMsgLoaded) {
11523         if (currentMove == cmailOldMove &&
11524             commentList[cmailOldMove] != NULL &&
11525             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11526                    "Black offers a draw" : "White offers a draw")) {
11527 #ifdef NOTDEF
11528             AppendComment(cmailOldMove, "Draw declined");
11529             DisplayComment(cmailOldMove - 1, "Draw declined");
11530 #endif /*NOTDEF*/
11531         } else {
11532             DisplayError(_("There is no pending offer on this move"), 0);
11533         }
11534     } else {
11535         /* Not used for offers from chess program */
11536     }
11537 }
11538
11539 void
11540 RematchEvent()
11541 {
11542     /* Issue ICS rematch command */
11543     if (appData.icsActive) {
11544         SendToICS(ics_prefix);
11545         SendToICS("rematch\n");
11546     }
11547 }
11548
11549 void
11550 CallFlagEvent()
11551 {
11552     /* Call your opponent's flag (claim a win on time) */
11553     if (appData.icsActive) {
11554         SendToICS(ics_prefix);
11555         SendToICS("flag\n");
11556     } else {
11557         switch (gameMode) {
11558           default:
11559             return;
11560           case MachinePlaysWhite:
11561             if (whiteFlag) {
11562                 if (blackFlag)
11563                   GameEnds(GameIsDrawn, "Both players ran out of time",
11564                            GE_PLAYER);
11565                 else
11566                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11567             } else {
11568                 DisplayError(_("Your opponent is not out of time"), 0);
11569             }
11570             break;
11571           case MachinePlaysBlack:
11572             if (blackFlag) {
11573                 if (whiteFlag)
11574                   GameEnds(GameIsDrawn, "Both players ran out of time",
11575                            GE_PLAYER);
11576                 else
11577                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11578             } else {
11579                 DisplayError(_("Your opponent is not out of time"), 0);
11580             }
11581             break;
11582         }
11583     }
11584 }
11585
11586 void
11587 DrawEvent()
11588 {
11589     /* Offer draw or accept pending draw offer from opponent */
11590
11591     if (appData.icsActive) {
11592         /* Note: tournament rules require draw offers to be
11593            made after you make your move but before you punch
11594            your clock.  Currently ICS doesn't let you do that;
11595            instead, you immediately punch your clock after making
11596            a move, but you can offer a draw at any time. */
11597
11598         SendToICS(ics_prefix);
11599         SendToICS("draw\n");
11600     } else if (cmailMsgLoaded) {
11601         if (currentMove == cmailOldMove &&
11602             commentList[cmailOldMove] != NULL &&
11603             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11604                    "Black offers a draw" : "White offers a draw")) {
11605             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11606             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11607         } else if (currentMove == cmailOldMove + 1) {
11608             char *offer = WhiteOnMove(cmailOldMove) ?
11609               "White offers a draw" : "Black offers a draw";
11610             AppendComment(currentMove, offer);
11611             DisplayComment(currentMove - 1, offer);
11612             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11613         } else {
11614             DisplayError(_("You must make your move before offering a draw"), 0);
11615             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11616         }
11617     } else if (first.offeredDraw) {
11618         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11619     } else {
11620         if (first.sendDrawOffers) {
11621             SendToProgram("draw\n", &first);
11622             userOfferedDraw = TRUE;
11623         }
11624     }
11625 }
11626
11627 void
11628 AdjournEvent()
11629 {
11630     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11631
11632     if (appData.icsActive) {
11633         SendToICS(ics_prefix);
11634         SendToICS("adjourn\n");
11635     } else {
11636         /* Currently GNU Chess doesn't offer or accept Adjourns */
11637     }
11638 }
11639
11640
11641 void
11642 AbortEvent()
11643 {
11644     /* Offer Abort or accept pending Abort offer from opponent */
11645
11646     if (appData.icsActive) {
11647         SendToICS(ics_prefix);
11648         SendToICS("abort\n");
11649     } else {
11650         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11651     }
11652 }
11653
11654 void
11655 ResignEvent()
11656 {
11657     /* Resign.  You can do this even if it's not your turn. */
11658
11659     if (appData.icsActive) {
11660         SendToICS(ics_prefix);
11661         SendToICS("resign\n");
11662     } else {
11663         switch (gameMode) {
11664           case MachinePlaysWhite:
11665             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11666             break;
11667           case MachinePlaysBlack:
11668             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11669             break;
11670           case EditGame:
11671             if (cmailMsgLoaded) {
11672                 TruncateGame();
11673                 if (WhiteOnMove(cmailOldMove)) {
11674                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11675                 } else {
11676                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11677                 }
11678                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11679             }
11680             break;
11681           default:
11682             break;
11683         }
11684     }
11685 }
11686
11687
11688 void
11689 StopObservingEvent()
11690 {
11691     /* Stop observing current games */
11692     SendToICS(ics_prefix);
11693     SendToICS("unobserve\n");
11694 }
11695
11696 void
11697 StopExaminingEvent()
11698 {
11699     /* Stop observing current game */
11700     SendToICS(ics_prefix);
11701     SendToICS("unexamine\n");
11702 }
11703
11704 void
11705 ForwardInner(target)
11706      int target;
11707 {
11708     int limit;
11709
11710     if (appData.debugMode)
11711         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11712                 target, currentMove, forwardMostMove);
11713
11714     if (gameMode == EditPosition)
11715       return;
11716
11717     if (gameMode == PlayFromGameFile && !pausing)
11718       PauseEvent();
11719
11720     if (gameMode == IcsExamining && pausing)
11721       limit = pauseExamForwardMostMove;
11722     else
11723       limit = forwardMostMove;
11724
11725     if (target > limit) target = limit;
11726
11727     if (target > 0 && moveList[target - 1][0]) {
11728         int fromX, fromY, toX, toY;
11729         toX = moveList[target - 1][2] - AAA;
11730         toY = moveList[target - 1][3] - ONE;
11731         if (moveList[target - 1][1] == '@') {
11732             if (appData.highlightLastMove) {
11733                 SetHighlights(-1, -1, toX, toY);
11734             }
11735         } else {
11736             fromX = moveList[target - 1][0] - AAA;
11737             fromY = moveList[target - 1][1] - ONE;
11738             if (target == currentMove + 1) {
11739                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11740             }
11741             if (appData.highlightLastMove) {
11742                 SetHighlights(fromX, fromY, toX, toY);
11743             }
11744         }
11745     }
11746     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11747         gameMode == Training || gameMode == PlayFromGameFile ||
11748         gameMode == AnalyzeFile) {
11749         while (currentMove < target) {
11750             SendMoveToProgram(currentMove++, &first);
11751         }
11752     } else {
11753         currentMove = target;
11754     }
11755
11756     if (gameMode == EditGame || gameMode == EndOfGame) {
11757         whiteTimeRemaining = timeRemaining[0][currentMove];
11758         blackTimeRemaining = timeRemaining[1][currentMove];
11759     }
11760     DisplayBothClocks();
11761     DisplayMove(currentMove - 1);
11762     DrawPosition(FALSE, boards[currentMove]);
11763     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11764     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11765         DisplayComment(currentMove - 1, commentList[currentMove]);
11766     }
11767 }
11768
11769
11770 void
11771 ForwardEvent()
11772 {
11773     if (gameMode == IcsExamining && !pausing) {
11774         SendToICS(ics_prefix);
11775         SendToICS("forward\n");
11776     } else {
11777         ForwardInner(currentMove + 1);
11778     }
11779 }
11780
11781 void
11782 ToEndEvent()
11783 {
11784     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11785         /* to optimze, we temporarily turn off analysis mode while we feed
11786          * the remaining moves to the engine. Otherwise we get analysis output
11787          * after each move.
11788          */
11789         if (first.analysisSupport) {
11790           SendToProgram("exit\nforce\n", &first);
11791           first.analyzing = FALSE;
11792         }
11793     }
11794
11795     if (gameMode == IcsExamining && !pausing) {
11796         SendToICS(ics_prefix);
11797         SendToICS("forward 999999\n");
11798     } else {
11799         ForwardInner(forwardMostMove);
11800     }
11801
11802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11803         /* we have fed all the moves, so reactivate analysis mode */
11804         SendToProgram("analyze\n", &first);
11805         first.analyzing = TRUE;
11806         /*first.maybeThinking = TRUE;*/
11807         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11808     }
11809 }
11810
11811 void
11812 BackwardInner(target)
11813      int target;
11814 {
11815     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11816
11817     if (appData.debugMode)
11818         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11819                 target, currentMove, forwardMostMove);
11820
11821     if (gameMode == EditPosition) return;
11822     if (currentMove <= backwardMostMove) {
11823         ClearHighlights();
11824         DrawPosition(full_redraw, boards[currentMove]);
11825         return;
11826     }
11827     if (gameMode == PlayFromGameFile && !pausing)
11828       PauseEvent();
11829
11830     if (moveList[target][0]) {
11831         int fromX, fromY, toX, toY;
11832         toX = moveList[target][2] - AAA;
11833         toY = moveList[target][3] - ONE;
11834         if (moveList[target][1] == '@') {
11835             if (appData.highlightLastMove) {
11836                 SetHighlights(-1, -1, toX, toY);
11837             }
11838         } else {
11839             fromX = moveList[target][0] - AAA;
11840             fromY = moveList[target][1] - ONE;
11841             if (target == currentMove - 1) {
11842                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11843             }
11844             if (appData.highlightLastMove) {
11845                 SetHighlights(fromX, fromY, toX, toY);
11846             }
11847         }
11848     }
11849     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11850         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11851         while (currentMove > target) {
11852             SendToProgram("undo\n", &first);
11853             currentMove--;
11854         }
11855     } else {
11856         currentMove = target;
11857     }
11858
11859     if (gameMode == EditGame || gameMode == EndOfGame) {
11860         whiteTimeRemaining = timeRemaining[0][currentMove];
11861         blackTimeRemaining = timeRemaining[1][currentMove];
11862     }
11863     DisplayBothClocks();
11864     DisplayMove(currentMove - 1);
11865     DrawPosition(full_redraw, boards[currentMove]);
11866     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11867     // [HGM] PV info: routine tests if comment empty
11868     DisplayComment(currentMove - 1, commentList[currentMove]);
11869 }
11870
11871 void
11872 BackwardEvent()
11873 {
11874     if (gameMode == IcsExamining && !pausing) {
11875         SendToICS(ics_prefix);
11876         SendToICS("backward\n");
11877     } else {
11878         BackwardInner(currentMove - 1);
11879     }
11880 }
11881
11882 void
11883 ToStartEvent()
11884 {
11885     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11886         /* to optimze, we temporarily turn off analysis mode while we undo
11887          * all the moves. Otherwise we get analysis output after each undo.
11888          */
11889         if (first.analysisSupport) {
11890           SendToProgram("exit\nforce\n", &first);
11891           first.analyzing = FALSE;
11892         }
11893     }
11894
11895     if (gameMode == IcsExamining && !pausing) {
11896         SendToICS(ics_prefix);
11897         SendToICS("backward 999999\n");
11898     } else {
11899         BackwardInner(backwardMostMove);
11900     }
11901
11902     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11903         /* we have fed all the moves, so reactivate analysis mode */
11904         SendToProgram("analyze\n", &first);
11905         first.analyzing = TRUE;
11906         /*first.maybeThinking = TRUE;*/
11907         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11908     }
11909 }
11910
11911 void
11912 ToNrEvent(int to)
11913 {
11914   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11915   if (to >= forwardMostMove) to = forwardMostMove;
11916   if (to <= backwardMostMove) to = backwardMostMove;
11917   if (to < currentMove) {
11918     BackwardInner(to);
11919   } else {
11920     ForwardInner(to);
11921   }
11922 }
11923
11924 void
11925 RevertEvent()
11926 {
11927     if (gameMode != IcsExamining) {
11928         DisplayError(_("You are not examining a game"), 0);
11929         return;
11930     }
11931     if (pausing) {
11932         DisplayError(_("You can't revert while pausing"), 0);
11933         return;
11934     }
11935     SendToICS(ics_prefix);
11936     SendToICS("revert\n");
11937 }
11938
11939 void
11940 RetractMoveEvent()
11941 {
11942     switch (gameMode) {
11943       case MachinePlaysWhite:
11944       case MachinePlaysBlack:
11945         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11946             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11947             return;
11948         }
11949         if (forwardMostMove < 2) return;
11950         currentMove = forwardMostMove = forwardMostMove - 2;
11951         whiteTimeRemaining = timeRemaining[0][currentMove];
11952         blackTimeRemaining = timeRemaining[1][currentMove];
11953         DisplayBothClocks();
11954         DisplayMove(currentMove - 1);
11955         ClearHighlights();/*!! could figure this out*/
11956         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11957         SendToProgram("remove\n", &first);
11958         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11959         break;
11960
11961       case BeginningOfGame:
11962       default:
11963         break;
11964
11965       case IcsPlayingWhite:
11966       case IcsPlayingBlack:
11967         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11968             SendToICS(ics_prefix);
11969             SendToICS("takeback 2\n");
11970         } else {
11971             SendToICS(ics_prefix);
11972             SendToICS("takeback 1\n");
11973         }
11974         break;
11975     }
11976 }
11977
11978 void
11979 MoveNowEvent()
11980 {
11981     ChessProgramState *cps;
11982
11983     switch (gameMode) {
11984       case MachinePlaysWhite:
11985         if (!WhiteOnMove(forwardMostMove)) {
11986             DisplayError(_("It is your turn"), 0);
11987             return;
11988         }
11989         cps = &first;
11990         break;
11991       case MachinePlaysBlack:
11992         if (WhiteOnMove(forwardMostMove)) {
11993             DisplayError(_("It is your turn"), 0);
11994             return;
11995         }
11996         cps = &first;
11997         break;
11998       case TwoMachinesPlay:
11999         if (WhiteOnMove(forwardMostMove) ==
12000             (first.twoMachinesColor[0] == 'w')) {
12001             cps = &first;
12002         } else {
12003             cps = &second;
12004         }
12005         break;
12006       case BeginningOfGame:
12007       default:
12008         return;
12009     }
12010     SendToProgram("?\n", cps);
12011 }
12012
12013 void
12014 TruncateGameEvent()
12015 {
12016     EditGameEvent();
12017     if (gameMode != EditGame) return;
12018     TruncateGame();
12019 }
12020
12021 void
12022 TruncateGame()
12023 {
12024     if (forwardMostMove > currentMove) {
12025         if (gameInfo.resultDetails != NULL) {
12026             free(gameInfo.resultDetails);
12027             gameInfo.resultDetails = NULL;
12028             gameInfo.result = GameUnfinished;
12029         }
12030         forwardMostMove = currentMove;
12031         HistorySet(parseList, backwardMostMove, forwardMostMove,
12032                    currentMove-1);
12033     }
12034 }
12035
12036 void
12037 HintEvent()
12038 {
12039     if (appData.noChessProgram) return;
12040     switch (gameMode) {
12041       case MachinePlaysWhite:
12042         if (WhiteOnMove(forwardMostMove)) {
12043             DisplayError(_("Wait until your turn"), 0);
12044             return;
12045         }
12046         break;
12047       case BeginningOfGame:
12048       case MachinePlaysBlack:
12049         if (!WhiteOnMove(forwardMostMove)) {
12050             DisplayError(_("Wait until your turn"), 0);
12051             return;
12052         }
12053         break;
12054       default:
12055         DisplayError(_("No hint available"), 0);
12056         return;
12057     }
12058     SendToProgram("hint\n", &first);
12059     hintRequested = TRUE;
12060 }
12061
12062 void
12063 BookEvent()
12064 {
12065     if (appData.noChessProgram) return;
12066     switch (gameMode) {
12067       case MachinePlaysWhite:
12068         if (WhiteOnMove(forwardMostMove)) {
12069             DisplayError(_("Wait until your turn"), 0);
12070             return;
12071         }
12072         break;
12073       case BeginningOfGame:
12074       case MachinePlaysBlack:
12075         if (!WhiteOnMove(forwardMostMove)) {
12076             DisplayError(_("Wait until your turn"), 0);
12077             return;
12078         }
12079         break;
12080       case EditPosition:
12081         EditPositionDone();
12082         break;
12083       case TwoMachinesPlay:
12084         return;
12085       default:
12086         break;
12087     }
12088     SendToProgram("bk\n", &first);
12089     bookOutput[0] = NULLCHAR;
12090     bookRequested = TRUE;
12091 }
12092
12093 void
12094 AboutGameEvent()
12095 {
12096     char *tags = PGNTags(&gameInfo);
12097     TagsPopUp(tags, CmailMsg());
12098     free(tags);
12099 }
12100
12101 /* end button procedures */
12102
12103 void
12104 PrintPosition(fp, move)
12105      FILE *fp;
12106      int move;
12107 {
12108     int i, j;
12109
12110     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12111         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12112             char c = PieceToChar(boards[move][i][j]);
12113             fputc(c == 'x' ? '.' : c, fp);
12114             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12115         }
12116     }
12117     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12118       fprintf(fp, "white to play\n");
12119     else
12120       fprintf(fp, "black to play\n");
12121 }
12122
12123 void
12124 PrintOpponents(fp)
12125      FILE *fp;
12126 {
12127     if (gameInfo.white != NULL) {
12128         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12129     } else {
12130         fprintf(fp, "\n");
12131     }
12132 }
12133
12134 /* Find last component of program's own name, using some heuristics */
12135 void
12136 TidyProgramName(prog, host, buf)
12137      char *prog, *host, buf[MSG_SIZ];
12138 {
12139     char *p, *q;
12140     int local = (strcmp(host, "localhost") == 0);
12141     while (!local && (p = strchr(prog, ';')) != NULL) {
12142         p++;
12143         while (*p == ' ') p++;
12144         prog = p;
12145     }
12146     if (*prog == '"' || *prog == '\'') {
12147         q = strchr(prog + 1, *prog);
12148     } else {
12149         q = strchr(prog, ' ');
12150     }
12151     if (q == NULL) q = prog + strlen(prog);
12152     p = q;
12153     while (p >= prog && *p != '/' && *p != '\\') p--;
12154     p++;
12155     if(p == prog && *p == '"') p++;
12156     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12157     memcpy(buf, p, q - p);
12158     buf[q - p] = NULLCHAR;
12159     if (!local) {
12160         strcat(buf, "@");
12161         strcat(buf, host);
12162     }
12163 }
12164
12165 char *
12166 TimeControlTagValue()
12167 {
12168     char buf[MSG_SIZ];
12169     if (!appData.clockMode) {
12170         strcpy(buf, "-");
12171     } else if (movesPerSession > 0) {
12172         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12173     } else if (timeIncrement == 0) {
12174         sprintf(buf, "%ld", timeControl/1000);
12175     } else {
12176         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12177     }
12178     return StrSave(buf);
12179 }
12180
12181 void
12182 SetGameInfo()
12183 {
12184     /* This routine is used only for certain modes */
12185     VariantClass v = gameInfo.variant;
12186     ClearGameInfo(&gameInfo);
12187     gameInfo.variant = v;
12188
12189     switch (gameMode) {
12190       case MachinePlaysWhite:
12191         gameInfo.event = StrSave( appData.pgnEventHeader );
12192         gameInfo.site = StrSave(HostName());
12193         gameInfo.date = PGNDate();
12194         gameInfo.round = StrSave("-");
12195         gameInfo.white = StrSave(first.tidy);
12196         gameInfo.black = StrSave(UserName());
12197         gameInfo.timeControl = TimeControlTagValue();
12198         break;
12199
12200       case MachinePlaysBlack:
12201         gameInfo.event = StrSave( appData.pgnEventHeader );
12202         gameInfo.site = StrSave(HostName());
12203         gameInfo.date = PGNDate();
12204         gameInfo.round = StrSave("-");
12205         gameInfo.white = StrSave(UserName());
12206         gameInfo.black = StrSave(first.tidy);
12207         gameInfo.timeControl = TimeControlTagValue();
12208         break;
12209
12210       case TwoMachinesPlay:
12211         gameInfo.event = StrSave( appData.pgnEventHeader );
12212         gameInfo.site = StrSave(HostName());
12213         gameInfo.date = PGNDate();
12214         if (matchGame > 0) {
12215             char buf[MSG_SIZ];
12216             sprintf(buf, "%d", matchGame);
12217             gameInfo.round = StrSave(buf);
12218         } else {
12219             gameInfo.round = StrSave("-");
12220         }
12221         if (first.twoMachinesColor[0] == 'w') {
12222             gameInfo.white = StrSave(first.tidy);
12223             gameInfo.black = StrSave(second.tidy);
12224         } else {
12225             gameInfo.white = StrSave(second.tidy);
12226             gameInfo.black = StrSave(first.tidy);
12227         }
12228         gameInfo.timeControl = TimeControlTagValue();
12229         break;
12230
12231       case EditGame:
12232         gameInfo.event = StrSave("Edited game");
12233         gameInfo.site = StrSave(HostName());
12234         gameInfo.date = PGNDate();
12235         gameInfo.round = StrSave("-");
12236         gameInfo.white = StrSave("-");
12237         gameInfo.black = StrSave("-");
12238         break;
12239
12240       case EditPosition:
12241         gameInfo.event = StrSave("Edited position");
12242         gameInfo.site = StrSave(HostName());
12243         gameInfo.date = PGNDate();
12244         gameInfo.round = StrSave("-");
12245         gameInfo.white = StrSave("-");
12246         gameInfo.black = StrSave("-");
12247         break;
12248
12249       case IcsPlayingWhite:
12250       case IcsPlayingBlack:
12251       case IcsObserving:
12252       case IcsExamining:
12253         break;
12254
12255       case PlayFromGameFile:
12256         gameInfo.event = StrSave("Game from non-PGN file");
12257         gameInfo.site = StrSave(HostName());
12258         gameInfo.date = PGNDate();
12259         gameInfo.round = StrSave("-");
12260         gameInfo.white = StrSave("?");
12261         gameInfo.black = StrSave("?");
12262         break;
12263
12264       default:
12265         break;
12266     }
12267 }
12268
12269 void
12270 ReplaceComment(index, text)
12271      int index;
12272      char *text;
12273 {
12274     int len;
12275
12276     while (*text == '\n') text++;
12277     len = strlen(text);
12278     while (len > 0 && text[len - 1] == '\n') len--;
12279
12280     if (commentList[index] != NULL)
12281       free(commentList[index]);
12282
12283     if (len == 0) {
12284         commentList[index] = NULL;
12285         return;
12286     }
12287     commentList[index] = (char *) malloc(len + 2);
12288     strncpy(commentList[index], text, len);
12289     commentList[index][len] = '\n';
12290     commentList[index][len + 1] = NULLCHAR;
12291 }
12292
12293 void
12294 CrushCRs(text)
12295      char *text;
12296 {
12297   char *p = text;
12298   char *q = text;
12299   char ch;
12300
12301   do {
12302     ch = *p++;
12303     if (ch == '\r') continue;
12304     *q++ = ch;
12305   } while (ch != '\0');
12306 }
12307
12308 void
12309 AppendComment(index, text)
12310      int index;
12311      char *text;
12312 {
12313     int oldlen, len;
12314     char *old;
12315
12316     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12317
12318     CrushCRs(text);
12319     while (*text == '\n') text++;
12320     len = strlen(text);
12321     while (len > 0 && text[len - 1] == '\n') len--;
12322
12323     if (len == 0) return;
12324
12325     if (commentList[index] != NULL) {
12326         old = commentList[index];
12327         oldlen = strlen(old);
12328         commentList[index] = (char *) malloc(oldlen + len + 2);
12329         strcpy(commentList[index], old);
12330         free(old);
12331         strncpy(&commentList[index][oldlen], text, len);
12332         commentList[index][oldlen + len] = '\n';
12333         commentList[index][oldlen + len + 1] = NULLCHAR;
12334     } else {
12335         commentList[index] = (char *) malloc(len + 2);
12336         strncpy(commentList[index], text, len);
12337         commentList[index][len] = '\n';
12338         commentList[index][len + 1] = NULLCHAR;
12339     }
12340 }
12341
12342 static char * FindStr( char * text, char * sub_text )
12343 {
12344     char * result = strstr( text, sub_text );
12345
12346     if( result != NULL ) {
12347         result += strlen( sub_text );
12348     }
12349
12350     return result;
12351 }
12352
12353 /* [AS] Try to extract PV info from PGN comment */
12354 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12355 char *GetInfoFromComment( int index, char * text )
12356 {
12357     char * sep = text;
12358
12359     if( text != NULL && index > 0 ) {
12360         int score = 0;
12361         int depth = 0;
12362         int time = -1, sec = 0, deci;
12363         char * s_eval = FindStr( text, "[%eval " );
12364         char * s_emt = FindStr( text, "[%emt " );
12365
12366         if( s_eval != NULL || s_emt != NULL ) {
12367             /* New style */
12368             char delim;
12369
12370             if( s_eval != NULL ) {
12371                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12372                     return text;
12373                 }
12374
12375                 if( delim != ']' ) {
12376                     return text;
12377                 }
12378             }
12379
12380             if( s_emt != NULL ) {
12381             }
12382         }
12383         else {
12384             /* We expect something like: [+|-]nnn.nn/dd */
12385             int score_lo = 0;
12386
12387             sep = strchr( text, '/' );
12388             if( sep == NULL || sep < (text+4) ) {
12389                 return text;
12390             }
12391
12392             time = -1; sec = -1; deci = -1;
12393             if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12394                 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12395                 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12396                 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12397                 return text;
12398             }
12399
12400             if( score_lo < 0 || score_lo >= 100 ) {
12401                 return text;
12402             }
12403
12404             if(sec >= 0) time = 600*time + 10*sec; else
12405             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12406
12407             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12408
12409             /* [HGM] PV time: now locate end of PV info */
12410             while( *++sep >= '0' && *sep <= '9'); // strip depth
12411             if(time >= 0)
12412             while( *++sep >= '0' && *sep <= '9'); // strip time
12413             if(sec >= 0)
12414             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12415             if(deci >= 0)
12416             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12417             while(*sep == ' ') sep++;
12418         }
12419
12420         if( depth <= 0 ) {
12421             return text;
12422         }
12423
12424         if( time < 0 ) {
12425             time = -1;
12426         }
12427
12428         pvInfoList[index-1].depth = depth;
12429         pvInfoList[index-1].score = score;
12430         pvInfoList[index-1].time  = 10*time; // centi-sec
12431     }
12432     return sep;
12433 }
12434
12435 void
12436 SendToProgram(message, cps)
12437      char *message;
12438      ChessProgramState *cps;
12439 {
12440     int count, outCount, error;
12441     char buf[MSG_SIZ];
12442
12443     if (cps->pr == NULL) return;
12444     Attention(cps);
12445
12446     if (appData.debugMode) {
12447         TimeMark now;
12448         GetTimeMark(&now);
12449         fprintf(debugFP, "%ld >%-6s: %s",
12450                 SubtractTimeMarks(&now, &programStartTime),
12451                 cps->which, message);
12452     }
12453
12454     count = strlen(message);
12455     outCount = OutputToProcess(cps->pr, message, count, &error);
12456     if (outCount < count && !exiting
12457                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12458         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12459         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12460             if(epStatus[forwardMostMove] <= EP_DRAWS) {
12461                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12462                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12463             } else {
12464                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12465             }
12466             gameInfo.resultDetails = buf;
12467         }
12468         DisplayFatalError(buf, error, 1);
12469     }
12470 }
12471
12472 void
12473 ReceiveFromProgram(isr, closure, message, count, error)
12474      InputSourceRef isr;
12475      VOIDSTAR closure;
12476      char *message;
12477      int count;
12478      int error;
12479 {
12480     char *end_str;
12481     char buf[MSG_SIZ];
12482     ChessProgramState *cps = (ChessProgramState *)closure;
12483
12484     if (isr != cps->isr) return; /* Killed intentionally */
12485     if (count <= 0) {
12486         if (count == 0) {
12487             sprintf(buf,
12488                     _("Error: %s chess program (%s) exited unexpectedly"),
12489                     cps->which, cps->program);
12490         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12491                 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12492                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12493                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12494                 } else {
12495                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12496                 }
12497                 gameInfo.resultDetails = buf;
12498             }
12499             RemoveInputSource(cps->isr);
12500             DisplayFatalError(buf, 0, 1);
12501         } else {
12502             sprintf(buf,
12503                     _("Error reading from %s chess program (%s)"),
12504                     cps->which, cps->program);
12505             RemoveInputSource(cps->isr);
12506
12507             /* [AS] Program is misbehaving badly... kill it */
12508             if( count == -2 ) {
12509                 DestroyChildProcess( cps->pr, 9 );
12510                 cps->pr = NoProc;
12511             }
12512
12513             DisplayFatalError(buf, error, 1);
12514         }
12515         return;
12516     }
12517
12518     if ((end_str = strchr(message, '\r')) != NULL)
12519       *end_str = NULLCHAR;
12520     if ((end_str = strchr(message, '\n')) != NULL)
12521       *end_str = NULLCHAR;
12522
12523     if (appData.debugMode) {
12524         TimeMark now; int print = 1;
12525         char *quote = ""; char c; int i;
12526
12527         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12528                 char start = message[0];
12529                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12530                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12531                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12532                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12533                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12534                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12535                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12536                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12537                         { quote = "# "; print = (appData.engineComments == 2); }
12538                 message[0] = start; // restore original message
12539         }
12540         if(print) {
12541                 GetTimeMark(&now);
12542                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12543                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12544                         quote,
12545                         message);
12546         }
12547     }
12548
12549     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12550     if (appData.icsEngineAnalyze) {
12551         if (strstr(message, "whisper") != NULL ||
12552              strstr(message, "kibitz") != NULL ||
12553             strstr(message, "tellics") != NULL) return;
12554     }
12555
12556     HandleMachineMove(message, cps);
12557 }
12558
12559
12560 void
12561 SendTimeControl(cps, mps, tc, inc, sd, st)
12562      ChessProgramState *cps;
12563      int mps, inc, sd, st;
12564      long tc;
12565 {
12566     char buf[MSG_SIZ];
12567     int seconds;
12568
12569     if( timeControl_2 > 0 ) {
12570         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12571             tc = timeControl_2;
12572         }
12573     }
12574     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12575     inc /= cps->timeOdds;
12576     st  /= cps->timeOdds;
12577
12578     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12579
12580     if (st > 0) {
12581       /* Set exact time per move, normally using st command */
12582       if (cps->stKludge) {
12583         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12584         seconds = st % 60;
12585         if (seconds == 0) {
12586           sprintf(buf, "level 1 %d\n", st/60);
12587         } else {
12588           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12589         }
12590       } else {
12591         sprintf(buf, "st %d\n", st);
12592       }
12593     } else {
12594       /* Set conventional or incremental time control, using level command */
12595       if (seconds == 0) {
12596         /* Note old gnuchess bug -- minutes:seconds used to not work.
12597            Fixed in later versions, but still avoid :seconds
12598            when seconds is 0. */
12599         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12600       } else {
12601         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12602                 seconds, inc/1000);
12603       }
12604     }
12605     SendToProgram(buf, cps);
12606
12607     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12608     /* Orthogonally, limit search to given depth */
12609     if (sd > 0) {
12610       if (cps->sdKludge) {
12611         sprintf(buf, "depth\n%d\n", sd);
12612       } else {
12613         sprintf(buf, "sd %d\n", sd);
12614       }
12615       SendToProgram(buf, cps);
12616     }
12617
12618     if(cps->nps > 0) { /* [HGM] nps */
12619         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12620         else {
12621                 sprintf(buf, "nps %d\n", cps->nps);
12622               SendToProgram(buf, cps);
12623         }
12624     }
12625 }
12626
12627 ChessProgramState *WhitePlayer()
12628 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12629 {
12630     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12631        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12632         return &second;
12633     return &first;
12634 }
12635
12636 void
12637 SendTimeRemaining(cps, machineWhite)
12638      ChessProgramState *cps;
12639      int /*boolean*/ machineWhite;
12640 {
12641     char message[MSG_SIZ];
12642     long time, otime;
12643
12644     /* Note: this routine must be called when the clocks are stopped
12645        or when they have *just* been set or switched; otherwise
12646        it will be off by the time since the current tick started.
12647     */
12648     if (machineWhite) {
12649         time = whiteTimeRemaining / 10;
12650         otime = blackTimeRemaining / 10;
12651     } else {
12652         time = blackTimeRemaining / 10;
12653         otime = whiteTimeRemaining / 10;
12654     }
12655     /* [HGM] translate opponent's time by time-odds factor */
12656     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12657     if (appData.debugMode) {
12658         fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12659     }
12660
12661     if (time <= 0) time = 1;
12662     if (otime <= 0) otime = 1;
12663
12664     sprintf(message, "time %ld\n", time);
12665     SendToProgram(message, cps);
12666
12667     sprintf(message, "otim %ld\n", otime);
12668     SendToProgram(message, cps);
12669 }
12670
12671 int
12672 BoolFeature(p, name, loc, cps)
12673      char **p;
12674      char *name;
12675      int *loc;
12676      ChessProgramState *cps;
12677 {
12678   char buf[MSG_SIZ];
12679   int len = strlen(name);
12680   int val;
12681   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12682     (*p) += len + 1;
12683     sscanf(*p, "%d", &val);
12684     *loc = (val != 0);
12685     while (**p && **p != ' ') (*p)++;
12686     sprintf(buf, "accepted %s\n", name);
12687     SendToProgram(buf, cps);
12688     return TRUE;
12689   }
12690   return FALSE;
12691 }
12692
12693 int
12694 IntFeature(p, name, loc, cps)
12695      char **p;
12696      char *name;
12697      int *loc;
12698      ChessProgramState *cps;
12699 {
12700   char buf[MSG_SIZ];
12701   int len = strlen(name);
12702   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12703     (*p) += len + 1;
12704     sscanf(*p, "%d", loc);
12705     while (**p && **p != ' ') (*p)++;
12706     sprintf(buf, "accepted %s\n", name);
12707     SendToProgram(buf, cps);
12708     return TRUE;
12709   }
12710   return FALSE;
12711 }
12712
12713 int
12714 StringFeature(p, name, loc, cps)
12715      char **p;
12716      char *name;
12717      char loc[];
12718      ChessProgramState *cps;
12719 {
12720   char buf[MSG_SIZ];
12721   int len = strlen(name);
12722   if (strncmp((*p), name, len) == 0
12723       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12724     (*p) += len + 2;
12725     sscanf(*p, "%[^\"]", loc);
12726     while (**p && **p != '\"') (*p)++;
12727     if (**p == '\"') (*p)++;
12728     sprintf(buf, "accepted %s\n", name);
12729     SendToProgram(buf, cps);
12730     return TRUE;
12731   }
12732   return FALSE;
12733 }
12734
12735 int
12736 ParseOption(Option *opt, ChessProgramState *cps)
12737 // [HGM] options: process the string that defines an engine option, and determine
12738 // name, type, default value, and allowed value range
12739 {
12740         char *p, *q, buf[MSG_SIZ];
12741         int n, min = (-1)<<31, max = 1<<31, def;
12742
12743         if(p = strstr(opt->name, " -spin ")) {
12744             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12745             if(max < min) max = min; // enforce consistency
12746             if(def < min) def = min;
12747             if(def > max) def = max;
12748             opt->value = def;
12749             opt->min = min;
12750             opt->max = max;
12751             opt->type = Spin;
12752         } else if((p = strstr(opt->name, " -slider "))) {
12753             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12754             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12755             if(max < min) max = min; // enforce consistency
12756             if(def < min) def = min;
12757             if(def > max) def = max;
12758             opt->value = def;
12759             opt->min = min;
12760             opt->max = max;
12761             opt->type = Spin; // Slider;
12762         } else if((p = strstr(opt->name, " -string "))) {
12763             opt->textValue = p+9;
12764             opt->type = TextBox;
12765         } else if((p = strstr(opt->name, " -file "))) {
12766             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12767             opt->textValue = p+7;
12768             opt->type = TextBox; // FileName;
12769         } else if((p = strstr(opt->name, " -path "))) {
12770             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12771             opt->textValue = p+7;
12772             opt->type = TextBox; // PathName;
12773         } else if(p = strstr(opt->name, " -check ")) {
12774             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12775             opt->value = (def != 0);
12776             opt->type = CheckBox;
12777         } else if(p = strstr(opt->name, " -combo ")) {
12778             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12779             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12780             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12781             opt->value = n = 0;
12782             while(q = StrStr(q, " /// ")) {
12783                 n++; *q = 0;    // count choices, and null-terminate each of them
12784                 q += 5;
12785                 if(*q == '*') { // remember default, which is marked with * prefix
12786                     q++;
12787                     opt->value = n;
12788                 }
12789                 cps->comboList[cps->comboCnt++] = q;
12790             }
12791             cps->comboList[cps->comboCnt++] = NULL;
12792             opt->max = n + 1;
12793             opt->type = ComboBox;
12794         } else if(p = strstr(opt->name, " -button")) {
12795             opt->type = Button;
12796         } else if(p = strstr(opt->name, " -save")) {
12797             opt->type = SaveButton;
12798         } else return FALSE;
12799         *p = 0; // terminate option name
12800         // now look if the command-line options define a setting for this engine option.
12801         if(cps->optionSettings && cps->optionSettings[0])
12802             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12803         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12804                 sprintf(buf, "option %s", p);
12805                 if(p = strstr(buf, ",")) *p = 0;
12806                 strcat(buf, "\n");
12807                 SendToProgram(buf, cps);
12808         }
12809         return TRUE;
12810 }
12811
12812 void
12813 FeatureDone(cps, val)
12814      ChessProgramState* cps;
12815      int val;
12816 {
12817   DelayedEventCallback cb = GetDelayedEvent();
12818   if ((cb == InitBackEnd3 && cps == &first) ||
12819       (cb == TwoMachinesEventIfReady && cps == &second)) {
12820     CancelDelayedEvent();
12821     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12822   }
12823   cps->initDone = val;
12824 }
12825
12826 /* Parse feature command from engine */
12827 void
12828 ParseFeatures(args, cps)
12829      char* args;
12830      ChessProgramState *cps;
12831 {
12832   char *p = args;
12833   char *q;
12834   int val;
12835   char buf[MSG_SIZ];
12836
12837   for (;;) {
12838     while (*p == ' ') p++;
12839     if (*p == NULLCHAR) return;
12840
12841     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12842     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12843     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12844     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12845     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12846     if (BoolFeature(&p, "reuse", &val, cps)) {
12847       /* Engine can disable reuse, but can't enable it if user said no */
12848       if (!val) cps->reuse = FALSE;
12849       continue;
12850     }
12851     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12852     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12853       if (gameMode == TwoMachinesPlay) {
12854         DisplayTwoMachinesTitle();
12855       } else {
12856         DisplayTitle("");
12857       }
12858       continue;
12859     }
12860     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12861     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12862     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12863     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12864     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12865     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12866     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12867     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12868     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12869     if (IntFeature(&p, "done", &val, cps)) {
12870       FeatureDone(cps, val);
12871       continue;
12872     }
12873     /* Added by Tord: */
12874     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12875     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12876     /* End of additions by Tord */
12877
12878     /* [HGM] added features: */
12879     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12880     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12881     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12882     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12883     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12884     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12885     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12886         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12887             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12888             SendToProgram(buf, cps);
12889             continue;
12890         }
12891         if(cps->nrOptions >= MAX_OPTIONS) {
12892             cps->nrOptions--;
12893             sprintf(buf, "%s engine has too many options\n", cps->which);
12894             DisplayError(buf, 0);
12895         }
12896         continue;
12897     }
12898     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12899     /* End of additions by HGM */
12900
12901     /* unknown feature: complain and skip */
12902     q = p;
12903     while (*q && *q != '=') q++;
12904     sprintf(buf, "rejected %.*s\n", q-p, p);
12905     SendToProgram(buf, cps);
12906     p = q;
12907     if (*p == '=') {
12908       p++;
12909       if (*p == '\"') {
12910         p++;
12911         while (*p && *p != '\"') p++;
12912         if (*p == '\"') p++;
12913       } else {
12914         while (*p && *p != ' ') p++;
12915       }
12916     }
12917   }
12918
12919 }
12920
12921 void
12922 PeriodicUpdatesEvent(newState)
12923      int newState;
12924 {
12925     if (newState == appData.periodicUpdates)
12926       return;
12927
12928     appData.periodicUpdates=newState;
12929
12930     /* Display type changes, so update it now */
12931 //    DisplayAnalysis();
12932
12933     /* Get the ball rolling again... */
12934     if (newState) {
12935         AnalysisPeriodicEvent(1);
12936         StartAnalysisClock();
12937     }
12938 }
12939
12940 void
12941 PonderNextMoveEvent(newState)
12942      int newState;
12943 {
12944     if (newState == appData.ponderNextMove) return;
12945     if (gameMode == EditPosition) EditPositionDone();
12946     if (newState) {
12947         SendToProgram("hard\n", &first);
12948         if (gameMode == TwoMachinesPlay) {
12949             SendToProgram("hard\n", &second);
12950         }
12951     } else {
12952         SendToProgram("easy\n", &first);
12953         thinkOutput[0] = NULLCHAR;
12954         if (gameMode == TwoMachinesPlay) {
12955             SendToProgram("easy\n", &second);
12956         }
12957     }
12958     appData.ponderNextMove = newState;
12959 }
12960
12961 void
12962 NewSettingEvent(option, command, value)
12963      char *command;
12964      int option, value;
12965 {
12966     char buf[MSG_SIZ];
12967
12968     if (gameMode == EditPosition) EditPositionDone();
12969     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12970     SendToProgram(buf, &first);
12971     if (gameMode == TwoMachinesPlay) {
12972         SendToProgram(buf, &second);
12973     }
12974 }
12975
12976 void
12977 ShowThinkingEvent()
12978 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12979 {
12980     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12981     int newState = appData.showThinking
12982         // [HGM] thinking: other features now need thinking output as well
12983         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12984
12985     if (oldState == newState) return;
12986     oldState = newState;
12987     if (gameMode == EditPosition) EditPositionDone();
12988     if (oldState) {
12989         SendToProgram("post\n", &first);
12990         if (gameMode == TwoMachinesPlay) {
12991             SendToProgram("post\n", &second);
12992         }
12993     } else {
12994         SendToProgram("nopost\n", &first);
12995         thinkOutput[0] = NULLCHAR;
12996         if (gameMode == TwoMachinesPlay) {
12997             SendToProgram("nopost\n", &second);
12998         }
12999     }
13000 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13001 }
13002
13003 void
13004 AskQuestionEvent(title, question, replyPrefix, which)
13005      char *title; char *question; char *replyPrefix; char *which;
13006 {
13007   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13008   if (pr == NoProc) return;
13009   AskQuestion(title, question, replyPrefix, pr);
13010 }
13011
13012 void
13013 DisplayMove(moveNumber)
13014      int moveNumber;
13015 {
13016     char message[MSG_SIZ];
13017     char res[MSG_SIZ];
13018     char cpThinkOutput[MSG_SIZ];
13019
13020     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13021
13022     if (moveNumber == forwardMostMove - 1 ||
13023         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13024
13025         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13026
13027         if (strchr(cpThinkOutput, '\n')) {
13028             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13029         }
13030     } else {
13031         *cpThinkOutput = NULLCHAR;
13032     }
13033
13034     /* [AS] Hide thinking from human user */
13035     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13036         *cpThinkOutput = NULLCHAR;
13037         if( thinkOutput[0] != NULLCHAR ) {
13038             int i;
13039
13040             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13041                 cpThinkOutput[i] = '.';
13042             }
13043             cpThinkOutput[i] = NULLCHAR;
13044             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13045         }
13046     }
13047
13048     if (moveNumber == forwardMostMove - 1 &&
13049         gameInfo.resultDetails != NULL) {
13050         if (gameInfo.resultDetails[0] == NULLCHAR) {
13051             sprintf(res, " %s", PGNResult(gameInfo.result));
13052         } else {
13053             sprintf(res, " {%s} %s",
13054                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13055         }
13056     } else {
13057         res[0] = NULLCHAR;
13058     }
13059
13060     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13061         DisplayMessage(res, cpThinkOutput);
13062     } else {
13063         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13064                 WhiteOnMove(moveNumber) ? " " : ".. ",
13065                 parseList[moveNumber], res);
13066         DisplayMessage(message, cpThinkOutput);
13067     }
13068 }
13069
13070 void
13071 DisplayComment(moveNumber, text)
13072      int moveNumber;
13073      char *text;
13074 {
13075     char title[MSG_SIZ];
13076     char buf[8000]; // comment can be long!
13077     int score, depth;
13078
13079     if( appData.autoDisplayComment ) {
13080         if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13081             strcpy(title, "Comment");
13082         } else {
13083             sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13084                     WhiteOnMove(moveNumber) ? " " : ".. ",
13085                     parseList[moveNumber]);
13086         }
13087         // [HGM] PV info: display PV info together with (or as) comment
13088         if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13089             if(text == NULL) text = "";
13090             score = pvInfoList[moveNumber].score;
13091             sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13092                               depth, (pvInfoList[moveNumber].time+50)/100, text);
13093             text = buf;
13094         }
13095     } else title[0] = 0;
13096
13097     if (text != NULL)
13098         CommentPopUp(title, text);
13099 }
13100
13101 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13102  * might be busy thinking or pondering.  It can be omitted if your
13103  * gnuchess is configured to stop thinking immediately on any user
13104  * input.  However, that gnuchess feature depends on the FIONREAD
13105  * ioctl, which does not work properly on some flavors of Unix.
13106  */
13107 void
13108 Attention(cps)
13109      ChessProgramState *cps;
13110 {
13111 #if ATTENTION
13112     if (!cps->useSigint) return;
13113     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13114     switch (gameMode) {
13115       case MachinePlaysWhite:
13116       case MachinePlaysBlack:
13117       case TwoMachinesPlay:
13118       case IcsPlayingWhite:
13119       case IcsPlayingBlack:
13120       case AnalyzeMode:
13121       case AnalyzeFile:
13122         /* Skip if we know it isn't thinking */
13123         if (!cps->maybeThinking) return;
13124         if (appData.debugMode)
13125           fprintf(debugFP, "Interrupting %s\n", cps->which);
13126         InterruptChildProcess(cps->pr);
13127         cps->maybeThinking = FALSE;
13128         break;
13129       default:
13130         break;
13131     }
13132 #endif /*ATTENTION*/
13133 }
13134
13135 int
13136 CheckFlags()
13137 {
13138     if (whiteTimeRemaining <= 0) {
13139         if (!whiteFlag) {
13140             whiteFlag = TRUE;
13141             if (appData.icsActive) {
13142                 if (appData.autoCallFlag &&
13143                     gameMode == IcsPlayingBlack && !blackFlag) {
13144                   SendToICS(ics_prefix);
13145                   SendToICS("flag\n");
13146                 }
13147             } else {
13148                 if (blackFlag) {
13149                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13150                 } else {
13151                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13152                     if (appData.autoCallFlag) {
13153                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13154                         return TRUE;
13155                     }
13156                 }
13157             }
13158         }
13159     }
13160     if (blackTimeRemaining <= 0) {
13161         if (!blackFlag) {
13162             blackFlag = TRUE;
13163             if (appData.icsActive) {
13164                 if (appData.autoCallFlag &&
13165                     gameMode == IcsPlayingWhite && !whiteFlag) {
13166                   SendToICS(ics_prefix);
13167                   SendToICS("flag\n");
13168                 }
13169             } else {
13170                 if (whiteFlag) {
13171                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13172                 } else {
13173                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13174                     if (appData.autoCallFlag) {
13175                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13176                         return TRUE;
13177                     }
13178                 }
13179             }
13180         }
13181     }
13182     return FALSE;
13183 }
13184
13185 void
13186 CheckTimeControl()
13187 {
13188     if (!appData.clockMode || appData.icsActive ||
13189         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13190
13191     /*
13192      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13193      */
13194     if ( !WhiteOnMove(forwardMostMove) )
13195         /* White made time control */
13196         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13197         /* [HGM] time odds: correct new time quota for time odds! */
13198                                             / WhitePlayer()->timeOdds;
13199       else
13200         /* Black made time control */
13201         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13202                                             / WhitePlayer()->other->timeOdds;
13203 }
13204
13205 void
13206 DisplayBothClocks()
13207 {
13208     int wom = gameMode == EditPosition ?
13209       !blackPlaysFirst : WhiteOnMove(currentMove);
13210     DisplayWhiteClock(whiteTimeRemaining, wom);
13211     DisplayBlackClock(blackTimeRemaining, !wom);
13212 }
13213
13214
13215 /* Timekeeping seems to be a portability nightmare.  I think everyone
13216    has ftime(), but I'm really not sure, so I'm including some ifdefs
13217    to use other calls if you don't.  Clocks will be less accurate if
13218    you have neither ftime nor gettimeofday.
13219 */
13220
13221 /* VS 2008 requires the #include outside of the function */
13222 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13223 #include <sys/timeb.h>
13224 #endif
13225
13226 /* Get the current time as a TimeMark */
13227 void
13228 GetTimeMark(tm)
13229      TimeMark *tm;
13230 {
13231 #if HAVE_GETTIMEOFDAY
13232
13233     struct timeval timeVal;
13234     struct timezone timeZone;
13235
13236     gettimeofday(&timeVal, &timeZone);
13237     tm->sec = (long) timeVal.tv_sec;
13238     tm->ms = (int) (timeVal.tv_usec / 1000L);
13239
13240 #else /*!HAVE_GETTIMEOFDAY*/
13241 #if HAVE_FTIME
13242
13243 // include <sys/timeb.h> / moved to just above start of function
13244     struct timeb timeB;
13245
13246     ftime(&timeB);
13247     tm->sec = (long) timeB.time;
13248     tm->ms = (int) timeB.millitm;
13249
13250 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13251     tm->sec = (long) time(NULL);
13252     tm->ms = 0;
13253 #endif
13254 #endif
13255 }
13256
13257 /* Return the difference in milliseconds between two
13258    time marks.  We assume the difference will fit in a long!
13259 */
13260 long
13261 SubtractTimeMarks(tm2, tm1)
13262      TimeMark *tm2, *tm1;
13263 {
13264     return 1000L*(tm2->sec - tm1->sec) +
13265            (long) (tm2->ms - tm1->ms);
13266 }
13267
13268
13269 /*
13270  * Code to manage the game clocks.
13271  *
13272  * In tournament play, black starts the clock and then white makes a move.
13273  * We give the human user a slight advantage if he is playing white---the
13274  * clocks don't run until he makes his first move, so it takes zero time.
13275  * Also, we don't account for network lag, so we could get out of sync
13276  * with GNU Chess's clock -- but then, referees are always right.
13277  */
13278
13279 static TimeMark tickStartTM;
13280 static long intendedTickLength;
13281
13282 long
13283 NextTickLength(timeRemaining)
13284      long timeRemaining;
13285 {
13286     long nominalTickLength, nextTickLength;
13287
13288     if (timeRemaining > 0L && timeRemaining <= 10000L)
13289       nominalTickLength = 100L;
13290     else
13291       nominalTickLength = 1000L;
13292     nextTickLength = timeRemaining % nominalTickLength;
13293     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13294
13295     return nextTickLength;
13296 }
13297
13298 /* Adjust clock one minute up or down */
13299 void
13300 AdjustClock(Boolean which, int dir)
13301 {
13302     if(which) blackTimeRemaining += 60000*dir;
13303     else      whiteTimeRemaining += 60000*dir;
13304     DisplayBothClocks();
13305 }
13306
13307 /* Stop clocks and reset to a fresh time control */
13308 void
13309 ResetClocks()
13310 {
13311     (void) StopClockTimer();
13312     if (appData.icsActive) {
13313         whiteTimeRemaining = blackTimeRemaining = 0;
13314     } else { /* [HGM] correct new time quote for time odds */
13315         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13316         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13317     }
13318     if (whiteFlag || blackFlag) {
13319         DisplayTitle("");
13320         whiteFlag = blackFlag = FALSE;
13321     }
13322     DisplayBothClocks();
13323 }
13324
13325 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13326
13327 /* Decrement running clock by amount of time that has passed */
13328 void
13329 DecrementClocks()
13330 {
13331     long timeRemaining;
13332     long lastTickLength, fudge;
13333     TimeMark now;
13334
13335     if (!appData.clockMode) return;
13336     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13337
13338     GetTimeMark(&now);
13339
13340     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13341
13342     /* Fudge if we woke up a little too soon */
13343     fudge = intendedTickLength - lastTickLength;
13344     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13345
13346     if (WhiteOnMove(forwardMostMove)) {
13347         if(whiteNPS >= 0) lastTickLength = 0;
13348         timeRemaining = whiteTimeRemaining -= lastTickLength;
13349         DisplayWhiteClock(whiteTimeRemaining - fudge,
13350                           WhiteOnMove(currentMove));
13351     } else {
13352         if(blackNPS >= 0) lastTickLength = 0;
13353         timeRemaining = blackTimeRemaining -= lastTickLength;
13354         DisplayBlackClock(blackTimeRemaining - fudge,
13355                           !WhiteOnMove(currentMove));
13356     }
13357
13358     if (CheckFlags()) return;
13359
13360     tickStartTM = now;
13361     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13362     StartClockTimer(intendedTickLength);
13363
13364     /* if the time remaining has fallen below the alarm threshold, sound the
13365      * alarm. if the alarm has sounded and (due to a takeback or time control
13366      * with increment) the time remaining has increased to a level above the
13367      * threshold, reset the alarm so it can sound again.
13368      */
13369
13370     if (appData.icsActive && appData.icsAlarm) {
13371
13372         /* make sure we are dealing with the user's clock */
13373         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13374                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13375            )) return;
13376
13377         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13378             alarmSounded = FALSE;
13379         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13380             PlayAlarmSound();
13381             alarmSounded = TRUE;
13382         }
13383     }
13384 }
13385
13386
13387 /* A player has just moved, so stop the previously running
13388    clock and (if in clock mode) start the other one.
13389    We redisplay both clocks in case we're in ICS mode, because
13390    ICS gives us an update to both clocks after every move.
13391    Note that this routine is called *after* forwardMostMove
13392    is updated, so the last fractional tick must be subtracted
13393    from the color that is *not* on move now.
13394 */
13395 void
13396 SwitchClocks()
13397 {
13398     long lastTickLength;
13399     TimeMark now;
13400     int flagged = FALSE;
13401
13402     GetTimeMark(&now);
13403
13404     if (StopClockTimer() && appData.clockMode) {
13405         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13406         if (WhiteOnMove(forwardMostMove)) {
13407             if(blackNPS >= 0) lastTickLength = 0;
13408             blackTimeRemaining -= lastTickLength;
13409            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13410 //         if(pvInfoList[forwardMostMove-1].time == -1)
13411                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13412                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13413         } else {
13414            if(whiteNPS >= 0) lastTickLength = 0;
13415            whiteTimeRemaining -= lastTickLength;
13416            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13417 //         if(pvInfoList[forwardMostMove-1].time == -1)
13418                  pvInfoList[forwardMostMove-1].time =
13419                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13420         }
13421         flagged = CheckFlags();
13422     }
13423     CheckTimeControl();
13424
13425     if (flagged || !appData.clockMode) return;
13426
13427     switch (gameMode) {
13428       case MachinePlaysBlack:
13429       case MachinePlaysWhite:
13430       case BeginningOfGame:
13431         if (pausing) return;
13432         break;
13433
13434       case EditGame:
13435       case PlayFromGameFile:
13436       case IcsExamining:
13437         return;
13438
13439       default:
13440         break;
13441     }
13442
13443     tickStartTM = now;
13444     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13445       whiteTimeRemaining : blackTimeRemaining);
13446     StartClockTimer(intendedTickLength);
13447 }
13448
13449
13450 /* Stop both clocks */
13451 void
13452 StopClocks()
13453 {
13454     long lastTickLength;
13455     TimeMark now;
13456
13457     if (!StopClockTimer()) return;
13458     if (!appData.clockMode) return;
13459
13460     GetTimeMark(&now);
13461
13462     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13463     if (WhiteOnMove(forwardMostMove)) {
13464         if(whiteNPS >= 0) lastTickLength = 0;
13465         whiteTimeRemaining -= lastTickLength;
13466         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13467     } else {
13468         if(blackNPS >= 0) lastTickLength = 0;
13469         blackTimeRemaining -= lastTickLength;
13470         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13471     }
13472     CheckFlags();
13473 }
13474
13475 /* Start clock of player on move.  Time may have been reset, so
13476    if clock is already running, stop and restart it. */
13477 void
13478 StartClocks()
13479 {
13480     (void) StopClockTimer(); /* in case it was running already */
13481     DisplayBothClocks();
13482     if (CheckFlags()) return;
13483
13484     if (!appData.clockMode) return;
13485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13486
13487     GetTimeMark(&tickStartTM);
13488     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13489       whiteTimeRemaining : blackTimeRemaining);
13490
13491    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13492     whiteNPS = blackNPS = -1;
13493     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13494        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13495         whiteNPS = first.nps;
13496     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13497        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13498         blackNPS = first.nps;
13499     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13500         whiteNPS = second.nps;
13501     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13502         blackNPS = second.nps;
13503     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13504
13505     StartClockTimer(intendedTickLength);
13506 }
13507
13508 char *
13509 TimeString(ms)
13510      long ms;
13511 {
13512     long second, minute, hour, day;
13513     char *sign = "";
13514     static char buf[32];
13515
13516     if (ms > 0 && ms <= 9900) {
13517       /* convert milliseconds to tenths, rounding up */
13518       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13519
13520       sprintf(buf, " %03.1f ", tenths/10.0);
13521       return buf;
13522     }
13523
13524     /* convert milliseconds to seconds, rounding up */
13525     /* use floating point to avoid strangeness of integer division
13526        with negative dividends on many machines */
13527     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13528
13529     if (second < 0) {
13530         sign = "-";
13531         second = -second;
13532     }
13533
13534     day = second / (60 * 60 * 24);
13535     second = second % (60 * 60 * 24);
13536     hour = second / (60 * 60);
13537     second = second % (60 * 60);
13538     minute = second / 60;
13539     second = second % 60;
13540
13541     if (day > 0)
13542       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13543               sign, day, hour, minute, second);
13544     else if (hour > 0)
13545       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13546     else
13547       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13548
13549     return buf;
13550 }
13551
13552
13553 /*
13554  * This is necessary because some C libraries aren't ANSI C compliant yet.
13555  */
13556 char *
13557 StrStr(string, match)
13558      char *string, *match;
13559 {
13560     int i, length;
13561
13562     length = strlen(match);
13563
13564     for (i = strlen(string) - length; i >= 0; i--, string++)
13565       if (!strncmp(match, string, length))
13566         return string;
13567
13568     return NULL;
13569 }
13570
13571 char *
13572 StrCaseStr(string, match)
13573      char *string, *match;
13574 {
13575     int i, j, length;
13576
13577     length = strlen(match);
13578
13579     for (i = strlen(string) - length; i >= 0; i--, string++) {
13580         for (j = 0; j < length; j++) {
13581             if (ToLower(match[j]) != ToLower(string[j]))
13582               break;
13583         }
13584         if (j == length) return string;
13585     }
13586
13587     return NULL;
13588 }
13589
13590 #ifndef _amigados
13591 int
13592 StrCaseCmp(s1, s2)
13593      char *s1, *s2;
13594 {
13595     char c1, c2;
13596
13597     for (;;) {
13598         c1 = ToLower(*s1++);
13599         c2 = ToLower(*s2++);
13600         if (c1 > c2) return 1;
13601         if (c1 < c2) return -1;
13602         if (c1 == NULLCHAR) return 0;
13603     }
13604 }
13605
13606
13607 int
13608 ToLower(c)
13609      int c;
13610 {
13611     return isupper(c) ? tolower(c) : c;
13612 }
13613
13614
13615 int
13616 ToUpper(c)
13617      int c;
13618 {
13619     return islower(c) ? toupper(c) : c;
13620 }
13621 #endif /* !_amigados    */
13622
13623 char *
13624 StrSave(s)
13625      char *s;
13626 {
13627     char *ret;
13628
13629     if ((ret = (char *) malloc(strlen(s) + 1))) {
13630         strcpy(ret, s);
13631     }
13632     return ret;
13633 }
13634
13635 char *
13636 StrSavePtr(s, savePtr)
13637      char *s, **savePtr;
13638 {
13639     if (*savePtr) {
13640         free(*savePtr);
13641     }
13642     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13643         strcpy(*savePtr, s);
13644     }
13645     return(*savePtr);
13646 }
13647
13648 char *
13649 PGNDate()
13650 {
13651     time_t clock;
13652     struct tm *tm;
13653     char buf[MSG_SIZ];
13654
13655     clock = time((time_t *)NULL);
13656     tm = localtime(&clock);
13657     sprintf(buf, "%04d.%02d.%02d",
13658             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13659     return StrSave(buf);
13660 }
13661
13662
13663 char *
13664 PositionToFEN(move, overrideCastling)
13665      int move;
13666      char *overrideCastling;
13667 {
13668     int i, j, fromX, fromY, toX, toY;
13669     int whiteToPlay;
13670     char buf[128];
13671     char *p, *q;
13672     int emptycount;
13673     ChessSquare piece;
13674
13675     whiteToPlay = (gameMode == EditPosition) ?
13676       !blackPlaysFirst : (move % 2 == 0);
13677     p = buf;
13678
13679     /* Piece placement data */
13680     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13681         emptycount = 0;
13682         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13683             if (boards[move][i][j] == EmptySquare) {
13684                 emptycount++;
13685             } else { ChessSquare piece = boards[move][i][j];
13686                 if (emptycount > 0) {
13687                     if(emptycount<10) /* [HGM] can be >= 10 */
13688                         *p++ = '0' + emptycount;
13689                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13690                     emptycount = 0;
13691                 }
13692                 if(PieceToChar(piece) == '+') {
13693                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13694                     *p++ = '+';
13695                     piece = (ChessSquare)(DEMOTED piece);
13696                 }
13697                 *p++ = PieceToChar(piece);
13698                 if(p[-1] == '~') {
13699                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13700                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13701                     *p++ = '~';
13702                 }
13703             }
13704         }
13705         if (emptycount > 0) {
13706             if(emptycount<10) /* [HGM] can be >= 10 */
13707                 *p++ = '0' + emptycount;
13708             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13709             emptycount = 0;
13710         }
13711         *p++ = '/';
13712     }
13713     *(p - 1) = ' ';
13714
13715     /* [HGM] print Crazyhouse or Shogi holdings */
13716     if( gameInfo.holdingsWidth ) {
13717         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13718         q = p;
13719         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13720             piece = boards[move][i][BOARD_WIDTH-1];
13721             if( piece != EmptySquare )
13722               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13723                   *p++ = PieceToChar(piece);
13724         }
13725         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13726             piece = boards[move][BOARD_HEIGHT-i-1][0];
13727             if( piece != EmptySquare )
13728               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13729                   *p++ = PieceToChar(piece);
13730         }
13731
13732         if( q == p ) *p++ = '-';
13733         *p++ = ']';
13734         *p++ = ' ';
13735     }
13736
13737     /* Active color */
13738     *p++ = whiteToPlay ? 'w' : 'b';
13739     *p++ = ' ';
13740
13741   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13742     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13743   } else {
13744   if(nrCastlingRights) {
13745      q = p;
13746      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13747        /* [HGM] write directly from rights */
13748            if(castlingRights[move][2] >= 0 &&
13749               castlingRights[move][0] >= 0   )
13750                 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13751            if(castlingRights[move][2] >= 0 &&
13752               castlingRights[move][1] >= 0   )
13753                 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13754            if(castlingRights[move][5] >= 0 &&
13755               castlingRights[move][3] >= 0   )
13756                 *p++ = castlingRights[move][3] + AAA;
13757            if(castlingRights[move][5] >= 0 &&
13758               castlingRights[move][4] >= 0   )
13759                 *p++ = castlingRights[move][4] + AAA;
13760      } else {
13761
13762         /* [HGM] write true castling rights */
13763         if( nrCastlingRights == 6 ) {
13764             if(castlingRights[move][0] == BOARD_RGHT-1 &&
13765                castlingRights[move][2] >= 0  ) *p++ = 'K';
13766             if(castlingRights[move][1] == BOARD_LEFT &&
13767                castlingRights[move][2] >= 0  ) *p++ = 'Q';
13768             if(castlingRights[move][3] == BOARD_RGHT-1 &&
13769                castlingRights[move][5] >= 0  ) *p++ = 'k';
13770             if(castlingRights[move][4] == BOARD_LEFT &&
13771                castlingRights[move][5] >= 0  ) *p++ = 'q';
13772         }
13773      }
13774      if (q == p) *p++ = '-'; /* No castling rights */
13775      *p++ = ' ';
13776   }
13777
13778   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13779      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13780     /* En passant target square */
13781     if (move > backwardMostMove) {
13782         fromX = moveList[move - 1][0] - AAA;
13783         fromY = moveList[move - 1][1] - ONE;
13784         toX = moveList[move - 1][2] - AAA;
13785         toY = moveList[move - 1][3] - ONE;
13786         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13787             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13788             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13789             fromX == toX) {
13790             /* 2-square pawn move just happened */
13791             *p++ = toX + AAA;
13792             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13793         } else {
13794             *p++ = '-';
13795         }
13796     } else if(move == backwardMostMove) {
13797         // [HGM] perhaps we should always do it like this, and forget the above?
13798         if(epStatus[move] >= 0) {
13799             *p++ = epStatus[move] + AAA;
13800             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13801         } else {
13802             *p++ = '-';
13803         }
13804     } else {
13805         *p++ = '-';
13806     }
13807     *p++ = ' ';
13808   }
13809   }
13810
13811     /* [HGM] find reversible plies */
13812     {   int i = 0, j=move;
13813
13814         if (appData.debugMode) { int k;
13815             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13816             for(k=backwardMostMove; k<=forwardMostMove; k++)
13817                 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13818
13819         }
13820
13821         while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13822         if( j == backwardMostMove ) i += initialRulePlies;
13823         sprintf(p, "%d ", i);
13824         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13825     }
13826     /* Fullmove number */
13827     sprintf(p, "%d", (move / 2) + 1);
13828
13829     return StrSave(buf);
13830 }
13831
13832 Boolean
13833 ParseFEN(board, blackPlaysFirst, fen)
13834     Board board;
13835      int *blackPlaysFirst;
13836      char *fen;
13837 {
13838     int i, j;
13839     char *p;
13840     int emptycount;
13841     ChessSquare piece;
13842
13843     p = fen;
13844
13845     /* [HGM] by default clear Crazyhouse holdings, if present */
13846     if(gameInfo.holdingsWidth) {
13847        for(i=0; i<BOARD_HEIGHT; i++) {
13848            board[i][0]             = EmptySquare; /* black holdings */
13849            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13850            board[i][1]             = (ChessSquare) 0; /* black counts */
13851            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13852        }
13853     }
13854
13855     /* Piece placement data */
13856     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13857         j = 0;
13858         for (;;) {
13859             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13860                 if (*p == '/') p++;
13861                 emptycount = gameInfo.boardWidth - j;
13862                 while (emptycount--)
13863                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13864                 break;
13865 #if(BOARD_SIZE >= 10)
13866             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13867                 p++; emptycount=10;
13868                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13869                 while (emptycount--)
13870                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13871 #endif
13872             } else if (isdigit(*p)) {
13873                 emptycount = *p++ - '0';
13874                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13875                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13876                 while (emptycount--)
13877                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13878             } else if (*p == '+' || isalpha(*p)) {
13879                 if (j >= gameInfo.boardWidth) return FALSE;
13880                 if(*p=='+') {
13881                     piece = CharToPiece(*++p);
13882                     if(piece == EmptySquare) return FALSE; /* unknown piece */
13883                     piece = (ChessSquare) (PROMOTED piece ); p++;
13884                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13885                 } else piece = CharToPiece(*p++);
13886
13887                 if(piece==EmptySquare) return FALSE; /* unknown piece */
13888                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13889                     piece = (ChessSquare) (PROMOTED piece);
13890                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13891                     p++;
13892                 }
13893                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13894             } else {
13895                 return FALSE;
13896             }
13897         }
13898     }
13899     while (*p == '/' || *p == ' ') p++;
13900
13901     /* [HGM] look for Crazyhouse holdings here */
13902     while(*p==' ') p++;
13903     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13904         if(*p == '[') p++;
13905         if(*p == '-' ) *p++; /* empty holdings */ else {
13906             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13907             /* if we would allow FEN reading to set board size, we would   */
13908             /* have to add holdings and shift the board read so far here   */
13909             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13910                 *p++;
13911                 if((int) piece >= (int) BlackPawn ) {
13912                     i = (int)piece - (int)BlackPawn;
13913                     i = PieceToNumber((ChessSquare)i);
13914                     if( i >= gameInfo.holdingsSize ) return FALSE;
13915                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13916                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
13917                 } else {
13918                     i = (int)piece - (int)WhitePawn;
13919                     i = PieceToNumber((ChessSquare)i);
13920                     if( i >= gameInfo.holdingsSize ) return FALSE;
13921                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
13922                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
13923                 }
13924             }
13925         }
13926         if(*p == ']') *p++;
13927     }
13928
13929     while(*p == ' ') p++;
13930
13931     /* Active color */
13932     switch (*p++) {
13933       case 'w':
13934         *blackPlaysFirst = FALSE;
13935         break;
13936       case 'b':
13937         *blackPlaysFirst = TRUE;
13938         break;
13939       default:
13940         return FALSE;
13941     }
13942
13943     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13944     /* return the extra info in global variiables             */
13945
13946     /* set defaults in case FEN is incomplete */
13947     FENepStatus = EP_UNKNOWN;
13948     for(i=0; i<nrCastlingRights; i++ ) {
13949         FENcastlingRights[i] =
13950             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13951     }   /* assume possible unless obviously impossible */
13952     if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13953     if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13954     if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13955     if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13956     if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13957     if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13958     FENrulePlies = 0;
13959
13960     while(*p==' ') p++;
13961     if(nrCastlingRights) {
13962       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13963           /* castling indicator present, so default becomes no castlings */
13964           for(i=0; i<nrCastlingRights; i++ ) {
13965                  FENcastlingRights[i] = -1;
13966           }
13967       }
13968       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13969              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13970              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13971              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
13972         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13973
13974         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13975             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13976             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
13977         }
13978         switch(c) {
13979           case'K':
13980               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13981               FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13982               FENcastlingRights[2] = whiteKingFile;
13983               break;
13984           case'Q':
13985               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13986               FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13987               FENcastlingRights[2] = whiteKingFile;
13988               break;
13989           case'k':
13990               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13991               FENcastlingRights[3] = i != blackKingFile ? i : -1;
13992               FENcastlingRights[5] = blackKingFile;
13993               break;
13994           case'q':
13995               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13996               FENcastlingRights[4] = i != blackKingFile ? i : -1;
13997               FENcastlingRights[5] = blackKingFile;
13998           case '-':
13999               break;
14000           default: /* FRC castlings */
14001               if(c >= 'a') { /* black rights */
14002                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14003                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14004                   if(i == BOARD_RGHT) break;
14005                   FENcastlingRights[5] = i;
14006                   c -= AAA;
14007                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14008                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14009                   if(c > i)
14010                       FENcastlingRights[3] = c;
14011                   else
14012                       FENcastlingRights[4] = c;
14013               } else { /* white rights */
14014                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14015                     if(board[0][i] == WhiteKing) break;
14016                   if(i == BOARD_RGHT) break;
14017                   FENcastlingRights[2] = i;
14018                   c -= AAA - 'a' + 'A';
14019                   if(board[0][c] >= WhiteKing) break;
14020                   if(c > i)
14021                       FENcastlingRights[0] = c;
14022                   else
14023                       FENcastlingRights[1] = c;
14024               }
14025         }
14026       }
14027     if (appData.debugMode) {
14028         fprintf(debugFP, "FEN castling rights:");
14029         for(i=0; i<nrCastlingRights; i++)
14030         fprintf(debugFP, " %d", FENcastlingRights[i]);
14031         fprintf(debugFP, "\n");
14032     }
14033
14034       while(*p==' ') p++;
14035     }
14036
14037     /* read e.p. field in games that know e.p. capture */
14038     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14039        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14040       if(*p=='-') {
14041         p++; FENepStatus = EP_NONE;
14042       } else {
14043          char c = *p++ - AAA;
14044
14045          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14046          if(*p >= '0' && *p <='9') *p++;
14047          FENepStatus = c;
14048       }
14049     }
14050
14051
14052     if(sscanf(p, "%d", &i) == 1) {
14053         FENrulePlies = i; /* 50-move ply counter */
14054         /* (The move number is still ignored)    */
14055     }
14056
14057     return TRUE;
14058 }
14059
14060 void
14061 EditPositionPasteFEN(char *fen)
14062 {
14063   if (fen != NULL) {
14064     Board initial_position;
14065
14066     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14067       DisplayError(_("Bad FEN position in clipboard"), 0);
14068       return ;
14069     } else {
14070       int savedBlackPlaysFirst = blackPlaysFirst;
14071       EditPositionEvent();
14072       blackPlaysFirst = savedBlackPlaysFirst;
14073       CopyBoard(boards[0], initial_position);
14074           /* [HGM] copy FEN attributes as well */
14075           {   int i;
14076               initialRulePlies = FENrulePlies;
14077               epStatus[0] = FENepStatus;
14078               for( i=0; i<nrCastlingRights; i++ )
14079                   castlingRights[0][i] = FENcastlingRights[i];
14080           }
14081       EditPositionDone();
14082       DisplayBothClocks();
14083       DrawPosition(FALSE, boards[currentMove]);
14084     }
14085   }
14086 }
14087
14088 static char cseq[12] = "\\   ";
14089
14090 Boolean set_cont_sequence(char *new_seq)
14091 {
14092     int len;
14093     Boolean ret;
14094
14095     // handle bad attempts to set the sequence
14096         if (!new_seq)
14097                 return 0; // acceptable error - no debug
14098
14099     len = strlen(new_seq);
14100     ret = (len > 0) && (len < sizeof(cseq));
14101     if (ret)
14102         strcpy(cseq, new_seq);
14103     else if (appData.debugMode)
14104         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14105     return ret;
14106 }
14107
14108 /*
14109     reformat a source message so words don't cross the width boundary.  internal
14110     newlines are not removed.  returns the wrapped size (no null character unless
14111     included in source message).  If dest is NULL, only calculate the size required
14112     for the dest buffer.  lp argument indicats line position upon entry, and it's
14113     passed back upon exit.
14114 */
14115 int wrap(char *dest, char *src, int count, int width, int *lp)
14116 {
14117     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14118
14119     cseq_len = strlen(cseq);
14120     old_line = line = *lp;
14121     ansi = len = clen = 0;
14122
14123     for (i=0; i < count; i++)
14124     {
14125         if (src[i] == '\033')
14126             ansi = 1;
14127
14128         // if we hit the width, back up
14129         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14130         {
14131             // store i & len in case the word is too long
14132             old_i = i, old_len = len;
14133
14134             // find the end of the last word
14135             while (i && src[i] != ' ' && src[i] != '\n')
14136             {
14137                 i--;
14138                 len--;
14139             }
14140
14141             // word too long?  restore i & len before splitting it
14142             if ((old_i-i+clen) >= width)
14143             {
14144                 i = old_i;
14145                 len = old_len;
14146             }
14147
14148             // extra space?
14149             if (i && src[i-1] == ' ')
14150                 len--;
14151
14152             if (src[i] != ' ' && src[i] != '\n')
14153             {
14154                 i--;
14155                 if (len)
14156                     len--;
14157             }
14158
14159             // now append the newline and continuation sequence
14160             if (dest)
14161                 dest[len] = '\n';
14162             len++;
14163             if (dest)
14164                 strncpy(dest+len, cseq, cseq_len);
14165             len += cseq_len;
14166             line = cseq_len;
14167             clen = cseq_len;
14168             continue;
14169         }
14170
14171         if (dest)
14172             dest[len] = src[i];
14173         len++;
14174         if (!ansi)
14175             line++;
14176         if (src[i] == '\n')
14177             line = 0;
14178         if (src[i] == 'm')
14179             ansi = 0;
14180     }
14181     if (dest && appData.debugMode)
14182     {
14183         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14184             count, width, line, len, *lp);
14185         show_bytes(debugFP, src, count);
14186         fprintf(debugFP, "\ndest: ");
14187         show_bytes(debugFP, dest, len);
14188         fprintf(debugFP, "\n");
14189     }
14190     *lp = dest ? line : old_line;
14191
14192     return len;
14193 }