Fix reading FEN castling rights for knightmate and twokings
[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));
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((Boolean fakeRights));
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 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
253
254 /* States for ics_getting_history */
255 #define H_FALSE 0
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
261
262 /* whosays values for GameEnds */
263 #define GE_ICS 0
264 #define GE_ENGINE 1
265 #define GE_PLAYER 2
266 #define GE_FILE 3
267 #define GE_XBOARD 4
268 #define GE_ENGINE1 5
269 #define GE_ENGINE2 6
270
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
273
274 /* Different types of move when calling RegisterMove */
275 #define CMAIL_MOVE   0
276 #define CMAIL_RESIGN 1
277 #define CMAIL_DRAW   2
278 #define CMAIL_ACCEPT 3
279
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
284
285 /* Telnet protocol constants */
286 #define TN_WILL 0373
287 #define TN_WONT 0374
288 #define TN_DO   0375
289 #define TN_DONT 0376
290 #define TN_IAC  0377
291 #define TN_ECHO 0001
292 #define TN_SGA  0003
293 #define TN_PORT 23
294
295 /* [AS] */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 {
298     assert( dst != NULL );
299     assert( src != NULL );
300     assert( count > 0 );
301
302     strncpy( dst, src, count );
303     dst[ count-1 ] = '\0';
304     return dst;
305 }
306
307 /* Some compiler can't cast u64 to double
308  * This function do the job for us:
309
310  * We use the highest bit for cast, this only
311  * works if the highest bit is not
312  * in use (This should not happen)
313  *
314  * We used this for all compiler
315  */
316 double
317 u64ToDouble(u64 value)
318 {
319   double r;
320   u64 tmp = value & u64Const(0x7fffffffffffffff);
321   r = (double)(s64)tmp;
322   if (value & u64Const(0x8000000000000000))
323        r +=  9.2233720368547758080e18; /* 2^63 */
324  return r;
325 }
326
327 /* Fake up flags for now, as we aren't keeping track of castling
328    availability yet. [HGM] Change of logic: the flag now only
329    indicates the type of castlings allowed by the rule of the game.
330    The actual rights themselves are maintained in the array
331    castlingRights, as part of the game history, and are not probed
332    by this function.
333  */
334 int
335 PosFlags(index)
336 {
337   int flags = F_ALL_CASTLE_OK;
338   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339   switch (gameInfo.variant) {
340   case VariantSuicide:
341     flags &= ~F_ALL_CASTLE_OK;
342   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343     flags |= F_IGNORE_CHECK;
344   case VariantLosers:
345     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346     break;
347   case VariantAtomic:
348     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349     break;
350   case VariantKriegspiel:
351     flags |= F_KRIEGSPIEL_CAPTURE;
352     break;
353   case VariantCapaRandom: 
354   case VariantFischeRandom:
355     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356   case VariantNoCastle:
357   case VariantShatranj:
358   case VariantCourier:
359     flags &= ~F_ALL_CASTLE_OK;
360     break;
361   default:
362     break;
363   }
364   return flags;
365 }
366
367 FILE *gameFileFP, *debugFP;
368
369 /* 
370     [AS] Note: sometimes, the sscanf() function is used to parse the input
371     into a fixed-size buffer. Because of this, we must be prepared to
372     receive strings as long as the size of the input buffer, which is currently
373     set to 4K for Windows and 8K for the rest.
374     So, we must either allocate sufficiently large buffers here, or
375     reduce the size of the input buffer in the input reading part.
376 */
377
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
381
382 ChessProgramState first, second;
383
384 /* premove variables */
385 int premoveToX = 0;
386 int premoveToY = 0;
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
390 int gotPremove = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
393
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
396
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
424 int movesPerSession;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
429 int matchGame = 0;
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
433
434 /* animateTraining preserves the state of appData.animate
435  * when Training mode is activated. This allows the
436  * response to be animated when appData.animate == TRUE and
437  * appData.animateDragging == TRUE.
438  */
439 Boolean animateTraining;
440
441 GameInfo gameInfo;
442
443 AppData appData;
444
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char  initialRights[BOARD_FILES];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
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 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
459 int storedGames = 0;
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
465
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
469
470 ChessSquare  FIDEArray[2][BOARD_FILES] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
570
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<=framePtr; i++ ) {
662             pvInfoList[i].depth = -1;
663             boards[i][EP_STATUS] = EP_NONE;
664             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813         appData.clockMode = FALSE;
814         first.sendTime = second.sendTime = 0;
815     }
816     
817 #if ZIPPY
818     /* Override some settings from environment variables, for backward
819        compatibility.  Unfortunately it's not feasible to have the env
820        vars just set defaults, at least in xboard.  Ugh.
821     */
822     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
823       ZippyInit();
824     }
825 #endif
826     
827     if (appData.noChessProgram) {
828         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829         sprintf(programVersion, "%s", PACKAGE_STRING);
830     } else {
831       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
834     }
835
836     if (!appData.icsActive) {
837       char buf[MSG_SIZ];
838       /* Check for variants that are supported only in ICS mode,
839          or not at all.  Some that are accepted here nevertheless
840          have bugs; see comments below.
841       */
842       VariantClass variant = StringToVariant(appData.variant);
843       switch (variant) {
844       case VariantBughouse:     /* need four players and two boards */
845       case VariantKriegspiel:   /* need to hide pieces and move details */
846       /* case VariantFischeRandom: (Fabien: moved below) */
847         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantUnknown:
852       case VariantLoadable:
853       case Variant29:
854       case Variant30:
855       case Variant31:
856       case Variant32:
857       case Variant33:
858       case Variant34:
859       case Variant35:
860       case Variant36:
861       default:
862         sprintf(buf, _("Unknown variant name %s"), appData.variant);
863         DisplayFatalError(buf, 0, 2);
864         return;
865
866       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
867       case VariantFairy:      /* [HGM] TestLegality definitely off! */
868       case VariantGothic:     /* [HGM] should work */
869       case VariantCapablanca: /* [HGM] should work */
870       case VariantCourier:    /* [HGM] initial forced moves not implemented */
871       case VariantShogi:      /* [HGM] drops not tested for legality */
872       case VariantKnightmate: /* [HGM] should work */
873       case VariantCylinder:   /* [HGM] untested */
874       case VariantFalcon:     /* [HGM] untested */
875       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876                                  offboard interposition not understood */
877       case VariantNormal:     /* definitely works! */
878       case VariantWildCastle: /* pieces not automatically shuffled */
879       case VariantNoCastle:   /* pieces not automatically shuffled */
880       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881       case VariantLosers:     /* should work except for win condition,
882                                  and doesn't know captures are mandatory */
883       case VariantSuicide:    /* should work except for win condition,
884                                  and doesn't know captures are mandatory */
885       case VariantGiveaway:   /* should work except for win condition,
886                                  and doesn't know captures are mandatory */
887       case VariantTwoKings:   /* should work */
888       case VariantAtomic:     /* should work except for win condition */
889       case Variant3Check:     /* should work except for win condition */
890       case VariantShatranj:   /* should work except for all win conditions */
891       case VariantBerolina:   /* might work if TestLegality is off */
892       case VariantCapaRandom: /* should work */
893       case VariantJanus:      /* should work */
894       case VariantSuper:      /* experimental */
895       case VariantGreat:      /* experimental, requires legality testing to be off */
896         break;
897       }
898     }
899
900     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
901     InitEngineUCI( installDir, &second );
902 }
903
904 int NextIntegerFromString( char ** str, long * value )
905 {
906     int result = -1;
907     char * s = *str;
908
909     while( *s == ' ' || *s == '\t' ) {
910         s++;
911     }
912
913     *value = 0;
914
915     if( *s >= '0' && *s <= '9' ) {
916         while( *s >= '0' && *s <= '9' ) {
917             *value = *value * 10 + (*s - '0');
918             s++;
919         }
920
921         result = 0;
922     }
923
924     *str = s;
925
926     return result;
927 }
928
929 int NextTimeControlFromString( char ** str, long * value )
930 {
931     long temp;
932     int result = NextIntegerFromString( str, &temp );
933
934     if( result == 0 ) {
935         *value = temp * 60; /* Minutes */
936         if( **str == ':' ) {
937             (*str)++;
938             result = NextIntegerFromString( str, &temp );
939             *value += temp; /* Seconds */
940         }
941     }
942
943     return result;
944 }
945
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
948     int result = -1; long temp, temp2;
949
950     if(**str != '+') return -1; // old params remain in force!
951     (*str)++;
952     if( NextTimeControlFromString( str, &temp ) ) return -1;
953
954     if(**str != '/') {
955         /* time only: incremental or sudden-death time control */
956         if(**str == '+') { /* increment follows; read it */
957             (*str)++;
958             if(result = NextIntegerFromString( str, &temp2)) return -1;
959             *inc = temp2 * 1000;
960         } else *inc = 0;
961         *moves = 0; *tc = temp * 1000; 
962         return 0;
963     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
964
965     (*str)++; /* classical time control */
966     result = NextTimeControlFromString( str, &temp2);
967     if(result == 0) {
968         *moves = temp/60;
969         *tc    = temp2 * 1000;
970         *inc   = 0;
971     }
972     return result;
973 }
974
975 int GetTimeQuota(int movenr)
976 {   /* [HGM] get time to add from the multi-session time-control string */
977     int moves=1; /* kludge to force reading of first session */
978     long time, increment;
979     char *s = fullTimeControlString;
980
981     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
982     do {
983         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985         if(movenr == -1) return time;    /* last move before new session     */
986         if(!moves) return increment;     /* current session is incremental   */
987         if(movenr >= 0) movenr -= moves; /* we already finished this session */
988     } while(movenr >= -1);               /* try again for next session       */
989
990     return 0; // no new time quota on this move
991 }
992
993 int
994 ParseTimeControl(tc, ti, mps)
995      char *tc;
996      int ti;
997      int mps;
998 {
999   long tc1;
1000   long tc2;
1001   char buf[MSG_SIZ];
1002   
1003   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1004   if(ti > 0) {
1005     if(mps)
1006       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007     else sprintf(buf, "+%s+%d", tc, ti);
1008   } else {
1009     if(mps)
1010              sprintf(buf, "+%d/%s", mps, tc);
1011     else sprintf(buf, "+%s", tc);
1012   }
1013   fullTimeControlString = StrSave(buf);
1014   
1015   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1016     return FALSE;
1017   }
1018   
1019   if( *tc == '/' ) {
1020     /* Parse second time control */
1021     tc++;
1022     
1023     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1024       return FALSE;
1025     }
1026     
1027     if( tc2 == 0 ) {
1028       return FALSE;
1029     }
1030     
1031     timeControl_2 = tc2 * 1000;
1032   }
1033   else {
1034     timeControl_2 = 0;
1035   }
1036   
1037   if( tc1 == 0 ) {
1038     return FALSE;
1039   }
1040   
1041   timeControl = tc1 * 1000;
1042   
1043   if (ti >= 0) {
1044     timeIncrement = ti * 1000;  /* convert to ms */
1045     movesPerSession = 0;
1046   } else {
1047     timeIncrement = 0;
1048     movesPerSession = mps;
1049   }
1050   return TRUE;
1051 }
1052
1053 void
1054 InitBackEnd2()
1055 {
1056     if (appData.debugMode) {
1057         fprintf(debugFP, "%s\n", programVersion);
1058     }
1059
1060     set_cont_sequence(appData.wrapContSeq);
1061     if (appData.matchGames > 0) {
1062         appData.matchMode = TRUE;
1063     } else if (appData.matchMode) {
1064         appData.matchGames = 1;
1065     }
1066     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067         appData.matchGames = appData.sameColorGames;
1068     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1071     }
1072     Reset(TRUE, FALSE);
1073     if (appData.noChessProgram || first.protocolVersion == 1) {
1074       InitBackEnd3();
1075     } else {
1076       /* kludge: allow timeout for initial "feature" commands */
1077       FreezeUI();
1078       DisplayMessage("", _("Starting chess program"));
1079       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1080     }
1081 }
1082
1083 void
1084 InitBackEnd3 P((void))
1085 {
1086     GameMode initialMode;
1087     char buf[MSG_SIZ];
1088     int err;
1089
1090     InitChessProgram(&first, startedFromSetupPosition);
1091
1092
1093     if (appData.icsActive) {
1094 #ifdef WIN32
1095         /* [DM] Make a console window if needed [HGM] merged ifs */
1096         ConsoleCreate(); 
1097 #endif
1098         err = establish();
1099         if (err != 0) {
1100             if (*appData.icsCommPort != NULLCHAR) {
1101                 sprintf(buf, _("Could not open comm port %s"),  
1102                         appData.icsCommPort);
1103             } else {
1104                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1105                         appData.icsHost, appData.icsPort);
1106             }
1107             DisplayFatalError(buf, err, 1);
1108             return;
1109         }
1110         SetICSMode();
1111         telnetISR =
1112           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1113         fromUserISR =
1114           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115     } else if (appData.noChessProgram) {
1116         SetNCPMode();
1117     } else {
1118         SetGNUMode();
1119     }
1120
1121     if (*appData.cmailGameName != NULLCHAR) {
1122         SetCmailMode();
1123         OpenLoopback(&cmailPR);
1124         cmailISR =
1125           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126     }
1127     
1128     ThawUI();
1129     DisplayMessage("", "");
1130     if (StrCaseCmp(appData.initialMode, "") == 0) {
1131       initialMode = BeginningOfGame;
1132     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133       initialMode = TwoMachinesPlay;
1134     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135       initialMode = AnalyzeFile; 
1136     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137       initialMode = AnalyzeMode;
1138     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139       initialMode = MachinePlaysWhite;
1140     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141       initialMode = MachinePlaysBlack;
1142     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143       initialMode = EditGame;
1144     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145       initialMode = EditPosition;
1146     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147       initialMode = Training;
1148     } else {
1149       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150       DisplayFatalError(buf, 0, 2);
1151       return;
1152     }
1153
1154     if (appData.matchMode) {
1155         /* Set up machine vs. machine match */
1156         if (appData.noChessProgram) {
1157             DisplayFatalError(_("Can't have a match with no chess programs"),
1158                               0, 2);
1159             return;
1160         }
1161         matchMode = TRUE;
1162         matchGame = 1;
1163         if (*appData.loadGameFile != NULLCHAR) {
1164             int index = appData.loadGameIndex; // [HGM] autoinc
1165             if(index<0) lastIndex = index = 1;
1166             if (!LoadGameFromFile(appData.loadGameFile,
1167                                   index,
1168                                   appData.loadGameFile, FALSE)) {
1169                 DisplayFatalError(_("Bad game file"), 0, 1);
1170                 return;
1171             }
1172         } else if (*appData.loadPositionFile != NULLCHAR) {
1173             int index = appData.loadPositionIndex; // [HGM] autoinc
1174             if(index<0) lastIndex = index = 1;
1175             if (!LoadPositionFromFile(appData.loadPositionFile,
1176                                       index,
1177                                       appData.loadPositionFile)) {
1178                 DisplayFatalError(_("Bad position file"), 0, 1);
1179                 return;
1180             }
1181         }
1182         TwoMachinesEvent();
1183     } else if (*appData.cmailGameName != NULLCHAR) {
1184         /* Set up cmail mode */
1185         ReloadCmailMsgEvent(TRUE);
1186     } else {
1187         /* Set up other modes */
1188         if (initialMode == AnalyzeFile) {
1189           if (*appData.loadGameFile == NULLCHAR) {
1190             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191             return;
1192           }
1193         }
1194         if (*appData.loadGameFile != NULLCHAR) {
1195             (void) LoadGameFromFile(appData.loadGameFile,
1196                                     appData.loadGameIndex,
1197                                     appData.loadGameFile, TRUE);
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             (void) LoadPositionFromFile(appData.loadPositionFile,
1200                                         appData.loadPositionIndex,
1201                                         appData.loadPositionFile);
1202             /* [HGM] try to make self-starting even after FEN load */
1203             /* to allow automatic setup of fairy variants with wtm */
1204             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205                 gameMode = BeginningOfGame;
1206                 setboardSpoiledMachineBlack = 1;
1207             }
1208             /* [HGM] loadPos: make that every new game uses the setup */
1209             /* from file as long as we do not switch variant          */
1210             if(!blackPlaysFirst) {
1211                 startedFromPositionFile = TRUE;
1212                 CopyBoard(filePosition, boards[0]);
1213             }
1214         }
1215         if (initialMode == AnalyzeMode) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1218             return;
1219           }
1220           if (appData.icsActive) {
1221             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222             return;
1223           }
1224           AnalyzeModeEvent();
1225         } else if (initialMode == AnalyzeFile) {
1226           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227           ShowThinkingEvent();
1228           AnalyzeFileEvent();
1229           AnalysisPeriodicEvent(1);
1230         } else if (initialMode == MachinePlaysWhite) {
1231           if (appData.noChessProgram) {
1232             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233                               0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238                               0, 2);
1239             return;
1240           }
1241           MachineWhiteEvent();
1242         } else if (initialMode == MachinePlaysBlack) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245                               0, 2);
1246             return;
1247           }
1248           if (appData.icsActive) {
1249             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250                               0, 2);
1251             return;
1252           }
1253           MachineBlackEvent();
1254         } else if (initialMode == TwoMachinesPlay) {
1255           if (appData.noChessProgram) {
1256             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257                               0, 2);
1258             return;
1259           }
1260           if (appData.icsActive) {
1261             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1262                               0, 2);
1263             return;
1264           }
1265           TwoMachinesEvent();
1266         } else if (initialMode == EditGame) {
1267           EditGameEvent();
1268         } else if (initialMode == EditPosition) {
1269           EditPositionEvent();
1270         } else if (initialMode == Training) {
1271           if (*appData.loadGameFile == NULLCHAR) {
1272             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1273             return;
1274           }
1275           TrainingEvent();
1276         }
1277     }
1278 }
1279
1280 /*
1281  * Establish will establish a contact to a remote host.port.
1282  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283  *  used to talk to the host.
1284  * Returns 0 if okay, error code if not.
1285  */
1286 int
1287 establish()
1288 {
1289     char buf[MSG_SIZ];
1290
1291     if (*appData.icsCommPort != NULLCHAR) {
1292         /* Talk to the host through a serial comm port */
1293         return OpenCommPort(appData.icsCommPort, &icsPR);
1294
1295     } else if (*appData.gateway != NULLCHAR) {
1296         if (*appData.remoteShell == NULLCHAR) {
1297             /* Use the rcmd protocol to run telnet program on a gateway host */
1298             snprintf(buf, sizeof(buf), "%s %s %s",
1299                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1300             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1301
1302         } else {
1303             /* Use the rsh program to run telnet program on a gateway host */
1304             if (*appData.remoteUser == NULLCHAR) {
1305                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306                         appData.gateway, appData.telnetProgram,
1307                         appData.icsHost, appData.icsPort);
1308             } else {
1309                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310                         appData.remoteShell, appData.gateway, 
1311                         appData.remoteUser, appData.telnetProgram,
1312                         appData.icsHost, appData.icsPort);
1313             }
1314             return StartChildProcess(buf, "", &icsPR);
1315
1316         }
1317     } else if (appData.useTelnet) {
1318         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1319
1320     } else {
1321         /* TCP socket interface differs somewhat between
1322            Unix and NT; handle details in the front end.
1323            */
1324         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1325     }
1326 }
1327
1328 void
1329 show_bytes(fp, buf, count)
1330      FILE *fp;
1331      char *buf;
1332      int count;
1333 {
1334     while (count--) {
1335         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336             fprintf(fp, "\\%03o", *buf & 0xff);
1337         } else {
1338             putc(*buf, fp);
1339         }
1340         buf++;
1341     }
1342     fflush(fp);
1343 }
1344
1345 /* Returns an errno value */
1346 int
1347 OutputMaybeTelnet(pr, message, count, outError)
1348      ProcRef pr;
1349      char *message;
1350      int count;
1351      int *outError;
1352 {
1353     char buf[8192], *p, *q, *buflim;
1354     int left, newcount, outcount;
1355
1356     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357         *appData.gateway != NULLCHAR) {
1358         if (appData.debugMode) {
1359             fprintf(debugFP, ">ICS: ");
1360             show_bytes(debugFP, message, count);
1361             fprintf(debugFP, "\n");
1362         }
1363         return OutputToProcess(pr, message, count, outError);
1364     }
1365
1366     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1367     p = message;
1368     q = buf;
1369     left = count;
1370     newcount = 0;
1371     while (left) {
1372         if (q >= buflim) {
1373             if (appData.debugMode) {
1374                 fprintf(debugFP, ">ICS: ");
1375                 show_bytes(debugFP, buf, newcount);
1376                 fprintf(debugFP, "\n");
1377             }
1378             outcount = OutputToProcess(pr, buf, newcount, outError);
1379             if (outcount < newcount) return -1; /* to be sure */
1380             q = buf;
1381             newcount = 0;
1382         }
1383         if (*p == '\n') {
1384             *q++ = '\r';
1385             newcount++;
1386         } else if (((unsigned char) *p) == TN_IAC) {
1387             *q++ = (char) TN_IAC;
1388             newcount ++;
1389         }
1390         *q++ = *p++;
1391         newcount++;
1392         left--;
1393     }
1394     if (appData.debugMode) {
1395         fprintf(debugFP, ">ICS: ");
1396         show_bytes(debugFP, buf, newcount);
1397         fprintf(debugFP, "\n");
1398     }
1399     outcount = OutputToProcess(pr, buf, newcount, outError);
1400     if (outcount < newcount) return -1; /* to be sure */
1401     return count;
1402 }
1403
1404 void
1405 read_from_player(isr, closure, message, count, error)
1406      InputSourceRef isr;
1407      VOIDSTAR closure;
1408      char *message;
1409      int count;
1410      int error;
1411 {
1412     int outError, outCount;
1413     static int gotEof = 0;
1414
1415     /* Pass data read from player on to ICS */
1416     if (count > 0) {
1417         gotEof = 0;
1418         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419         if (outCount < count) {
1420             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1421         }
1422     } else if (count < 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425     } else if (gotEof++ > 0) {
1426         RemoveInputSource(isr);
1427         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1428     }
1429 }
1430
1431 void
1432 KeepAlive()
1433 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434     SendToICS("date\n");
1435     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1436 }
1437
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1440 {
1441     char buffer[MSG_SIZ];
1442     va_list args;
1443
1444     va_start(args, format);
1445     vsnprintf(buffer, sizeof(buffer), format, args);
1446     buffer[sizeof(buffer)-1] = '\0';
1447     SendToICS(buffer);
1448     va_end(args);
1449 }
1450
1451 void
1452 SendToICS(s)
1453      char *s;
1454 {
1455     int count, outCount, outError;
1456
1457     if (icsPR == NULL) return;
1458
1459     count = strlen(s);
1460     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461     if (outCount < count) {
1462         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463     }
1464 }
1465
1466 /* This is used for sending logon scripts to the ICS. Sending
1467    without a delay causes problems when using timestamp on ICC
1468    (at least on my machine). */
1469 void
1470 SendToICSDelayed(s,msdelay)
1471      char *s;
1472      long msdelay;
1473 {
1474     int count, outCount, outError;
1475
1476     if (icsPR == NULL) return;
1477
1478     count = strlen(s);
1479     if (appData.debugMode) {
1480         fprintf(debugFP, ">ICS: ");
1481         show_bytes(debugFP, s, count);
1482         fprintf(debugFP, "\n");
1483     }
1484     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1485                                       msdelay);
1486     if (outCount < count) {
1487         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1488     }
1489 }
1490
1491
1492 /* Remove all highlighting escape sequences in s
1493    Also deletes any suffix starting with '(' 
1494    */
1495 char *
1496 StripHighlightAndTitle(s)
1497      char *s;
1498 {
1499     static char retbuf[MSG_SIZ];
1500     char *p = retbuf;
1501
1502     while (*s != NULLCHAR) {
1503         while (*s == '\033') {
1504             while (*s != NULLCHAR && !isalpha(*s)) s++;
1505             if (*s != NULLCHAR) s++;
1506         }
1507         while (*s != NULLCHAR && *s != '\033') {
1508             if (*s == '(' || *s == '[') {
1509                 *p = NULLCHAR;
1510                 return retbuf;
1511             }
1512             *p++ = *s++;
1513         }
1514     }
1515     *p = NULLCHAR;
1516     return retbuf;
1517 }
1518
1519 /* Remove all highlighting escape sequences in s */
1520 char *
1521 StripHighlight(s)
1522      char *s;
1523 {
1524     static char retbuf[MSG_SIZ];
1525     char *p = retbuf;
1526
1527     while (*s != NULLCHAR) {
1528         while (*s == '\033') {
1529             while (*s != NULLCHAR && !isalpha(*s)) s++;
1530             if (*s != NULLCHAR) s++;
1531         }
1532         while (*s != NULLCHAR && *s != '\033') {
1533             *p++ = *s++;
1534         }
1535     }
1536     *p = NULLCHAR;
1537     return retbuf;
1538 }
1539
1540 char *variantNames[] = VARIANT_NAMES;
1541 char *
1542 VariantName(v)
1543      VariantClass v;
1544 {
1545     return variantNames[v];
1546 }
1547
1548
1549 /* Identify a variant from the strings the chess servers use or the
1550    PGN Variant tag names we use. */
1551 VariantClass
1552 StringToVariant(e)
1553      char *e;
1554 {
1555     char *p;
1556     int wnum = -1;
1557     VariantClass v = VariantNormal;
1558     int i, found = FALSE;
1559     char buf[MSG_SIZ];
1560
1561     if (!e) return v;
1562
1563     /* [HGM] skip over optional board-size prefixes */
1564     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566         while( *e++ != '_');
1567     }
1568
1569     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570         v = VariantNormal;
1571         found = TRUE;
1572     } else
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574       if (StrCaseStr(e, variantNames[i])) {
1575         v = (VariantClass) i;
1576         found = TRUE;
1577         break;
1578       }
1579     }
1580
1581     if (!found) {
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583           || StrCaseStr(e, "wild/fr") 
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585         v = VariantFischeRandom;
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587                  (i = 1, p = StrCaseStr(e, "w"))) {
1588         p += i;
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1590         if (isdigit(*p)) {
1591           wnum = atoi(p);
1592         } else {
1593           wnum = -1;
1594         }
1595         switch (wnum) {
1596         case 0: /* FICS only, actually */
1597         case 1:
1598           /* Castling legal even if K starts on d-file */
1599           v = VariantWildCastle;
1600           break;
1601         case 2:
1602         case 3:
1603         case 4:
1604           /* Castling illegal even if K & R happen to start in
1605              normal positions. */
1606           v = VariantNoCastle;
1607           break;
1608         case 5:
1609         case 7:
1610         case 8:
1611         case 10:
1612         case 11:
1613         case 12:
1614         case 13:
1615         case 14:
1616         case 15:
1617         case 18:
1618         case 19:
1619           /* Castling legal iff K & R start in normal positions */
1620           v = VariantNormal;
1621           break;
1622         case 6:
1623         case 20:
1624         case 21:
1625           /* Special wilds for position setup; unclear what to do here */
1626           v = VariantLoadable;
1627           break;
1628         case 9:
1629           /* Bizarre ICC game */
1630           v = VariantTwoKings;
1631           break;
1632         case 16:
1633           v = VariantKriegspiel;
1634           break;
1635         case 17:
1636           v = VariantLosers;
1637           break;
1638         case 22:
1639           v = VariantFischeRandom;
1640           break;
1641         case 23:
1642           v = VariantCrazyhouse;
1643           break;
1644         case 24:
1645           v = VariantBughouse;
1646           break;
1647         case 25:
1648           v = Variant3Check;
1649           break;
1650         case 26:
1651           /* Not quite the same as FICS suicide! */
1652           v = VariantGiveaway;
1653           break;
1654         case 27:
1655           v = VariantAtomic;
1656           break;
1657         case 28:
1658           v = VariantShatranj;
1659           break;
1660
1661         /* Temporary names for future ICC types.  The name *will* change in 
1662            the next xboard/WinBoard release after ICC defines it. */
1663         case 29:
1664           v = Variant29;
1665           break;
1666         case 30:
1667           v = Variant30;
1668           break;
1669         case 31:
1670           v = Variant31;
1671           break;
1672         case 32:
1673           v = Variant32;
1674           break;
1675         case 33:
1676           v = Variant33;
1677           break;
1678         case 34:
1679           v = Variant34;
1680           break;
1681         case 35:
1682           v = Variant35;
1683           break;
1684         case 36:
1685           v = Variant36;
1686           break;
1687         case 37:
1688           v = VariantShogi;
1689           break;
1690         case 38:
1691           v = VariantXiangqi;
1692           break;
1693         case 39:
1694           v = VariantCourier;
1695           break;
1696         case 40:
1697           v = VariantGothic;
1698           break;
1699         case 41:
1700           v = VariantCapablanca;
1701           break;
1702         case 42:
1703           v = VariantKnightmate;
1704           break;
1705         case 43:
1706           v = VariantFairy;
1707           break;
1708         case 44:
1709           v = VariantCylinder;
1710           break;
1711         case 45:
1712           v = VariantFalcon;
1713           break;
1714         case 46:
1715           v = VariantCapaRandom;
1716           break;
1717         case 47:
1718           v = VariantBerolina;
1719           break;
1720         case 48:
1721           v = VariantJanus;
1722           break;
1723         case 49:
1724           v = VariantSuper;
1725           break;
1726         case 50:
1727           v = VariantGreat;
1728           break;
1729         case -1:
1730           /* Found "wild" or "w" in the string but no number;
1731              must assume it's normal chess. */
1732           v = VariantNormal;
1733           break;
1734         default:
1735           sprintf(buf, _("Unknown wild type %d"), wnum);
1736           DisplayError(buf, 0);
1737           v = VariantUnknown;
1738           break;
1739         }
1740       }
1741     }
1742     if (appData.debugMode) {
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744               e, wnum, VariantName(v));
1745     }
1746     return v;
1747 }
1748
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1751
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753    advance *index beyond it, and set leftover_start to the new value of
1754    *index; else return FALSE.  If pattern contains the character '*', it
1755    matches any sequence of characters not containing '\r', '\n', or the
1756    character following the '*' (if any), and the matched sequence(s) are
1757    copied into star_match.
1758    */
1759 int
1760 looking_at(buf, index, pattern)
1761      char *buf;
1762      int *index;
1763      char *pattern;
1764 {
1765     char *bufp = &buf[*index], *patternp = pattern;
1766     int star_count = 0;
1767     char *matchp = star_match[0];
1768     
1769     for (;;) {
1770         if (*patternp == NULLCHAR) {
1771             *index = leftover_start = bufp - buf;
1772             *matchp = NULLCHAR;
1773             return TRUE;
1774         }
1775         if (*bufp == NULLCHAR) return FALSE;
1776         if (*patternp == '*') {
1777             if (*bufp == *(patternp + 1)) {
1778                 *matchp = NULLCHAR;
1779                 matchp = star_match[++star_count];
1780                 patternp += 2;
1781                 bufp++;
1782                 continue;
1783             } else if (*bufp == '\n' || *bufp == '\r') {
1784                 patternp++;
1785                 if (*patternp == NULLCHAR)
1786                   continue;
1787                 else
1788                   return FALSE;
1789             } else {
1790                 *matchp++ = *bufp++;
1791                 continue;
1792             }
1793         }
1794         if (*patternp != *bufp) return FALSE;
1795         patternp++;
1796         bufp++;
1797     }
1798 }
1799
1800 void
1801 SendToPlayer(data, length)
1802      char *data;
1803      int length;
1804 {
1805     int error, outCount;
1806     outCount = OutputToProcess(NoProc, data, length, &error);
1807     if (outCount < length) {
1808         DisplayFatalError(_("Error writing to display"), error, 1);
1809     }
1810 }
1811
1812 void
1813 PackHolding(packed, holding)
1814      char packed[];
1815      char *holding;
1816 {
1817     char *p = holding;
1818     char *q = packed;
1819     int runlength = 0;
1820     int curr = 9999;
1821     do {
1822         if (*p == curr) {
1823             runlength++;
1824         } else {
1825             switch (runlength) {
1826               case 0:
1827                 break;
1828               case 1:
1829                 *q++ = curr;
1830                 break;
1831               case 2:
1832                 *q++ = curr;
1833                 *q++ = curr;
1834                 break;
1835               default:
1836                 sprintf(q, "%d", runlength);
1837                 while (*q) q++;
1838                 *q++ = curr;
1839                 break;
1840             }
1841             runlength = 1;
1842             curr = *p;
1843         }
1844     } while (*p++);
1845     *q = NULLCHAR;
1846 }
1847
1848 /* Telnet protocol requests from the front end */
1849 void
1850 TelnetRequest(ddww, option)
1851      unsigned char ddww, option;
1852 {
1853     unsigned char msg[3];
1854     int outCount, outError;
1855
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857
1858     if (appData.debugMode) {
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860         switch (ddww) {
1861           case TN_DO:
1862             ddwwStr = "DO";
1863             break;
1864           case TN_DONT:
1865             ddwwStr = "DONT";
1866             break;
1867           case TN_WILL:
1868             ddwwStr = "WILL";
1869             break;
1870           case TN_WONT:
1871             ddwwStr = "WONT";
1872             break;
1873           default:
1874             ddwwStr = buf1;
1875             sprintf(buf1, "%d", ddww);
1876             break;
1877         }
1878         switch (option) {
1879           case TN_ECHO:
1880             optionStr = "ECHO";
1881             break;
1882           default:
1883             optionStr = buf2;
1884             sprintf(buf2, "%d", option);
1885             break;
1886         }
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888     }
1889     msg[0] = TN_IAC;
1890     msg[1] = ddww;
1891     msg[2] = option;
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893     if (outCount < 3) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 void
1899 DoEcho()
1900 {
1901     if (!appData.icsActive) return;
1902     TelnetRequest(TN_DO, TN_ECHO);
1903 }
1904
1905 void
1906 DontEcho()
1907 {
1908     if (!appData.icsActive) return;
1909     TelnetRequest(TN_DONT, TN_ECHO);
1910 }
1911
1912 void
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 {
1915     /* put the holdings sent to us by the server on the board holdings area */
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917     char p;
1918     ChessSquare piece;
1919
1920     if(gameInfo.holdingsWidth < 2)  return;
1921     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922         return; // prevent overwriting by pre-board holdings
1923
1924     if( (int)lowestPiece >= BlackPawn ) {
1925         holdingsColumn = 0;
1926         countsColumn = 1;
1927         holdingsStartRow = BOARD_HEIGHT-1;
1928         direction = -1;
1929     } else {
1930         holdingsColumn = BOARD_WIDTH-1;
1931         countsColumn = BOARD_WIDTH-2;
1932         holdingsStartRow = 0;
1933         direction = 1;
1934     }
1935
1936     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937         board[i][holdingsColumn] = EmptySquare;
1938         board[i][countsColumn]   = (ChessSquare) 0;
1939     }
1940     while( (p=*holdings++) != NULLCHAR ) {
1941         piece = CharToPiece( ToUpper(p) );
1942         if(piece == EmptySquare) continue;
1943         /*j = (int) piece - (int) WhitePawn;*/
1944         j = PieceToNumber(piece);
1945         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946         if(j < 0) continue;               /* should not happen */
1947         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949         board[holdingsStartRow+j*direction][countsColumn]++;
1950     }
1951 }
1952
1953
1954 void
1955 VariantSwitch(Board board, VariantClass newVariant)
1956 {
1957    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1958    Board oldBoard;
1959
1960    startedFromPositionFile = FALSE;
1961    if(gameInfo.variant == newVariant) return;
1962
1963    /* [HGM] This routine is called each time an assignment is made to
1964     * gameInfo.variant during a game, to make sure the board sizes
1965     * are set to match the new variant. If that means adding or deleting
1966     * holdings, we shift the playing board accordingly
1967     * This kludge is needed because in ICS observe mode, we get boards
1968     * of an ongoing game without knowing the variant, and learn about the
1969     * latter only later. This can be because of the move list we requested,
1970     * in which case the game history is refilled from the beginning anyway,
1971     * but also when receiving holdings of a crazyhouse game. In the latter
1972     * case we want to add those holdings to the already received position.
1973     */
1974
1975    
1976    if (appData.debugMode) {
1977      fprintf(debugFP, "Switch board from %s to %s\n",
1978              VariantName(gameInfo.variant), VariantName(newVariant));
1979      setbuf(debugFP, NULL);
1980    }
1981    shuffleOpenings = 0;       /* [HGM] shuffle */
1982    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983    switch(newVariant) 
1984      {
1985      case VariantShogi:
1986        newWidth = 9;  newHeight = 9;
1987        gameInfo.holdingsSize = 7;
1988      case VariantBughouse:
1989      case VariantCrazyhouse:
1990        newHoldingsWidth = 2; break;
1991      case VariantGreat:
1992        newWidth = 10;
1993      case VariantSuper:
1994        newHoldingsWidth = 2;
1995        gameInfo.holdingsSize = 8;
1996        break;
1997      case VariantGothic:
1998      case VariantCapablanca:
1999      case VariantCapaRandom:
2000        newWidth = 10;
2001      default:
2002        newHoldingsWidth = gameInfo.holdingsSize = 0;
2003      };
2004    
2005    if(newWidth  != gameInfo.boardWidth  ||
2006       newHeight != gameInfo.boardHeight ||
2007       newHoldingsWidth != gameInfo.holdingsWidth ) {
2008      
2009      /* shift position to new playing area, if needed */
2010      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011        for(i=0; i<BOARD_HEIGHT; i++) 
2012          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014              board[i][j];
2015        for(i=0; i<newHeight; i++) {
2016          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018        }
2019      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020        for(i=0; i<BOARD_HEIGHT; i++)
2021          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2023              board[i][j];
2024      }
2025      gameInfo.boardWidth  = newWidth;
2026      gameInfo.boardHeight = newHeight;
2027      gameInfo.holdingsWidth = newHoldingsWidth;
2028      gameInfo.variant = newVariant;
2029      InitDrawingSizes(-2, 0);
2030    } else gameInfo.variant = newVariant;
2031    CopyBoard(oldBoard, board);   // remember correctly formatted board
2032      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2033    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2034 }
2035
2036 static int loggedOn = FALSE;
2037
2038 /*-- Game start info cache: --*/
2039 int gs_gamenum;
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\   ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2047
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2050
2051 void
2052 read_from_ics(isr, closure, data, count, error)
2053      InputSourceRef isr;
2054      VOIDSTAR closure;
2055      char *data;
2056      int count;
2057      int error;
2058 {
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2068     
2069     static int started = STARTED_NONE;
2070     static char parse[20000];
2071     static int parse_pos = 0;
2072     static char buf[BUF_SIZE + 1];
2073     static int firstTime = TRUE, intfSet = FALSE;
2074     static ColorClass prevColor = ColorNormal;
2075     static int savingComment = FALSE;
2076     static int cmatch = 0; // continuation sequence match
2077     char *bp;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085     char talker[MSG_SIZ]; // [HGM] chat
2086     int channel;
2087
2088     if (appData.debugMode) {
2089       if (!error) {
2090         fprintf(debugFP, "<ICS: ");
2091         show_bytes(debugFP, data, count);
2092         fprintf(debugFP, "\n");
2093       }
2094     }
2095
2096     if (appData.debugMode) { int f = forwardMostMove;
2097         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2100     }
2101     if (count > 0) {
2102         /* If last read ended with a partial line that we couldn't parse,
2103            prepend it to the new read and try again. */
2104         if (leftover_len > 0) {
2105             for (i=0; i<leftover_len; i++)
2106               buf[i] = buf[leftover_start + i];
2107         }
2108
2109     /* copy new characters into the buffer */
2110     bp = buf + leftover_len;
2111     buf_len=leftover_len;
2112     for (i=0; i<count; i++)
2113     {
2114         // ignore these
2115         if (data[i] == '\r')
2116             continue;
2117
2118         // join lines split by ICS?
2119         if (!appData.noJoin)
2120         {
2121             /*
2122                 Joining just consists of finding matches against the
2123                 continuation sequence, and discarding that sequence
2124                 if found instead of copying it.  So, until a match
2125                 fails, there's nothing to do since it might be the
2126                 complete sequence, and thus, something we don't want
2127                 copied.
2128             */
2129             if (data[i] == cont_seq[cmatch])
2130             {
2131                 cmatch++;
2132                 if (cmatch == strlen(cont_seq))
2133                 {
2134                     cmatch = 0; // complete match.  just reset the counter
2135
2136                     /*
2137                         it's possible for the ICS to not include the space
2138                         at the end of the last word, making our [correct]
2139                         join operation fuse two separate words.  the server
2140                         does this when the space occurs at the width setting.
2141                     */
2142                     if (!buf_len || buf[buf_len-1] != ' ')
2143                     {
2144                         *bp++ = ' ';
2145                         buf_len++;
2146                     }
2147                 }
2148                 continue;
2149             }
2150             else if (cmatch)
2151             {
2152                 /*
2153                     match failed, so we have to copy what matched before
2154                     falling through and copying this character.  In reality,
2155                     this will only ever be just the newline character, but
2156                     it doesn't hurt to be precise.
2157                 */
2158                 strncpy(bp, cont_seq, cmatch);
2159                 bp += cmatch;
2160                 buf_len += cmatch;
2161                 cmatch = 0;
2162             }
2163         }
2164
2165         // copy this char
2166         *bp++ = data[i];
2167         buf_len++;
2168     }
2169
2170         buf[buf_len] = NULLCHAR;
2171 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2172         next_out = 0;
2173         leftover_start = 0;
2174         
2175         i = 0;
2176         while (i < buf_len) {
2177             /* Deal with part of the TELNET option negotiation
2178                protocol.  We refuse to do anything beyond the
2179                defaults, except that we allow the WILL ECHO option,
2180                which ICS uses to turn off password echoing when we are
2181                directly connected to it.  We reject this option
2182                if localLineEditing mode is on (always on in xboard)
2183                and we are talking to port 23, which might be a real
2184                telnet server that will try to keep WILL ECHO on permanently.
2185              */
2186             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2187                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2188                 unsigned char option;
2189                 oldi = i;
2190                 switch ((unsigned char) buf[++i]) {
2191                   case TN_WILL:
2192                     if (appData.debugMode)
2193                       fprintf(debugFP, "\n<WILL ");
2194                     switch (option = (unsigned char) buf[++i]) {
2195                       case TN_ECHO:
2196                         if (appData.debugMode)
2197                           fprintf(debugFP, "ECHO ");
2198                         /* Reply only if this is a change, according
2199                            to the protocol rules. */
2200                         if (remoteEchoOption) break;
2201                         if (appData.localLineEditing &&
2202                             atoi(appData.icsPort) == TN_PORT) {
2203                             TelnetRequest(TN_DONT, TN_ECHO);
2204                         } else {
2205                             EchoOff();
2206                             TelnetRequest(TN_DO, TN_ECHO);
2207                             remoteEchoOption = TRUE;
2208                         }
2209                         break;
2210                       default:
2211                         if (appData.debugMode)
2212                           fprintf(debugFP, "%d ", option);
2213                         /* Whatever this is, we don't want it. */
2214                         TelnetRequest(TN_DONT, option);
2215                         break;
2216                     }
2217                     break;
2218                   case TN_WONT:
2219                     if (appData.debugMode)
2220                       fprintf(debugFP, "\n<WONT ");
2221                     switch (option = (unsigned char) buf[++i]) {
2222                       case TN_ECHO:
2223                         if (appData.debugMode)
2224                           fprintf(debugFP, "ECHO ");
2225                         /* Reply only if this is a change, according
2226                            to the protocol rules. */
2227                         if (!remoteEchoOption) break;
2228                         EchoOn();
2229                         TelnetRequest(TN_DONT, TN_ECHO);
2230                         remoteEchoOption = FALSE;
2231                         break;
2232                       default:
2233                         if (appData.debugMode)
2234                           fprintf(debugFP, "%d ", (unsigned char) option);
2235                         /* Whatever this is, it must already be turned
2236                            off, because we never agree to turn on
2237                            anything non-default, so according to the
2238                            protocol rules, we don't reply. */
2239                         break;
2240                     }
2241                     break;
2242                   case TN_DO:
2243                     if (appData.debugMode)
2244                       fprintf(debugFP, "\n<DO ");
2245                     switch (option = (unsigned char) buf[++i]) {
2246                       default:
2247                         /* Whatever this is, we refuse to do it. */
2248                         if (appData.debugMode)
2249                           fprintf(debugFP, "%d ", option);
2250                         TelnetRequest(TN_WONT, option);
2251                         break;
2252                     }
2253                     break;
2254                   case TN_DONT:
2255                     if (appData.debugMode)
2256                       fprintf(debugFP, "\n<DONT ");
2257                     switch (option = (unsigned char) buf[++i]) {
2258                       default:
2259                         if (appData.debugMode)
2260                           fprintf(debugFP, "%d ", option);
2261                         /* Whatever this is, we are already not doing
2262                            it, because we never agree to do anything
2263                            non-default, so according to the protocol
2264                            rules, we don't reply. */
2265                         break;
2266                     }
2267                     break;
2268                   case TN_IAC:
2269                     if (appData.debugMode)
2270                       fprintf(debugFP, "\n<IAC ");
2271                     /* Doubled IAC; pass it through */
2272                     i--;
2273                     break;
2274                   default:
2275                     if (appData.debugMode)
2276                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2277                     /* Drop all other telnet commands on the floor */
2278                     break;
2279                 }
2280                 if (oldi > next_out)
2281                   SendToPlayer(&buf[next_out], oldi - next_out);
2282                 if (++i > next_out)
2283                   next_out = i;
2284                 continue;
2285             }
2286                 
2287             /* OK, this at least will *usually* work */
2288             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2289                 loggedOn = TRUE;
2290             }
2291             
2292             if (loggedOn && !intfSet) {
2293                 if (ics_type == ICS_ICC) {
2294                   sprintf(str,
2295                           "/set-quietly interface %s\n/set-quietly style 12\n",
2296                           programVersion);
2297                 } else if (ics_type == ICS_CHESSNET) {
2298                   sprintf(str, "/style 12\n");
2299                 } else {
2300                   strcpy(str, "alias $ @\n$set interface ");
2301                   strcat(str, programVersion);
2302                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2303 #ifdef WIN32
2304                   strcat(str, "$iset nohighlight 1\n");
2305 #endif
2306                   strcat(str, "$iset lock 1\n$style 12\n");
2307                 }
2308                 SendToICS(str);
2309                 NotifyFrontendLogin();
2310                 intfSet = TRUE;
2311             }
2312
2313             if (started == STARTED_COMMENT) {
2314                 /* Accumulate characters in comment */
2315                 parse[parse_pos++] = buf[i];
2316                 if (buf[i] == '\n') {
2317                     parse[parse_pos] = NULLCHAR;
2318                     if(chattingPartner>=0) {
2319                         char mess[MSG_SIZ];
2320                         sprintf(mess, "%s%s", talker, parse);
2321                         OutputChatMessage(chattingPartner, mess);
2322                         chattingPartner = -1;
2323                     } else
2324                     if(!suppressKibitz) // [HGM] kibitz
2325                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2326                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2327                         int nrDigit = 0, nrAlph = 0, j;
2328                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2329                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2330                         parse[parse_pos] = NULLCHAR;
2331                         // try to be smart: if it does not look like search info, it should go to
2332                         // ICS interaction window after all, not to engine-output window.
2333                         for(j=0; j<parse_pos; j++) { // count letters and digits
2334                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2335                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2336                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2337                         }
2338                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2339                             int depth=0; float score;
2340                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2341                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2342                                 pvInfoList[forwardMostMove-1].depth = depth;
2343                                 pvInfoList[forwardMostMove-1].score = 100*score;
2344                             }
2345                             OutputKibitz(suppressKibitz, parse);
2346                             next_out = i+1; // [HGM] suppress printing in ICS window
2347                         } else {
2348                             char tmp[MSG_SIZ];
2349                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2350                             SendToPlayer(tmp, strlen(tmp));
2351                         }
2352                     }
2353                     started = STARTED_NONE;
2354                 } else {
2355                     /* Don't match patterns against characters in comment */
2356                     i++;
2357                     continue;
2358                 }
2359             }
2360             if (started == STARTED_CHATTER) {
2361                 if (buf[i] != '\n') {
2362                     /* Don't match patterns against characters in chatter */
2363                     i++;
2364                     continue;
2365                 }
2366                 started = STARTED_NONE;
2367             }
2368
2369             /* Kludge to deal with rcmd protocol */
2370             if (firstTime && looking_at(buf, &i, "\001*")) {
2371                 DisplayFatalError(&buf[1], 0, 1);
2372                 continue;
2373             } else {
2374                 firstTime = FALSE;
2375             }
2376
2377             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2378                 ics_type = ICS_ICC;
2379                 ics_prefix = "/";
2380                 if (appData.debugMode)
2381                   fprintf(debugFP, "ics_type %d\n", ics_type);
2382                 continue;
2383             }
2384             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2385                 ics_type = ICS_FICS;
2386                 ics_prefix = "$";
2387                 if (appData.debugMode)
2388                   fprintf(debugFP, "ics_type %d\n", ics_type);
2389                 continue;
2390             }
2391             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2392                 ics_type = ICS_CHESSNET;
2393                 ics_prefix = "/";
2394                 if (appData.debugMode)
2395                   fprintf(debugFP, "ics_type %d\n", ics_type);
2396                 continue;
2397             }
2398
2399             if (!loggedOn &&
2400                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2401                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2402                  looking_at(buf, &i, "will be \"*\""))) {
2403               strcpy(ics_handle, star_match[0]);
2404               continue;
2405             }
2406
2407             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2408               char buf[MSG_SIZ];
2409               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2410               DisplayIcsInteractionTitle(buf);
2411               have_set_title = TRUE;
2412             }
2413
2414             /* skip finger notes */
2415             if (started == STARTED_NONE &&
2416                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2417                  (buf[i] == '1' && buf[i+1] == '0')) &&
2418                 buf[i+2] == ':' && buf[i+3] == ' ') {
2419               started = STARTED_CHATTER;
2420               i += 3;
2421               continue;
2422             }
2423
2424             /* skip formula vars */
2425             if (started == STARTED_NONE &&
2426                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2427               started = STARTED_CHATTER;
2428               i += 3;
2429               continue;
2430             }
2431
2432             oldi = i;
2433             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2434             if (appData.autoKibitz && started == STARTED_NONE && 
2435                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2436                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2437                 if(looking_at(buf, &i, "* kibitzes: ") &&
2438                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2439                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2440                         suppressKibitz = TRUE;
2441                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2442                                 && (gameMode == IcsPlayingWhite)) ||
2443                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2444                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2445                             started = STARTED_CHATTER; // own kibitz we simply discard
2446                         else {
2447                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2448                             parse_pos = 0; parse[0] = NULLCHAR;
2449                             savingComment = TRUE;
2450                             suppressKibitz = gameMode != IcsObserving ? 2 :
2451                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2452                         } 
2453                         continue;
2454                 } else
2455                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2456                     // suppress the acknowledgements of our own autoKibitz
2457                     SendToPlayer(star_match[0], strlen(star_match[0]));
2458                     looking_at(buf, &i, "*% "); // eat prompt
2459                     next_out = i;
2460                 }
2461             } // [HGM] kibitz: end of patch
2462
2463 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2464
2465             // [HGM] chat: intercept tells by users for which we have an open chat window
2466             channel = -1;
2467             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2468                                            looking_at(buf, &i, "* whispers:") ||
2469                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2470                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2471                 int p;
2472                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2473                 chattingPartner = -1;
2474
2475                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(channel == atoi(chatPartner[p])) {
2478                     talker[0] = '['; strcat(talker, "]");
2479                     chattingPartner = p; break;
2480                     }
2481                 } else
2482                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2483                 for(p=0; p<MAX_CHAT; p++) {
2484                     if(!strcmp("WHISPER", chatPartner[p])) {
2485                         talker[0] = '['; strcat(talker, "]");
2486                         chattingPartner = p; break;
2487                     }
2488                 }
2489                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2490                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2491                     talker[0] = 0;
2492                     chattingPartner = p; break;
2493                 }
2494                 if(chattingPartner<0) i = oldi; else {
2495                     started = STARTED_COMMENT;
2496                     parse_pos = 0; parse[0] = NULLCHAR;
2497                     savingComment = TRUE;
2498                     suppressKibitz = TRUE;
2499                 }
2500             } // [HGM] chat: end of patch
2501
2502             if (appData.zippyTalk || appData.zippyPlay) {
2503                 /* [DM] Backup address for color zippy lines */
2504                 backup = i;
2505 #if ZIPPY
2506        #ifdef WIN32
2507                if (loggedOn == TRUE)
2508                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2509                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2510        #else
2511                 if (ZippyControl(buf, &i) ||
2512                     ZippyConverse(buf, &i) ||
2513                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2514                       loggedOn = TRUE;
2515                       if (!appData.colorize) continue;
2516                 }
2517        #endif
2518 #endif
2519             } // [DM] 'else { ' deleted
2520                 if (
2521                     /* Regular tells and says */
2522                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2523                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2524                     looking_at(buf, &i, "* says: ") ||
2525                     /* Don't color "message" or "messages" output */
2526                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2527                     looking_at(buf, &i, "*. * at *:*: ") ||
2528                     looking_at(buf, &i, "--* (*:*): ") ||
2529                     /* Message notifications (same color as tells) */
2530                     looking_at(buf, &i, "* has left a message ") ||
2531                     looking_at(buf, &i, "* just sent you a message:\n") ||
2532                     /* Whispers and kibitzes */
2533                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2534                     looking_at(buf, &i, "* kibitzes: ") ||
2535                     /* Channel tells */
2536                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2537
2538                   if (tkind == 1 && strchr(star_match[0], ':')) {
2539                       /* Avoid "tells you:" spoofs in channels */
2540                      tkind = 3;
2541                   }
2542                   if (star_match[0][0] == NULLCHAR ||
2543                       strchr(star_match[0], ' ') ||
2544                       (tkind == 3 && strchr(star_match[1], ' '))) {
2545                     /* Reject bogus matches */
2546                     i = oldi;
2547                   } else {
2548                     if (appData.colorize) {
2549                       if (oldi > next_out) {
2550                         SendToPlayer(&buf[next_out], oldi - next_out);
2551                         next_out = oldi;
2552                       }
2553                       switch (tkind) {
2554                       case 1:
2555                         Colorize(ColorTell, FALSE);
2556                         curColor = ColorTell;
2557                         break;
2558                       case 2:
2559                         Colorize(ColorKibitz, FALSE);
2560                         curColor = ColorKibitz;
2561                         break;
2562                       case 3:
2563                         p = strrchr(star_match[1], '(');
2564                         if (p == NULL) {
2565                           p = star_match[1];
2566                         } else {
2567                           p++;
2568                         }
2569                         if (atoi(p) == 1) {
2570                           Colorize(ColorChannel1, FALSE);
2571                           curColor = ColorChannel1;
2572                         } else {
2573                           Colorize(ColorChannel, FALSE);
2574                           curColor = ColorChannel;
2575                         }
2576                         break;
2577                       case 5:
2578                         curColor = ColorNormal;
2579                         break;
2580                       }
2581                     }
2582                     if (started == STARTED_NONE && appData.autoComment &&
2583                         (gameMode == IcsObserving ||
2584                          gameMode == IcsPlayingWhite ||
2585                          gameMode == IcsPlayingBlack)) {
2586                       parse_pos = i - oldi;
2587                       memcpy(parse, &buf[oldi], parse_pos);
2588                       parse[parse_pos] = NULLCHAR;
2589                       started = STARTED_COMMENT;
2590                       savingComment = TRUE;
2591                     } else {
2592                       started = STARTED_CHATTER;
2593                       savingComment = FALSE;
2594                     }
2595                     loggedOn = TRUE;
2596                     continue;
2597                   }
2598                 }
2599
2600                 if (looking_at(buf, &i, "* s-shouts: ") ||
2601                     looking_at(buf, &i, "* c-shouts: ")) {
2602                     if (appData.colorize) {
2603                         if (oldi > next_out) {
2604                             SendToPlayer(&buf[next_out], oldi - next_out);
2605                             next_out = oldi;
2606                         }
2607                         Colorize(ColorSShout, FALSE);
2608                         curColor = ColorSShout;
2609                     }
2610                     loggedOn = TRUE;
2611                     started = STARTED_CHATTER;
2612                     continue;
2613                 }
2614
2615                 if (looking_at(buf, &i, "--->")) {
2616                     loggedOn = TRUE;
2617                     continue;
2618                 }
2619
2620                 if (looking_at(buf, &i, "* shouts: ") ||
2621                     looking_at(buf, &i, "--> ")) {
2622                     if (appData.colorize) {
2623                         if (oldi > next_out) {
2624                             SendToPlayer(&buf[next_out], oldi - next_out);
2625                             next_out = oldi;
2626                         }
2627                         Colorize(ColorShout, FALSE);
2628                         curColor = ColorShout;
2629                     }
2630                     loggedOn = TRUE;
2631                     started = STARTED_CHATTER;
2632                     continue;
2633                 }
2634
2635                 if (looking_at( buf, &i, "Challenge:")) {
2636                     if (appData.colorize) {
2637                         if (oldi > next_out) {
2638                             SendToPlayer(&buf[next_out], oldi - next_out);
2639                             next_out = oldi;
2640                         }
2641                         Colorize(ColorChallenge, FALSE);
2642                         curColor = ColorChallenge;
2643                     }
2644                     loggedOn = TRUE;
2645                     continue;
2646                 }
2647
2648                 if (looking_at(buf, &i, "* offers you") ||
2649                     looking_at(buf, &i, "* offers to be") ||
2650                     looking_at(buf, &i, "* would like to") ||
2651                     looking_at(buf, &i, "* requests to") ||
2652                     looking_at(buf, &i, "Your opponent offers") ||
2653                     looking_at(buf, &i, "Your opponent requests")) {
2654
2655                     if (appData.colorize) {
2656                         if (oldi > next_out) {
2657                             SendToPlayer(&buf[next_out], oldi - next_out);
2658                             next_out = oldi;
2659                         }
2660                         Colorize(ColorRequest, FALSE);
2661                         curColor = ColorRequest;
2662                     }
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* (*) seeking")) {
2667                     if (appData.colorize) {
2668                         if (oldi > next_out) {
2669                             SendToPlayer(&buf[next_out], oldi - next_out);
2670                             next_out = oldi;
2671                         }
2672                         Colorize(ColorSeek, FALSE);
2673                         curColor = ColorSeek;
2674                     }
2675                     continue;
2676             }
2677
2678             if (looking_at(buf, &i, "\\   ")) {
2679                 if (prevColor != ColorNormal) {
2680                     if (oldi > next_out) {
2681                         SendToPlayer(&buf[next_out], oldi - next_out);
2682                         next_out = oldi;
2683                     }
2684                     Colorize(prevColor, TRUE);
2685                     curColor = prevColor;
2686                 }
2687                 if (savingComment) {
2688                     parse_pos = i - oldi;
2689                     memcpy(parse, &buf[oldi], parse_pos);
2690                     parse[parse_pos] = NULLCHAR;
2691                     started = STARTED_COMMENT;
2692                 } else {
2693                     started = STARTED_CHATTER;
2694                 }
2695                 continue;
2696             }
2697
2698             if (looking_at(buf, &i, "Black Strength :") ||
2699                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2700                 looking_at(buf, &i, "<10>") ||
2701                 looking_at(buf, &i, "#@#")) {
2702                 /* Wrong board style */
2703                 loggedOn = TRUE;
2704                 SendToICS(ics_prefix);
2705                 SendToICS("set style 12\n");
2706                 SendToICS(ics_prefix);
2707                 SendToICS("refresh\n");
2708                 continue;
2709             }
2710             
2711             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2712                 ICSInitScript();
2713                 have_sent_ICS_logon = 1;
2714                 continue;
2715             }
2716               
2717             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2718                 (looking_at(buf, &i, "\n<12> ") ||
2719                  looking_at(buf, &i, "<12> "))) {
2720                 loggedOn = TRUE;
2721                 if (oldi > next_out) {
2722                     SendToPlayer(&buf[next_out], oldi - next_out);
2723                 }
2724                 next_out = i;
2725                 started = STARTED_BOARD;
2726                 parse_pos = 0;
2727                 continue;
2728             }
2729
2730             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2731                 looking_at(buf, &i, "<b1> ")) {
2732                 if (oldi > next_out) {
2733                     SendToPlayer(&buf[next_out], oldi - next_out);
2734                 }
2735                 next_out = i;
2736                 started = STARTED_HOLDINGS;
2737                 parse_pos = 0;
2738                 continue;
2739             }
2740
2741             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2742                 loggedOn = TRUE;
2743                 /* Header for a move list -- first line */
2744
2745                 switch (ics_getting_history) {
2746                   case H_FALSE:
2747                     switch (gameMode) {
2748                       case IcsIdle:
2749                       case BeginningOfGame:
2750                         /* User typed "moves" or "oldmoves" while we
2751                            were idle.  Pretend we asked for these
2752                            moves and soak them up so user can step
2753                            through them and/or save them.
2754                            */
2755                         Reset(FALSE, TRUE);
2756                         gameMode = IcsObserving;
2757                         ModeHighlight();
2758                         ics_gamenum = -1;
2759                         ics_getting_history = H_GOT_UNREQ_HEADER;
2760                         break;
2761                       case EditGame: /*?*/
2762                       case EditPosition: /*?*/
2763                         /* Should above feature work in these modes too? */
2764                         /* For now it doesn't */
2765                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2766                         break;
2767                       default:
2768                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2769                         break;
2770                     }
2771                     break;
2772                   case H_REQUESTED:
2773                     /* Is this the right one? */
2774                     if (gameInfo.white && gameInfo.black &&
2775                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2776                         strcmp(gameInfo.black, star_match[2]) == 0) {
2777                         /* All is well */
2778                         ics_getting_history = H_GOT_REQ_HEADER;
2779                     }
2780                     break;
2781                   case H_GOT_REQ_HEADER:
2782                   case H_GOT_UNREQ_HEADER:
2783                   case H_GOT_UNWANTED_HEADER:
2784                   case H_GETTING_MOVES:
2785                     /* Should not happen */
2786                     DisplayError(_("Error gathering move list: two headers"), 0);
2787                     ics_getting_history = H_FALSE;
2788                     break;
2789                 }
2790
2791                 /* Save player ratings into gameInfo if needed */
2792                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2793                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2794                     (gameInfo.whiteRating == -1 ||
2795                      gameInfo.blackRating == -1)) {
2796
2797                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2798                     gameInfo.blackRating = string_to_rating(star_match[3]);
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2801                               gameInfo.whiteRating, gameInfo.blackRating);
2802                 }
2803                 continue;
2804             }
2805
2806             if (looking_at(buf, &i,
2807               "* * match, initial time: * minute*, increment: * second")) {
2808                 /* Header for a move list -- second line */
2809                 /* Initial board will follow if this is a wild game */
2810                 if (gameInfo.event != NULL) free(gameInfo.event);
2811                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2812                 gameInfo.event = StrSave(str);
2813                 /* [HGM] we switched variant. Translate boards if needed. */
2814                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2815                 continue;
2816             }
2817
2818             if (looking_at(buf, &i, "Move  ")) {
2819                 /* Beginning of a move list */
2820                 switch (ics_getting_history) {
2821                   case H_FALSE:
2822                     /* Normally should not happen */
2823                     /* Maybe user hit reset while we were parsing */
2824                     break;
2825                   case H_REQUESTED:
2826                     /* Happens if we are ignoring a move list that is not
2827                      * the one we just requested.  Common if the user
2828                      * tries to observe two games without turning off
2829                      * getMoveList */
2830                     break;
2831                   case H_GETTING_MOVES:
2832                     /* Should not happen */
2833                     DisplayError(_("Error gathering move list: nested"), 0);
2834                     ics_getting_history = H_FALSE;
2835                     break;
2836                   case H_GOT_REQ_HEADER:
2837                     ics_getting_history = H_GETTING_MOVES;
2838                     started = STARTED_MOVES;
2839                     parse_pos = 0;
2840                     if (oldi > next_out) {
2841                         SendToPlayer(&buf[next_out], oldi - next_out);
2842                     }
2843                     break;
2844                   case H_GOT_UNREQ_HEADER:
2845                     ics_getting_history = H_GETTING_MOVES;
2846                     started = STARTED_MOVES_NOHIDE;
2847                     parse_pos = 0;
2848                     break;
2849                   case H_GOT_UNWANTED_HEADER:
2850                     ics_getting_history = H_FALSE;
2851                     break;
2852                 }
2853                 continue;
2854             }                           
2855             
2856             if (looking_at(buf, &i, "% ") ||
2857                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2858                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2859                 if(suppressKibitz) next_out = i;
2860                 savingComment = FALSE;
2861                 suppressKibitz = 0;
2862                 switch (started) {
2863                   case STARTED_MOVES:
2864                   case STARTED_MOVES_NOHIDE:
2865                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2866                     parse[parse_pos + i - oldi] = NULLCHAR;
2867                     ParseGameHistory(parse);
2868 #if ZIPPY
2869                     if (appData.zippyPlay && first.initDone) {
2870                         FeedMovesToProgram(&first, forwardMostMove);
2871                         if (gameMode == IcsPlayingWhite) {
2872                             if (WhiteOnMove(forwardMostMove)) {
2873                                 if (first.sendTime) {
2874                                   if (first.useColors) {
2875                                     SendToProgram("black\n", &first); 
2876                                   }
2877                                   SendTimeRemaining(&first, TRUE);
2878                                 }
2879                                 if (first.useColors) {
2880                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2881                                 }
2882                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2883                                 first.maybeThinking = TRUE;
2884                             } else {
2885                                 if (first.usePlayother) {
2886                                   if (first.sendTime) {
2887                                     SendTimeRemaining(&first, TRUE);
2888                                   }
2889                                   SendToProgram("playother\n", &first);
2890                                   firstMove = FALSE;
2891                                 } else {
2892                                   firstMove = TRUE;
2893                                 }
2894                             }
2895                         } else if (gameMode == IcsPlayingBlack) {
2896                             if (!WhiteOnMove(forwardMostMove)) {
2897                                 if (first.sendTime) {
2898                                   if (first.useColors) {
2899                                     SendToProgram("white\n", &first);
2900                                   }
2901                                   SendTimeRemaining(&first, FALSE);
2902                                 }
2903                                 if (first.useColors) {
2904                                   SendToProgram("black\n", &first);
2905                                 }
2906                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2907                                 first.maybeThinking = TRUE;
2908                             } else {
2909                                 if (first.usePlayother) {
2910                                   if (first.sendTime) {
2911                                     SendTimeRemaining(&first, FALSE);
2912                                   }
2913                                   SendToProgram("playother\n", &first);
2914                                   firstMove = FALSE;
2915                                 } else {
2916                                   firstMove = TRUE;
2917                                 }
2918                             }
2919                         }                       
2920                     }
2921 #endif
2922                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2923                         /* Moves came from oldmoves or moves command
2924                            while we weren't doing anything else.
2925                            */
2926                         currentMove = forwardMostMove;
2927                         ClearHighlights();/*!!could figure this out*/
2928                         flipView = appData.flipView;
2929                         DrawPosition(TRUE, boards[currentMove]);
2930                         DisplayBothClocks();
2931                         sprintf(str, "%s vs. %s",
2932                                 gameInfo.white, gameInfo.black);
2933                         DisplayTitle(str);
2934                         gameMode = IcsIdle;
2935                     } else {
2936                         /* Moves were history of an active game */
2937                         if (gameInfo.resultDetails != NULL) {
2938                             free(gameInfo.resultDetails);
2939                             gameInfo.resultDetails = NULL;
2940                         }
2941                     }
2942                     HistorySet(parseList, backwardMostMove,
2943                                forwardMostMove, currentMove-1);
2944                     DisplayMove(currentMove - 1);
2945                     if (started == STARTED_MOVES) next_out = i;
2946                     started = STARTED_NONE;
2947                     ics_getting_history = H_FALSE;
2948                     break;
2949
2950                   case STARTED_OBSERVE:
2951                     started = STARTED_NONE;
2952                     SendToICS(ics_prefix);
2953                     SendToICS("refresh\n");
2954                     break;
2955
2956                   default:
2957                     break;
2958                 }
2959                 if(bookHit) { // [HGM] book: simulate book reply
2960                     static char bookMove[MSG_SIZ]; // a bit generous?
2961
2962                     programStats.nodes = programStats.depth = programStats.time = 
2963                     programStats.score = programStats.got_only_move = 0;
2964                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2965
2966                     strcpy(bookMove, "move ");
2967                     strcat(bookMove, bookHit);
2968                     HandleMachineMove(bookMove, &first);
2969                 }
2970                 continue;
2971             }
2972             
2973             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2974                  started == STARTED_HOLDINGS ||
2975                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2976                 /* Accumulate characters in move list or board */
2977                 parse[parse_pos++] = buf[i];
2978             }
2979             
2980             /* Start of game messages.  Mostly we detect start of game
2981                when the first board image arrives.  On some versions
2982                of the ICS, though, we need to do a "refresh" after starting
2983                to observe in order to get the current board right away. */
2984             if (looking_at(buf, &i, "Adding game * to observation list")) {
2985                 started = STARTED_OBSERVE;
2986                 continue;
2987             }
2988
2989             /* Handle auto-observe */
2990             if (appData.autoObserve &&
2991                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2992                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2993                 char *player;
2994                 /* Choose the player that was highlighted, if any. */
2995                 if (star_match[0][0] == '\033' ||
2996                     star_match[1][0] != '\033') {
2997                     player = star_match[0];
2998                 } else {
2999                     player = star_match[2];
3000                 }
3001                 sprintf(str, "%sobserve %s\n",
3002                         ics_prefix, StripHighlightAndTitle(player));
3003                 SendToICS(str);
3004
3005                 /* Save ratings from notify string */
3006                 strcpy(player1Name, star_match[0]);
3007                 player1Rating = string_to_rating(star_match[1]);
3008                 strcpy(player2Name, star_match[2]);
3009                 player2Rating = string_to_rating(star_match[3]);
3010
3011                 if (appData.debugMode)
3012                   fprintf(debugFP, 
3013                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3014                           player1Name, player1Rating,
3015                           player2Name, player2Rating);
3016
3017                 continue;
3018             }
3019
3020             /* Deal with automatic examine mode after a game,
3021                and with IcsObserving -> IcsExamining transition */
3022             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3023                 looking_at(buf, &i, "has made you an examiner of game *")) {
3024
3025                 int gamenum = atoi(star_match[0]);
3026                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3027                     gamenum == ics_gamenum) {
3028                     /* We were already playing or observing this game;
3029                        no need to refetch history */
3030                     gameMode = IcsExamining;
3031                     if (pausing) {
3032                         pauseExamForwardMostMove = forwardMostMove;
3033                     } else if (currentMove < forwardMostMove) {
3034                         ForwardInner(forwardMostMove);
3035                     }
3036                 } else {
3037                     /* I don't think this case really can happen */
3038                     SendToICS(ics_prefix);
3039                     SendToICS("refresh\n");
3040                 }
3041                 continue;
3042             }    
3043             
3044             /* Error messages */
3045 //          if (ics_user_moved) {
3046             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3047                 if (looking_at(buf, &i, "Illegal move") ||
3048                     looking_at(buf, &i, "Not a legal move") ||
3049                     looking_at(buf, &i, "Your king is in check") ||
3050                     looking_at(buf, &i, "It isn't your turn") ||
3051                     looking_at(buf, &i, "It is not your move")) {
3052                     /* Illegal move */
3053                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3054                         currentMove = --forwardMostMove;
3055                         DisplayMove(currentMove - 1); /* before DMError */
3056                         DrawPosition(FALSE, boards[currentMove]);
3057                         SwitchClocks();
3058                         DisplayBothClocks();
3059                     }
3060                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3061                     ics_user_moved = 0;
3062                     continue;
3063                 }
3064             }
3065
3066             if (looking_at(buf, &i, "still have time") ||
3067                 looking_at(buf, &i, "not out of time") ||
3068                 looking_at(buf, &i, "either player is out of time") ||
3069                 looking_at(buf, &i, "has timeseal; checking")) {
3070                 /* We must have called his flag a little too soon */
3071                 whiteFlag = blackFlag = FALSE;
3072                 continue;
3073             }
3074
3075             if (looking_at(buf, &i, "added * seconds to") ||
3076                 looking_at(buf, &i, "seconds were added to")) {
3077                 /* Update the clocks */
3078                 SendToICS(ics_prefix);
3079                 SendToICS("refresh\n");
3080                 continue;
3081             }
3082
3083             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3084                 ics_clock_paused = TRUE;
3085                 StopClocks();
3086                 continue;
3087             }
3088
3089             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3090                 ics_clock_paused = FALSE;
3091                 StartClocks();
3092                 continue;
3093             }
3094
3095             /* Grab player ratings from the Creating: message.
3096                Note we have to check for the special case when
3097                the ICS inserts things like [white] or [black]. */
3098             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3099                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3100                 /* star_matches:
3101                    0    player 1 name (not necessarily white)
3102                    1    player 1 rating
3103                    2    empty, white, or black (IGNORED)
3104                    3    player 2 name (not necessarily black)
3105                    4    player 2 rating
3106                    
3107                    The names/ratings are sorted out when the game
3108                    actually starts (below).
3109                 */
3110                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3111                 player1Rating = string_to_rating(star_match[1]);
3112                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3113                 player2Rating = string_to_rating(star_match[4]);
3114
3115                 if (appData.debugMode)
3116                   fprintf(debugFP, 
3117                           "Ratings from 'Creating:' %s %d, %s %d\n",
3118                           player1Name, player1Rating,
3119                           player2Name, player2Rating);
3120
3121                 continue;
3122             }
3123             
3124             /* Improved generic start/end-of-game messages */
3125             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3126                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3127                 /* If tkind == 0: */
3128                 /* star_match[0] is the game number */
3129                 /*           [1] is the white player's name */
3130                 /*           [2] is the black player's name */
3131                 /* For end-of-game: */
3132                 /*           [3] is the reason for the game end */
3133                 /*           [4] is a PGN end game-token, preceded by " " */
3134                 /* For start-of-game: */
3135                 /*           [3] begins with "Creating" or "Continuing" */
3136                 /*           [4] is " *" or empty (don't care). */
3137                 int gamenum = atoi(star_match[0]);
3138                 char *whitename, *blackname, *why, *endtoken;
3139                 ChessMove endtype = (ChessMove) 0;
3140
3141                 if (tkind == 0) {
3142                   whitename = star_match[1];
3143                   blackname = star_match[2];
3144                   why = star_match[3];
3145                   endtoken = star_match[4];
3146                 } else {
3147                   whitename = star_match[1];
3148                   blackname = star_match[3];
3149                   why = star_match[5];
3150                   endtoken = star_match[6];
3151                 }
3152
3153                 /* Game start messages */
3154                 if (strncmp(why, "Creating ", 9) == 0 ||
3155                     strncmp(why, "Continuing ", 11) == 0) {
3156                     gs_gamenum = gamenum;
3157                     strcpy(gs_kind, strchr(why, ' ') + 1);
3158                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3159 #if ZIPPY
3160                     if (appData.zippyPlay) {
3161                         ZippyGameStart(whitename, blackname);
3162                     }
3163 #endif /*ZIPPY*/
3164                     continue;
3165                 }
3166
3167                 /* Game end messages */
3168                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3169                     ics_gamenum != gamenum) {
3170                     continue;
3171                 }
3172                 while (endtoken[0] == ' ') endtoken++;
3173                 switch (endtoken[0]) {
3174                   case '*':
3175                   default:
3176                     endtype = GameUnfinished;
3177                     break;
3178                   case '0':
3179                     endtype = BlackWins;
3180                     break;
3181                   case '1':
3182                     if (endtoken[1] == '/')
3183                       endtype = GameIsDrawn;
3184                     else
3185                       endtype = WhiteWins;
3186                     break;
3187                 }
3188                 GameEnds(endtype, why, GE_ICS);
3189 #if ZIPPY
3190                 if (appData.zippyPlay && first.initDone) {
3191                     ZippyGameEnd(endtype, why);
3192                     if (first.pr == NULL) {
3193                       /* Start the next process early so that we'll
3194                          be ready for the next challenge */
3195                       StartChessProgram(&first);
3196                     }
3197                     /* Send "new" early, in case this command takes
3198                        a long time to finish, so that we'll be ready
3199                        for the next challenge. */
3200                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3201                     Reset(TRUE, TRUE);
3202                 }
3203 #endif /*ZIPPY*/
3204                 continue;
3205             }
3206
3207             if (looking_at(buf, &i, "Removing game * from observation") ||
3208                 looking_at(buf, &i, "no longer observing game *") ||
3209                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3210                 if (gameMode == IcsObserving &&
3211                     atoi(star_match[0]) == ics_gamenum)
3212                   {
3213                       /* icsEngineAnalyze */
3214                       if (appData.icsEngineAnalyze) {
3215                             ExitAnalyzeMode();
3216                             ModeHighlight();
3217                       }
3218                       StopClocks();
3219                       gameMode = IcsIdle;
3220                       ics_gamenum = -1;
3221                       ics_user_moved = FALSE;
3222                   }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i, "no longer examining game *")) {
3227                 if (gameMode == IcsExamining &&
3228                     atoi(star_match[0]) == ics_gamenum)
3229                   {
3230                       gameMode = IcsIdle;
3231                       ics_gamenum = -1;
3232                       ics_user_moved = FALSE;
3233                   }
3234                 continue;
3235             }
3236
3237             /* Advance leftover_start past any newlines we find,
3238                so only partial lines can get reparsed */
3239             if (looking_at(buf, &i, "\n")) {
3240                 prevColor = curColor;
3241                 if (curColor != ColorNormal) {
3242                     if (oldi > next_out) {
3243                         SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = oldi;
3245                     }
3246                     Colorize(ColorNormal, FALSE);
3247                     curColor = ColorNormal;
3248                 }
3249                 if (started == STARTED_BOARD) {
3250                     started = STARTED_NONE;
3251                     parse[parse_pos] = NULLCHAR;
3252                     ParseBoard12(parse);
3253                     ics_user_moved = 0;
3254
3255                     /* Send premove here */
3256                     if (appData.premove) {
3257                       char str[MSG_SIZ];
3258                       if (currentMove == 0 &&
3259                           gameMode == IcsPlayingWhite &&
3260                           appData.premoveWhite) {
3261                         sprintf(str, "%s\n", appData.premoveWhiteText);
3262                         if (appData.debugMode)
3263                           fprintf(debugFP, "Sending premove:\n");
3264                         SendToICS(str);
3265                       } else if (currentMove == 1 &&
3266                                  gameMode == IcsPlayingBlack &&
3267                                  appData.premoveBlack) {
3268                         sprintf(str, "%s\n", appData.premoveBlackText);
3269                         if (appData.debugMode)
3270                           fprintf(debugFP, "Sending premove:\n");
3271                         SendToICS(str);
3272                       } else if (gotPremove) {
3273                         gotPremove = 0;
3274                         ClearPremoveHighlights();
3275                         if (appData.debugMode)
3276                           fprintf(debugFP, "Sending premove:\n");
3277                           UserMoveEvent(premoveFromX, premoveFromY, 
3278                                         premoveToX, premoveToY, 
3279                                         premovePromoChar);
3280                       }
3281                     }
3282
3283                     /* Usually suppress following prompt */
3284                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3285                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3286                         if (looking_at(buf, &i, "*% ")) {
3287                             savingComment = FALSE;
3288                             suppressKibitz = 0;
3289                         }
3290                     }
3291                     next_out = i;
3292                 } else if (started == STARTED_HOLDINGS) {
3293                     int gamenum;
3294                     char new_piece[MSG_SIZ];
3295                     started = STARTED_NONE;
3296                     parse[parse_pos] = NULLCHAR;
3297                     if (appData.debugMode)
3298                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3299                                                         parse, currentMove);
3300                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3301                         gamenum == ics_gamenum) {
3302                         if (gameInfo.variant == VariantNormal) {
3303                           /* [HGM] We seem to switch variant during a game!
3304                            * Presumably no holdings were displayed, so we have
3305                            * to move the position two files to the right to
3306                            * create room for them!
3307                            */
3308                           VariantClass newVariant;
3309                           switch(gameInfo.boardWidth) { // base guess on board width
3310                                 case 9:  newVariant = VariantShogi; break;
3311                                 case 10: newVariant = VariantGreat; break;
3312                                 default: newVariant = VariantCrazyhouse; break;
3313                           }
3314                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3315                           /* Get a move list just to see the header, which
3316                              will tell us whether this is really bug or zh */
3317                           if (ics_getting_history == H_FALSE) {
3318                             ics_getting_history = H_REQUESTED;
3319                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3320                             SendToICS(str);
3321                           }
3322                         }
3323                         new_piece[0] = NULLCHAR;
3324                         sscanf(parse, "game %d white [%s black [%s <- %s",
3325                                &gamenum, white_holding, black_holding,
3326                                new_piece);
3327                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3328                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3329                         /* [HGM] copy holdings to board holdings area */
3330                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3331                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3332                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3333 #if ZIPPY
3334                         if (appData.zippyPlay && first.initDone) {
3335                             ZippyHoldings(white_holding, black_holding,
3336                                           new_piece);
3337                         }
3338 #endif /*ZIPPY*/
3339                         if (tinyLayout || smallLayout) {
3340                             char wh[16], bh[16];
3341                             PackHolding(wh, white_holding);
3342                             PackHolding(bh, black_holding);
3343                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3344                                     gameInfo.white, gameInfo.black);
3345                         } else {
3346                             sprintf(str, "%s [%s] vs. %s [%s]",
3347                                     gameInfo.white, white_holding,
3348                                     gameInfo.black, black_holding);
3349                         }
3350
3351                         DrawPosition(FALSE, boards[currentMove]);
3352                         DisplayTitle(str);
3353                     }
3354                     /* Suppress following prompt */
3355                     if (looking_at(buf, &i, "*% ")) {
3356                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3357                         savingComment = FALSE;
3358                         suppressKibitz = 0;
3359                     }
3360                     next_out = i;
3361                 }
3362                 continue;
3363             }
3364
3365             i++;                /* skip unparsed character and loop back */
3366         }
3367         
3368         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3369 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3370 //          SendToPlayer(&buf[next_out], i - next_out);
3371             started != STARTED_HOLDINGS && leftover_start > next_out) {
3372             SendToPlayer(&buf[next_out], leftover_start - next_out);
3373             next_out = i;
3374         }
3375         
3376         leftover_len = buf_len - leftover_start;
3377         /* if buffer ends with something we couldn't parse,
3378            reparse it after appending the next read */
3379         
3380     } else if (count == 0) {
3381         RemoveInputSource(isr);
3382         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3383     } else {
3384         DisplayFatalError(_("Error reading from ICS"), error, 1);
3385     }
3386 }
3387
3388
3389 /* Board style 12 looks like this:
3390    
3391    <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
3392    
3393  * The "<12> " is stripped before it gets to this routine.  The two
3394  * trailing 0's (flip state and clock ticking) are later addition, and
3395  * some chess servers may not have them, or may have only the first.
3396  * Additional trailing fields may be added in the future.  
3397  */
3398
3399 #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"
3400
3401 #define RELATION_OBSERVING_PLAYED    0
3402 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3403 #define RELATION_PLAYING_MYMOVE      1
3404 #define RELATION_PLAYING_NOTMYMOVE  -1
3405 #define RELATION_EXAMINING           2
3406 #define RELATION_ISOLATED_BOARD     -3
3407 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3408
3409 void
3410 ParseBoard12(string)
3411      char *string;
3412
3413     GameMode newGameMode;
3414     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3415     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3416     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3417     char to_play, board_chars[200];
3418     char move_str[500], str[500], elapsed_time[500];
3419     char black[32], white[32];
3420     Board board;
3421     int prevMove = currentMove;
3422     int ticking = 2;
3423     ChessMove moveType;
3424     int fromX, fromY, toX, toY;
3425     char promoChar;
3426     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3427     char *bookHit = NULL; // [HGM] book
3428     Boolean weird = FALSE, reqFlag = FALSE;
3429
3430     fromX = fromY = toX = toY = -1;
3431     
3432     newGame = FALSE;
3433
3434     if (appData.debugMode)
3435       fprintf(debugFP, _("Parsing board: %s\n"), string);
3436
3437     move_str[0] = NULLCHAR;
3438     elapsed_time[0] = NULLCHAR;
3439     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3440         int  i = 0, j;
3441         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3442             if(string[i] == ' ') { ranks++; files = 0; }
3443             else files++;
3444             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3445             i++;
3446         }
3447         for(j = 0; j <i; j++) board_chars[j] = string[j];
3448         board_chars[i] = '\0';
3449         string += i + 1;
3450     }
3451     n = sscanf(string, PATTERN, &to_play, &double_push,
3452                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3453                &gamenum, white, black, &relation, &basetime, &increment,
3454                &white_stren, &black_stren, &white_time, &black_time,
3455                &moveNum, str, elapsed_time, move_str, &ics_flip,
3456                &ticking);
3457
3458     if (n < 21) {
3459         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3460         DisplayError(str, 0);
3461         return;
3462     }
3463
3464     /* Convert the move number to internal form */
3465     moveNum = (moveNum - 1) * 2;
3466     if (to_play == 'B') moveNum++;
3467     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3468       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3469                         0, 1);
3470       return;
3471     }
3472     
3473     switch (relation) {
3474       case RELATION_OBSERVING_PLAYED:
3475       case RELATION_OBSERVING_STATIC:
3476         if (gamenum == -1) {
3477             /* Old ICC buglet */
3478             relation = RELATION_OBSERVING_STATIC;
3479         }
3480         newGameMode = IcsObserving;
3481         break;
3482       case RELATION_PLAYING_MYMOVE:
3483       case RELATION_PLAYING_NOTMYMOVE:
3484         newGameMode =
3485           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3486             IcsPlayingWhite : IcsPlayingBlack;
3487         break;
3488       case RELATION_EXAMINING:
3489         newGameMode = IcsExamining;
3490         break;
3491       case RELATION_ISOLATED_BOARD:
3492       default:
3493         /* Just display this board.  If user was doing something else,
3494            we will forget about it until the next board comes. */ 
3495         newGameMode = IcsIdle;
3496         break;
3497       case RELATION_STARTING_POSITION:
3498         newGameMode = gameMode;
3499         break;
3500     }
3501     
3502     /* Modify behavior for initial board display on move listing
3503        of wild games.
3504        */
3505     switch (ics_getting_history) {
3506       case H_FALSE:
3507       case H_REQUESTED:
3508         break;
3509       case H_GOT_REQ_HEADER:
3510       case H_GOT_UNREQ_HEADER:
3511         /* This is the initial position of the current game */
3512         gamenum = ics_gamenum;
3513         moveNum = 0;            /* old ICS bug workaround */
3514         if (to_play == 'B') {
3515           startedFromSetupPosition = TRUE;
3516           blackPlaysFirst = TRUE;
3517           moveNum = 1;
3518           if (forwardMostMove == 0) forwardMostMove = 1;
3519           if (backwardMostMove == 0) backwardMostMove = 1;
3520           if (currentMove == 0) currentMove = 1;
3521         }
3522         newGameMode = gameMode;
3523         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3524         break;
3525       case H_GOT_UNWANTED_HEADER:
3526         /* This is an initial board that we don't want */
3527         return;
3528       case H_GETTING_MOVES:
3529         /* Should not happen */
3530         DisplayError(_("Error gathering move list: extra board"), 0);
3531         ics_getting_history = H_FALSE;
3532         return;
3533     }
3534
3535    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3536                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3537      /* [HGM] We seem to have switched variant unexpectedly
3538       * Try to guess new variant from board size
3539       */
3540           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3541           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3542           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3543           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3544           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3545           if(!weird) newVariant = VariantNormal;
3546           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3547           /* Get a move list just to see the header, which
3548              will tell us whether this is really bug or zh */
3549           if (ics_getting_history == H_FALSE) {
3550             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3551             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3552             SendToICS(str);
3553           }
3554     }
3555     
3556     /* Take action if this is the first board of a new game, or of a
3557        different game than is currently being displayed.  */
3558     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3559         relation == RELATION_ISOLATED_BOARD) {
3560         
3561         /* Forget the old game and get the history (if any) of the new one */
3562         if (gameMode != BeginningOfGame) {
3563           Reset(TRUE, TRUE);
3564         }
3565         newGame = TRUE;
3566         if (appData.autoRaiseBoard) BoardToTop();
3567         prevMove = -3;
3568         if (gamenum == -1) {
3569             newGameMode = IcsIdle;
3570         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3571                    appData.getMoveList && !reqFlag) {
3572             /* Need to get game history */
3573             ics_getting_history = H_REQUESTED;
3574             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3575             SendToICS(str);
3576         }
3577         
3578         /* Initially flip the board to have black on the bottom if playing
3579            black or if the ICS flip flag is set, but let the user change
3580            it with the Flip View button. */
3581         flipView = appData.autoFlipView ? 
3582           (newGameMode == IcsPlayingBlack) || ics_flip :
3583           appData.flipView;
3584         
3585         /* Done with values from previous mode; copy in new ones */
3586         gameMode = newGameMode;
3587         ModeHighlight();
3588         ics_gamenum = gamenum;
3589         if (gamenum == gs_gamenum) {
3590             int klen = strlen(gs_kind);
3591             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3592             sprintf(str, "ICS %s", gs_kind);
3593             gameInfo.event = StrSave(str);
3594         } else {
3595             gameInfo.event = StrSave("ICS game");
3596         }
3597         gameInfo.site = StrSave(appData.icsHost);
3598         gameInfo.date = PGNDate();
3599         gameInfo.round = StrSave("-");
3600         gameInfo.white = StrSave(white);
3601         gameInfo.black = StrSave(black);
3602         timeControl = basetime * 60 * 1000;
3603         timeControl_2 = 0;
3604         timeIncrement = increment * 1000;
3605         movesPerSession = 0;
3606         gameInfo.timeControl = TimeControlTagValue();
3607         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3608   if (appData.debugMode) {
3609     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3610     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3611     setbuf(debugFP, NULL);
3612   }
3613
3614         gameInfo.outOfBook = NULL;
3615         
3616         /* Do we have the ratings? */
3617         if (strcmp(player1Name, white) == 0 &&
3618             strcmp(player2Name, black) == 0) {
3619             if (appData.debugMode)
3620               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3621                       player1Rating, player2Rating);
3622             gameInfo.whiteRating = player1Rating;
3623             gameInfo.blackRating = player2Rating;
3624         } else if (strcmp(player2Name, white) == 0 &&
3625                    strcmp(player1Name, black) == 0) {
3626             if (appData.debugMode)
3627               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3628                       player2Rating, player1Rating);
3629             gameInfo.whiteRating = player2Rating;
3630             gameInfo.blackRating = player1Rating;
3631         }
3632         player1Name[0] = player2Name[0] = NULLCHAR;
3633
3634         /* Silence shouts if requested */
3635         if (appData.quietPlay &&
3636             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3637             SendToICS(ics_prefix);
3638             SendToICS("set shout 0\n");
3639         }
3640     }
3641     
3642     /* Deal with midgame name changes */
3643     if (!newGame) {
3644         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3645             if (gameInfo.white) free(gameInfo.white);
3646             gameInfo.white = StrSave(white);
3647         }
3648         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3649             if (gameInfo.black) free(gameInfo.black);
3650             gameInfo.black = StrSave(black);
3651         }
3652     }
3653     
3654     /* Throw away game result if anything actually changes in examine mode */
3655     if (gameMode == IcsExamining && !newGame) {
3656         gameInfo.result = GameUnfinished;
3657         if (gameInfo.resultDetails != NULL) {
3658             free(gameInfo.resultDetails);
3659             gameInfo.resultDetails = NULL;
3660         }
3661     }
3662     
3663     /* In pausing && IcsExamining mode, we ignore boards coming
3664        in if they are in a different variation than we are. */
3665     if (pauseExamInvalid) return;
3666     if (pausing && gameMode == IcsExamining) {
3667         if (moveNum <= pauseExamForwardMostMove) {
3668             pauseExamInvalid = TRUE;
3669             forwardMostMove = pauseExamForwardMostMove;
3670             return;
3671         }
3672     }
3673     
3674   if (appData.debugMode) {
3675     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3676   }
3677     /* Parse the board */
3678     for (k = 0; k < ranks; k++) {
3679       for (j = 0; j < files; j++)
3680         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3681       if(gameInfo.holdingsWidth > 1) {
3682            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3683            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3684       }
3685     }
3686     CopyBoard(boards[moveNum], board);
3687     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3688     if (moveNum == 0) {
3689         startedFromSetupPosition =
3690           !CompareBoards(board, initialPosition);
3691         if(startedFromSetupPosition)
3692             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3693     }
3694
3695     /* [HGM] Set castling rights. Take the outermost Rooks,
3696        to make it also work for FRC opening positions. Note that board12
3697        is really defective for later FRC positions, as it has no way to
3698        indicate which Rook can castle if they are on the same side of King.
3699        For the initial position we grant rights to the outermost Rooks,
3700        and remember thos rights, and we then copy them on positions
3701        later in an FRC game. This means WB might not recognize castlings with
3702        Rooks that have moved back to their original position as illegal,
3703        but in ICS mode that is not its job anyway.
3704     */
3705     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3706     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3707
3708         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3709             if(board[0][i] == WhiteRook) j = i;
3710         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3711         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3712             if(board[0][i] == WhiteRook) j = i;
3713         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3714         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3715             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3716         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3717         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3718             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3719         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3720
3721         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3722         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3723             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3724         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3725             if(board[BOARD_HEIGHT-1][k] == bKing)
3726                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3727         if(gameInfo.variant == VariantTwoKings) {
3728             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3729             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3730             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3731         }
3732     } else { int r;
3733         r = boards[moveNum][CASTLING][0] = initialRights[0];
3734         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3735         r = boards[moveNum][CASTLING][1] = initialRights[1];
3736         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3737         r = boards[moveNum][CASTLING][3] = initialRights[3];
3738         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3739         r = boards[moveNum][CASTLING][4] = initialRights[4];
3740         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3741         /* wildcastle kludge: always assume King has rights */
3742         r = boards[moveNum][CASTLING][2] = initialRights[2];
3743         r = boards[moveNum][CASTLING][5] = initialRights[5];
3744     }
3745     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3746     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3747
3748     
3749     if (ics_getting_history == H_GOT_REQ_HEADER ||
3750         ics_getting_history == H_GOT_UNREQ_HEADER) {
3751         /* This was an initial position from a move list, not
3752            the current position */
3753         return;
3754     }
3755     
3756     /* Update currentMove and known move number limits */
3757     newMove = newGame || moveNum > forwardMostMove;
3758
3759     if (newGame) {
3760         forwardMostMove = backwardMostMove = currentMove = moveNum;
3761         if (gameMode == IcsExamining && moveNum == 0) {
3762           /* Workaround for ICS limitation: we are not told the wild
3763              type when starting to examine a game.  But if we ask for
3764              the move list, the move list header will tell us */
3765             ics_getting_history = H_REQUESTED;
3766             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3767             SendToICS(str);
3768         }
3769     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3770                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3771 #if ZIPPY
3772         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3773         /* [HGM] applied this also to an engine that is silently watching        */
3774         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3775             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3776             gameInfo.variant == currentlyInitializedVariant) {
3777           takeback = forwardMostMove - moveNum;
3778           for (i = 0; i < takeback; i++) {
3779             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3780             SendToProgram("undo\n", &first);
3781           }
3782         }
3783 #endif
3784
3785         forwardMostMove = moveNum;
3786         if (!pausing || currentMove > forwardMostMove)
3787           currentMove = forwardMostMove;
3788     } else {
3789         /* New part of history that is not contiguous with old part */ 
3790         if (pausing && gameMode == IcsExamining) {
3791             pauseExamInvalid = TRUE;
3792             forwardMostMove = pauseExamForwardMostMove;
3793             return;
3794         }
3795         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3796 #if ZIPPY
3797             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3798                 // [HGM] when we will receive the move list we now request, it will be
3799                 // fed to the engine from the first move on. So if the engine is not
3800                 // in the initial position now, bring it there.
3801                 InitChessProgram(&first, 0);
3802             }
3803 #endif
3804             ics_getting_history = H_REQUESTED;
3805             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3806             SendToICS(str);
3807         }
3808         forwardMostMove = backwardMostMove = currentMove = moveNum;
3809     }
3810     
3811     /* Update the clocks */
3812     if (strchr(elapsed_time, '.')) {
3813       /* Time is in ms */
3814       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3815       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3816     } else {
3817       /* Time is in seconds */
3818       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3819       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3820     }
3821       
3822
3823 #if ZIPPY
3824     if (appData.zippyPlay && newGame &&
3825         gameMode != IcsObserving && gameMode != IcsIdle &&
3826         gameMode != IcsExamining)
3827       ZippyFirstBoard(moveNum, basetime, increment);
3828 #endif
3829     
3830     /* Put the move on the move list, first converting
3831        to canonical algebraic form. */
3832     if (moveNum > 0) {
3833   if (appData.debugMode) {
3834     if (appData.debugMode) { int f = forwardMostMove;
3835         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3836                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3837                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3838     }
3839     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3840     fprintf(debugFP, "moveNum = %d\n", moveNum);
3841     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3842     setbuf(debugFP, NULL);
3843   }
3844         if (moveNum <= backwardMostMove) {
3845             /* We don't know what the board looked like before
3846                this move.  Punt. */
3847             strcpy(parseList[moveNum - 1], move_str);
3848             strcat(parseList[moveNum - 1], " ");
3849             strcat(parseList[moveNum - 1], elapsed_time);
3850             moveList[moveNum - 1][0] = NULLCHAR;
3851         } else if (strcmp(move_str, "none") == 0) {
3852             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3853             /* Again, we don't know what the board looked like;
3854                this is really the start of the game. */
3855             parseList[moveNum - 1][0] = NULLCHAR;
3856             moveList[moveNum - 1][0] = NULLCHAR;
3857             backwardMostMove = moveNum;
3858             startedFromSetupPosition = TRUE;
3859             fromX = fromY = toX = toY = -1;
3860         } else {
3861           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3862           //                 So we parse the long-algebraic move string in stead of the SAN move
3863           int valid; char buf[MSG_SIZ], *prom;
3864
3865           // str looks something like "Q/a1-a2"; kill the slash
3866           if(str[1] == '/') 
3867                 sprintf(buf, "%c%s", str[0], str+2);
3868           else  strcpy(buf, str); // might be castling
3869           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3870                 strcat(buf, prom); // long move lacks promo specification!
3871           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3872                 if(appData.debugMode) 
3873                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3874                 strcpy(move_str, buf);
3875           }
3876           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3877                                 &fromX, &fromY, &toX, &toY, &promoChar)
3878                || ParseOneMove(buf, moveNum - 1, &moveType,
3879                                 &fromX, &fromY, &toX, &toY, &promoChar);
3880           // end of long SAN patch
3881           if (valid) {
3882             (void) CoordsToAlgebraic(boards[moveNum - 1],
3883                                      PosFlags(moveNum - 1),
3884                                      fromY, fromX, toY, toX, promoChar,
3885                                      parseList[moveNum-1]);
3886             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3887               case MT_NONE:
3888               case MT_STALEMATE:
3889               default:
3890                 break;
3891               case MT_CHECK:
3892                 if(gameInfo.variant != VariantShogi)
3893                     strcat(parseList[moveNum - 1], "+");
3894                 break;
3895               case MT_CHECKMATE:
3896               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3897                 strcat(parseList[moveNum - 1], "#");
3898                 break;
3899             }
3900             strcat(parseList[moveNum - 1], " ");
3901             strcat(parseList[moveNum - 1], elapsed_time);
3902             /* currentMoveString is set as a side-effect of ParseOneMove */
3903             strcpy(moveList[moveNum - 1], currentMoveString);
3904             strcat(moveList[moveNum - 1], "\n");
3905           } else {
3906             /* Move from ICS was illegal!?  Punt. */
3907   if (appData.debugMode) {
3908     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3909     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3910   }
3911             strcpy(parseList[moveNum - 1], move_str);
3912             strcat(parseList[moveNum - 1], " ");
3913             strcat(parseList[moveNum - 1], elapsed_time);
3914             moveList[moveNum - 1][0] = NULLCHAR;
3915             fromX = fromY = toX = toY = -1;
3916           }
3917         }
3918   if (appData.debugMode) {
3919     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3920     setbuf(debugFP, NULL);
3921   }
3922
3923 #if ZIPPY
3924         /* Send move to chess program (BEFORE animating it). */
3925         if (appData.zippyPlay && !newGame && newMove && 
3926            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3927
3928             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3929                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3930                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3931                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3932                             move_str);
3933                     DisplayError(str, 0);
3934                 } else {
3935                     if (first.sendTime) {
3936                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3937                     }
3938                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3939                     if (firstMove && !bookHit) {
3940                         firstMove = FALSE;
3941                         if (first.useColors) {
3942                           SendToProgram(gameMode == IcsPlayingWhite ?
3943                                         "white\ngo\n" :
3944                                         "black\ngo\n", &first);
3945                         } else {
3946                           SendToProgram("go\n", &first);
3947                         }
3948                         first.maybeThinking = TRUE;
3949                     }
3950                 }
3951             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3952               if (moveList[moveNum - 1][0] == NULLCHAR) {
3953                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3954                 DisplayError(str, 0);
3955               } else {
3956                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3957                 SendMoveToProgram(moveNum - 1, &first);
3958               }
3959             }
3960         }
3961 #endif
3962     }
3963
3964     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3965         /* If move comes from a remote source, animate it.  If it
3966            isn't remote, it will have already been animated. */
3967         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3968             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3969         }
3970         if (!pausing && appData.highlightLastMove) {
3971             SetHighlights(fromX, fromY, toX, toY);
3972         }
3973     }
3974     
3975     /* Start the clocks */
3976     whiteFlag = blackFlag = FALSE;
3977     appData.clockMode = !(basetime == 0 && increment == 0);
3978     if (ticking == 0) {
3979       ics_clock_paused = TRUE;
3980       StopClocks();
3981     } else if (ticking == 1) {
3982       ics_clock_paused = FALSE;
3983     }
3984     if (gameMode == IcsIdle ||
3985         relation == RELATION_OBSERVING_STATIC ||
3986         relation == RELATION_EXAMINING ||
3987         ics_clock_paused)
3988       DisplayBothClocks();
3989     else
3990       StartClocks();
3991     
3992     /* Display opponents and material strengths */
3993     if (gameInfo.variant != VariantBughouse &&
3994         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3995         if (tinyLayout || smallLayout) {
3996             if(gameInfo.variant == VariantNormal)
3997                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3998                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3999                     basetime, increment);
4000             else
4001                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4002                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4003                     basetime, increment, (int) gameInfo.variant);
4004         } else {
4005             if(gameInfo.variant == VariantNormal)
4006                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4007                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4008                     basetime, increment);
4009             else
4010                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4011                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4012                     basetime, increment, VariantName(gameInfo.variant));
4013         }
4014         DisplayTitle(str);
4015   if (appData.debugMode) {
4016     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4017   }
4018     }
4019
4020    
4021     /* Display the board */
4022     if (!pausing && !appData.noGUI) {
4023       
4024       if (appData.premove)
4025           if (!gotPremove || 
4026              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4027              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4028               ClearPremoveHighlights();
4029
4030       DrawPosition(FALSE, boards[currentMove]);
4031       DisplayMove(moveNum - 1);
4032       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4033             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4034               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4035         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4036       }
4037     }
4038
4039     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4040 #if ZIPPY
4041     if(bookHit) { // [HGM] book: simulate book reply
4042         static char bookMove[MSG_SIZ]; // a bit generous?
4043
4044         programStats.nodes = programStats.depth = programStats.time = 
4045         programStats.score = programStats.got_only_move = 0;
4046         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4047
4048         strcpy(bookMove, "move ");
4049         strcat(bookMove, bookHit);
4050         HandleMachineMove(bookMove, &first);
4051     }
4052 #endif
4053 }
4054
4055 void
4056 GetMoveListEvent()
4057 {
4058     char buf[MSG_SIZ];
4059     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4060         ics_getting_history = H_REQUESTED;
4061         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4062         SendToICS(buf);
4063     }
4064 }
4065
4066 void
4067 AnalysisPeriodicEvent(force)
4068      int force;
4069 {
4070     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4071          && !force) || !appData.periodicUpdates)
4072       return;
4073
4074     /* Send . command to Crafty to collect stats */
4075     SendToProgram(".\n", &first);
4076
4077     /* Don't send another until we get a response (this makes
4078        us stop sending to old Crafty's which don't understand
4079        the "." command (sending illegal cmds resets node count & time,
4080        which looks bad)) */
4081     programStats.ok_to_send = 0;
4082 }
4083
4084 void ics_update_width(new_width)
4085         int new_width;
4086 {
4087         ics_printf("set width %d\n", new_width);
4088 }
4089
4090 void
4091 SendMoveToProgram(moveNum, cps)
4092      int moveNum;
4093      ChessProgramState *cps;
4094 {
4095     char buf[MSG_SIZ];
4096
4097     if (cps->useUsermove) {
4098       SendToProgram("usermove ", cps);
4099     }
4100     if (cps->useSAN) {
4101       char *space;
4102       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4103         int len = space - parseList[moveNum];
4104         memcpy(buf, parseList[moveNum], len);
4105         buf[len++] = '\n';
4106         buf[len] = NULLCHAR;
4107       } else {
4108         sprintf(buf, "%s\n", parseList[moveNum]);
4109       }
4110       SendToProgram(buf, cps);
4111     } else {
4112       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4113         AlphaRank(moveList[moveNum], 4);
4114         SendToProgram(moveList[moveNum], cps);
4115         AlphaRank(moveList[moveNum], 4); // and back
4116       } else
4117       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4118        * the engine. It would be nice to have a better way to identify castle 
4119        * moves here. */
4120       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4121                                                                          && cps->useOOCastle) {
4122         int fromX = moveList[moveNum][0] - AAA; 
4123         int fromY = moveList[moveNum][1] - ONE;
4124         int toX = moveList[moveNum][2] - AAA; 
4125         int toY = moveList[moveNum][3] - ONE;
4126         if((boards[moveNum][fromY][fromX] == WhiteKing 
4127             && boards[moveNum][toY][toX] == WhiteRook)
4128            || (boards[moveNum][fromY][fromX] == BlackKing 
4129                && boards[moveNum][toY][toX] == BlackRook)) {
4130           if(toX > fromX) SendToProgram("O-O\n", cps);
4131           else SendToProgram("O-O-O\n", cps);
4132         }
4133         else SendToProgram(moveList[moveNum], cps);
4134       }
4135       else SendToProgram(moveList[moveNum], cps);
4136       /* End of additions by Tord */
4137     }
4138
4139     /* [HGM] setting up the opening has brought engine in force mode! */
4140     /*       Send 'go' if we are in a mode where machine should play. */
4141     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4142         (gameMode == TwoMachinesPlay   ||
4143 #ifdef ZIPPY
4144          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4145 #endif
4146          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4147         SendToProgram("go\n", cps);
4148   if (appData.debugMode) {
4149     fprintf(debugFP, "(extra)\n");
4150   }
4151     }
4152     setboardSpoiledMachineBlack = 0;
4153 }
4154
4155 void
4156 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4157      ChessMove moveType;
4158      int fromX, fromY, toX, toY;
4159 {
4160     char user_move[MSG_SIZ];
4161
4162     switch (moveType) {
4163       default:
4164         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4165                 (int)moveType, fromX, fromY, toX, toY);
4166         DisplayError(user_move + strlen("say "), 0);
4167         break;
4168       case WhiteKingSideCastle:
4169       case BlackKingSideCastle:
4170       case WhiteQueenSideCastleWild:
4171       case BlackQueenSideCastleWild:
4172       /* PUSH Fabien */
4173       case WhiteHSideCastleFR:
4174       case BlackHSideCastleFR:
4175       /* POP Fabien */
4176         sprintf(user_move, "o-o\n");
4177         break;
4178       case WhiteQueenSideCastle:
4179       case BlackQueenSideCastle:
4180       case WhiteKingSideCastleWild:
4181       case BlackKingSideCastleWild:
4182       /* PUSH Fabien */
4183       case WhiteASideCastleFR:
4184       case BlackASideCastleFR:
4185       /* POP Fabien */
4186         sprintf(user_move, "o-o-o\n");
4187         break;
4188       case WhitePromotionQueen:
4189       case BlackPromotionQueen:
4190       case WhitePromotionRook:
4191       case BlackPromotionRook:
4192       case WhitePromotionBishop:
4193       case BlackPromotionBishop:
4194       case WhitePromotionKnight:
4195       case BlackPromotionKnight:
4196       case WhitePromotionKing:
4197       case BlackPromotionKing:
4198       case WhitePromotionChancellor:
4199       case BlackPromotionChancellor:
4200       case WhitePromotionArchbishop:
4201       case BlackPromotionArchbishop:
4202         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4203             sprintf(user_move, "%c%c%c%c=%c\n",
4204                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4205                 PieceToChar(WhiteFerz));
4206         else if(gameInfo.variant == VariantGreat)
4207             sprintf(user_move, "%c%c%c%c=%c\n",
4208                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4209                 PieceToChar(WhiteMan));
4210         else
4211             sprintf(user_move, "%c%c%c%c=%c\n",
4212                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4213                 PieceToChar(PromoPiece(moveType)));
4214         break;
4215       case WhiteDrop:
4216       case BlackDrop:
4217         sprintf(user_move, "%c@%c%c\n",
4218                 ToUpper(PieceToChar((ChessSquare) fromX)),
4219                 AAA + toX, ONE + toY);
4220         break;
4221       case NormalMove:
4222       case WhiteCapturesEnPassant:
4223       case BlackCapturesEnPassant:
4224       case IllegalMove:  /* could be a variant we don't quite understand */
4225         sprintf(user_move, "%c%c%c%c\n",
4226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4227         break;
4228     }
4229     SendToICS(user_move);
4230     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4231         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4232 }
4233
4234 void
4235 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4236      int rf, ff, rt, ft;
4237      char promoChar;
4238      char move[7];
4239 {
4240     if (rf == DROP_RANK) {
4241         sprintf(move, "%c@%c%c\n",
4242                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4243     } else {
4244         if (promoChar == 'x' || promoChar == NULLCHAR) {
4245             sprintf(move, "%c%c%c%c\n",
4246                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4247         } else {
4248             sprintf(move, "%c%c%c%c%c\n",
4249                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4250         }
4251     }
4252 }
4253
4254 void
4255 ProcessICSInitScript(f)
4256      FILE *f;
4257 {
4258     char buf[MSG_SIZ];
4259
4260     while (fgets(buf, MSG_SIZ, f)) {
4261         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4262     }
4263
4264     fclose(f);
4265 }
4266
4267
4268 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4269 void
4270 AlphaRank(char *move, int n)
4271 {
4272 //    char *p = move, c; int x, y;
4273
4274     if (appData.debugMode) {
4275         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4276     }
4277
4278     if(move[1]=='*' && 
4279        move[2]>='0' && move[2]<='9' &&
4280        move[3]>='a' && move[3]<='x'    ) {
4281         move[1] = '@';
4282         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4283         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4284     } else
4285     if(move[0]>='0' && move[0]<='9' &&
4286        move[1]>='a' && move[1]<='x' &&
4287        move[2]>='0' && move[2]<='9' &&
4288        move[3]>='a' && move[3]<='x'    ) {
4289         /* input move, Shogi -> normal */
4290         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4291         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4292         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4293         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4294     } else
4295     if(move[1]=='@' &&
4296        move[3]>='0' && move[3]<='9' &&
4297        move[2]>='a' && move[2]<='x'    ) {
4298         move[1] = '*';
4299         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4300         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4301     } else
4302     if(
4303        move[0]>='a' && move[0]<='x' &&
4304        move[3]>='0' && move[3]<='9' &&
4305        move[2]>='a' && move[2]<='x'    ) {
4306          /* output move, normal -> Shogi */
4307         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4308         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4309         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4310         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4311         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4312     }
4313     if (appData.debugMode) {
4314         fprintf(debugFP, "   out = '%s'\n", move);
4315     }
4316 }
4317
4318 /* Parser for moves from gnuchess, ICS, or user typein box */
4319 Boolean
4320 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4321      char *move;
4322      int moveNum;
4323      ChessMove *moveType;
4324      int *fromX, *fromY, *toX, *toY;
4325      char *promoChar;
4326 {       
4327     if (appData.debugMode) {
4328         fprintf(debugFP, "move to parse: %s\n", move);
4329     }
4330     *moveType = yylexstr(moveNum, move);
4331
4332     switch (*moveType) {
4333       case WhitePromotionChancellor:
4334       case BlackPromotionChancellor:
4335       case WhitePromotionArchbishop:
4336       case BlackPromotionArchbishop:
4337       case WhitePromotionQueen:
4338       case BlackPromotionQueen:
4339       case WhitePromotionRook:
4340       case BlackPromotionRook:
4341       case WhitePromotionBishop:
4342       case BlackPromotionBishop:
4343       case WhitePromotionKnight:
4344       case BlackPromotionKnight:
4345       case WhitePromotionKing:
4346       case BlackPromotionKing:
4347       case NormalMove:
4348       case WhiteCapturesEnPassant:
4349       case BlackCapturesEnPassant:
4350       case WhiteKingSideCastle:
4351       case WhiteQueenSideCastle:
4352       case BlackKingSideCastle:
4353       case BlackQueenSideCastle:
4354       case WhiteKingSideCastleWild:
4355       case WhiteQueenSideCastleWild:
4356       case BlackKingSideCastleWild:
4357       case BlackQueenSideCastleWild:
4358       /* Code added by Tord: */
4359       case WhiteHSideCastleFR:
4360       case WhiteASideCastleFR:
4361       case BlackHSideCastleFR:
4362       case BlackASideCastleFR:
4363       /* End of code added by Tord */
4364       case IllegalMove:         /* bug or odd chess variant */
4365         *fromX = currentMoveString[0] - AAA;
4366         *fromY = currentMoveString[1] - ONE;
4367         *toX = currentMoveString[2] - AAA;
4368         *toY = currentMoveString[3] - ONE;
4369         *promoChar = currentMoveString[4];
4370         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4371             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4372     if (appData.debugMode) {
4373         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4374     }
4375             *fromX = *fromY = *toX = *toY = 0;
4376             return FALSE;
4377         }
4378         if (appData.testLegality) {
4379           return (*moveType != IllegalMove);
4380         } else {
4381           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4382                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4383         }
4384
4385       case WhiteDrop:
4386       case BlackDrop:
4387         *fromX = *moveType == WhiteDrop ?
4388           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4389           (int) CharToPiece(ToLower(currentMoveString[0]));
4390         *fromY = DROP_RANK;
4391         *toX = currentMoveString[2] - AAA;
4392         *toY = currentMoveString[3] - ONE;
4393         *promoChar = NULLCHAR;
4394         return TRUE;
4395
4396       case AmbiguousMove:
4397       case ImpossibleMove:
4398       case (ChessMove) 0:       /* end of file */
4399       case ElapsedTime:
4400       case Comment:
4401       case PGNTag:
4402       case NAG:
4403       case WhiteWins:
4404       case BlackWins:
4405       case GameIsDrawn:
4406       default:
4407     if (appData.debugMode) {
4408         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4409     }
4410         /* bug? */
4411         *fromX = *fromY = *toX = *toY = 0;
4412         *promoChar = NULLCHAR;
4413         return FALSE;
4414     }
4415 }
4416
4417
4418 void
4419 ParsePV(char *pv)
4420 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4421   int fromX, fromY, toX, toY; char promoChar;
4422   ChessMove moveType;
4423   Boolean valid;
4424   int nr = 0;
4425
4426   endPV = forwardMostMove;
4427   do {
4428     while(*pv == ' ') pv++;
4429     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4430     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4431 if(appData.debugMode){
4432 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4433 }
4434     if(!valid && nr == 0 &&
4435        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4436         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4437     }
4438     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4439     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4440     nr++;
4441     if(endPV+1 > framePtr) break; // no space, truncate
4442     if(!valid) break;
4443     endPV++;
4444     CopyBoard(boards[endPV], boards[endPV-1]);
4445     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4446     moveList[endPV-1][0] = fromX + AAA;
4447     moveList[endPV-1][1] = fromY + ONE;
4448     moveList[endPV-1][2] = toX + AAA;
4449     moveList[endPV-1][3] = toY + ONE;
4450     parseList[endPV-1][0] = NULLCHAR;
4451   } while(valid);
4452   currentMove = endPV;
4453   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4454   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4455                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4456   DrawPosition(TRUE, boards[currentMove]);
4457 }
4458
4459 static int lastX, lastY;
4460
4461 Boolean
4462 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4463 {
4464         int startPV;
4465
4466         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4467         lastX = x; lastY = y;
4468         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4469         startPV = index;
4470       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4471       index = startPV;
4472         while(buf[index] && buf[index] != '\n') index++;
4473         buf[index] = 0;
4474         ParsePV(buf+startPV);
4475         *start = startPV; *end = index-1;
4476         return TRUE;
4477 }
4478
4479 Boolean
4480 LoadPV(int x, int y)
4481 { // called on right mouse click to load PV
4482   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4483   lastX = x; lastY = y;
4484   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4485   return TRUE;
4486 }
4487
4488 void
4489 UnLoadPV()
4490 {
4491   if(endPV < 0) return;
4492   endPV = -1;
4493   currentMove = forwardMostMove;
4494   ClearPremoveHighlights();
4495   DrawPosition(TRUE, boards[currentMove]);
4496 }
4497
4498 void
4499 MovePV(int x, int y, int h)
4500 { // step through PV based on mouse coordinates (called on mouse move)
4501   int margin = h>>3, step = 0;
4502
4503   if(endPV < 0) return;
4504   // we must somehow check if right button is still down (might be released off board!)
4505   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4506   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4507   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4508   if(!step) return;
4509   lastX = x; lastY = y;
4510   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4511   currentMove += step;
4512   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4513   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4514                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4515   DrawPosition(FALSE, boards[currentMove]);
4516 }
4517
4518
4519 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4520 // All positions will have equal probability, but the current method will not provide a unique
4521 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4522 #define DARK 1
4523 #define LITE 2
4524 #define ANY 3
4525
4526 int squaresLeft[4];
4527 int piecesLeft[(int)BlackPawn];
4528 int seed, nrOfShuffles;
4529
4530 void GetPositionNumber()
4531 {       // sets global variable seed
4532         int i;
4533
4534         seed = appData.defaultFrcPosition;
4535         if(seed < 0) { // randomize based on time for negative FRC position numbers
4536                 for(i=0; i<50; i++) seed += random();
4537                 seed = random() ^ random() >> 8 ^ random() << 8;
4538                 if(seed<0) seed = -seed;
4539         }
4540 }
4541
4542 int put(Board board, int pieceType, int rank, int n, int shade)
4543 // put the piece on the (n-1)-th empty squares of the given shade
4544 {
4545         int i;
4546
4547         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4548                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4549                         board[rank][i] = (ChessSquare) pieceType;
4550                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4551                         squaresLeft[ANY]--;
4552                         piecesLeft[pieceType]--; 
4553                         return i;
4554                 }
4555         }
4556         return -1;
4557 }
4558
4559
4560 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4561 // calculate where the next piece goes, (any empty square), and put it there
4562 {
4563         int i;
4564
4565         i = seed % squaresLeft[shade];
4566         nrOfShuffles *= squaresLeft[shade];
4567         seed /= squaresLeft[shade];
4568         put(board, pieceType, rank, i, shade);
4569 }
4570
4571 void AddTwoPieces(Board board, int pieceType, int rank)
4572 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4573 {
4574         int i, n=squaresLeft[ANY], j=n-1, k;
4575
4576         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4577         i = seed % k;  // pick one
4578         nrOfShuffles *= k;
4579         seed /= k;
4580         while(i >= j) i -= j--;
4581         j = n - 1 - j; i += j;
4582         put(board, pieceType, rank, j, ANY);
4583         put(board, pieceType, rank, i, ANY);
4584 }
4585
4586 void SetUpShuffle(Board board, int number)
4587 {
4588         int i, p, first=1;
4589
4590         GetPositionNumber(); nrOfShuffles = 1;
4591
4592         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4593         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4594         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4595
4596         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4597
4598         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4599             p = (int) board[0][i];
4600             if(p < (int) BlackPawn) piecesLeft[p] ++;
4601             board[0][i] = EmptySquare;
4602         }
4603
4604         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4605             // shuffles restricted to allow normal castling put KRR first
4606             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4607                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4608             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4609                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4610             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4611                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4612             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4613                 put(board, WhiteRook, 0, 0, ANY);
4614             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4615         }
4616
4617         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4618             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4619             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4620                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4621                 while(piecesLeft[p] >= 2) {
4622                     AddOnePiece(board, p, 0, LITE);
4623                     AddOnePiece(board, p, 0, DARK);
4624                 }
4625                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4626             }
4627
4628         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4629             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4630             // but we leave King and Rooks for last, to possibly obey FRC restriction
4631             if(p == (int)WhiteRook) continue;
4632             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4633             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4634         }
4635
4636         // now everything is placed, except perhaps King (Unicorn) and Rooks
4637
4638         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4639             // Last King gets castling rights
4640             while(piecesLeft[(int)WhiteUnicorn]) {
4641                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4642                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4643             }
4644
4645             while(piecesLeft[(int)WhiteKing]) {
4646                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4647                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4648             }
4649
4650
4651         } else {
4652             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4653             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4654         }
4655
4656         // Only Rooks can be left; simply place them all
4657         while(piecesLeft[(int)WhiteRook]) {
4658                 i = put(board, WhiteRook, 0, 0, ANY);
4659                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4660                         if(first) {
4661                                 first=0;
4662                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4663                         }
4664                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4665                 }
4666         }
4667         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4668             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4669         }
4670
4671         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4672 }
4673
4674 int SetCharTable( char *table, const char * map )
4675 /* [HGM] moved here from winboard.c because of its general usefulness */
4676 /*       Basically a safe strcpy that uses the last character as King */
4677 {
4678     int result = FALSE; int NrPieces;
4679
4680     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4681                     && NrPieces >= 12 && !(NrPieces&1)) {
4682         int i; /* [HGM] Accept even length from 12 to 34 */
4683
4684         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4685         for( i=0; i<NrPieces/2-1; i++ ) {
4686             table[i] = map[i];
4687             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4688         }
4689         table[(int) WhiteKing]  = map[NrPieces/2-1];
4690         table[(int) BlackKing]  = map[NrPieces-1];
4691
4692         result = TRUE;
4693     }
4694
4695     return result;
4696 }
4697
4698 void Prelude(Board board)
4699 {       // [HGM] superchess: random selection of exo-pieces
4700         int i, j, k; ChessSquare p; 
4701         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4702
4703         GetPositionNumber(); // use FRC position number
4704
4705         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4706             SetCharTable(pieceToChar, appData.pieceToCharTable);
4707             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4708                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4709         }
4710
4711         j = seed%4;                 seed /= 4; 
4712         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4713         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4714         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4715         j = seed%3 + (seed%3 >= j); seed /= 3; 
4716         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4717         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4718         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4719         j = seed%3;                 seed /= 3; 
4720         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4721         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4722         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4723         j = seed%2 + (seed%2 >= j); seed /= 2; 
4724         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4725         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4726         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4727         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4728         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4729         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4730         put(board, exoPieces[0],    0, 0, ANY);
4731         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4732 }
4733
4734 void
4735 InitPosition(redraw)
4736      int redraw;
4737 {
4738     ChessSquare (* pieces)[BOARD_FILES];
4739     int i, j, pawnRow, overrule,
4740     oldx = gameInfo.boardWidth,
4741     oldy = gameInfo.boardHeight,
4742     oldh = gameInfo.holdingsWidth,
4743     oldv = gameInfo.variant;
4744
4745     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4746
4747     /* [AS] Initialize pv info list [HGM] and game status */
4748     {
4749         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4750             pvInfoList[i].depth = 0;
4751             boards[i][EP_STATUS] = EP_NONE;
4752             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4753         }
4754
4755         initialRulePlies = 0; /* 50-move counter start */
4756
4757         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4758         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4759     }
4760
4761     
4762     /* [HGM] logic here is completely changed. In stead of full positions */
4763     /* the initialized data only consist of the two backranks. The switch */
4764     /* selects which one we will use, which is than copied to the Board   */
4765     /* initialPosition, which for the rest is initialized by Pawns and    */
4766     /* empty squares. This initial position is then copied to boards[0],  */
4767     /* possibly after shuffling, so that it remains available.            */
4768
4769     gameInfo.holdingsWidth = 0; /* default board sizes */
4770     gameInfo.boardWidth    = 8;
4771     gameInfo.boardHeight   = 8;
4772     gameInfo.holdingsSize  = 0;
4773     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4774     for(i=0; i<BOARD_FILES-2; i++)
4775       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4776     initialPosition[EP_STATUS] = EP_NONE;
4777     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4778
4779     switch (gameInfo.variant) {
4780     case VariantFischeRandom:
4781       shuffleOpenings = TRUE;
4782     default:
4783       pieces = FIDEArray;
4784       break;
4785     case VariantShatranj:
4786       pieces = ShatranjArray;
4787       nrCastlingRights = 0;
4788       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4789       break;
4790     case VariantTwoKings:
4791       pieces = twoKingsArray;
4792       break;
4793     case VariantCapaRandom:
4794       shuffleOpenings = TRUE;
4795     case VariantCapablanca:
4796       pieces = CapablancaArray;
4797       gameInfo.boardWidth = 10;
4798       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4799       break;
4800     case VariantGothic:
4801       pieces = GothicArray;
4802       gameInfo.boardWidth = 10;
4803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4804       break;
4805     case VariantJanus:
4806       pieces = JanusArray;
4807       gameInfo.boardWidth = 10;
4808       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4809       nrCastlingRights = 6;
4810         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4811         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4812         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4813         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4814         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4815         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4816       break;
4817     case VariantFalcon:
4818       pieces = FalconArray;
4819       gameInfo.boardWidth = 10;
4820       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4821       break;
4822     case VariantXiangqi:
4823       pieces = XiangqiArray;
4824       gameInfo.boardWidth  = 9;
4825       gameInfo.boardHeight = 10;
4826       nrCastlingRights = 0;
4827       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4828       break;
4829     case VariantShogi:
4830       pieces = ShogiArray;
4831       gameInfo.boardWidth  = 9;
4832       gameInfo.boardHeight = 9;
4833       gameInfo.holdingsSize = 7;
4834       nrCastlingRights = 0;
4835       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4836       break;
4837     case VariantCourier:
4838       pieces = CourierArray;
4839       gameInfo.boardWidth  = 12;
4840       nrCastlingRights = 0;
4841       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4842       break;
4843     case VariantKnightmate:
4844       pieces = KnightmateArray;
4845       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4846       break;
4847     case VariantFairy:
4848       pieces = fairyArray;
4849       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4850       break;
4851     case VariantGreat:
4852       pieces = GreatArray;
4853       gameInfo.boardWidth = 10;
4854       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4855       gameInfo.holdingsSize = 8;
4856       break;
4857     case VariantSuper:
4858       pieces = FIDEArray;
4859       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4860       gameInfo.holdingsSize = 8;
4861       startedFromSetupPosition = TRUE;
4862       break;
4863     case VariantCrazyhouse:
4864     case VariantBughouse:
4865       pieces = FIDEArray;
4866       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4867       gameInfo.holdingsSize = 5;
4868       break;
4869     case VariantWildCastle:
4870       pieces = FIDEArray;
4871       /* !!?shuffle with kings guaranteed to be on d or e file */
4872       shuffleOpenings = 1;
4873       break;
4874     case VariantNoCastle:
4875       pieces = FIDEArray;
4876       nrCastlingRights = 0;
4877       /* !!?unconstrained back-rank shuffle */
4878       shuffleOpenings = 1;
4879       break;
4880     }
4881
4882     overrule = 0;
4883     if(appData.NrFiles >= 0) {
4884         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4885         gameInfo.boardWidth = appData.NrFiles;
4886     }
4887     if(appData.NrRanks >= 0) {
4888         gameInfo.boardHeight = appData.NrRanks;
4889     }
4890     if(appData.holdingsSize >= 0) {
4891         i = appData.holdingsSize;
4892         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4893         gameInfo.holdingsSize = i;
4894     }
4895     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4896     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4897         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4898
4899     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4900     if(pawnRow < 1) pawnRow = 1;
4901
4902     /* User pieceToChar list overrules defaults */
4903     if(appData.pieceToCharTable != NULL)
4904         SetCharTable(pieceToChar, appData.pieceToCharTable);
4905
4906     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4907
4908         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4909             s = (ChessSquare) 0; /* account holding counts in guard band */
4910         for( i=0; i<BOARD_HEIGHT; i++ )
4911             initialPosition[i][j] = s;
4912
4913         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4914         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4915         initialPosition[pawnRow][j] = WhitePawn;
4916         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4917         if(gameInfo.variant == VariantXiangqi) {
4918             if(j&1) {
4919                 initialPosition[pawnRow][j] = 
4920                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4921                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4922                    initialPosition[2][j] = WhiteCannon;
4923                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4924                 }
4925             }
4926         }
4927         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4928     }
4929     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4930
4931             j=BOARD_LEFT+1;
4932             initialPosition[1][j] = WhiteBishop;
4933             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4934             j=BOARD_RGHT-2;
4935             initialPosition[1][j] = WhiteRook;
4936             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4937     }
4938
4939     if( nrCastlingRights == -1) {
4940         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4941         /*       This sets default castling rights from none to normal corners   */
4942         /* Variants with other castling rights must set them themselves above    */
4943         nrCastlingRights = 6;
4944        
4945         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4946         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4947         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4948         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4949         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4950         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4951      }
4952
4953      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4954      if(gameInfo.variant == VariantGreat) { // promotion commoners
4955         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4956         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4957         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4958         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4959      }
4960   if (appData.debugMode) {
4961     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4962   }
4963     if(shuffleOpenings) {
4964         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4965         startedFromSetupPosition = TRUE;
4966     }
4967     if(startedFromPositionFile) {
4968       /* [HGM] loadPos: use PositionFile for every new game */
4969       CopyBoard(initialPosition, filePosition);
4970       for(i=0; i<nrCastlingRights; i++)
4971           initialRights[i] = filePosition[CASTLING][i];
4972       startedFromSetupPosition = TRUE;
4973     }
4974
4975     CopyBoard(boards[0], initialPosition);
4976
4977     if(oldx != gameInfo.boardWidth ||
4978        oldy != gameInfo.boardHeight ||
4979        oldh != gameInfo.holdingsWidth
4980 #ifdef GOTHIC
4981        || oldv == VariantGothic ||        // For licensing popups
4982        gameInfo.variant == VariantGothic
4983 #endif
4984 #ifdef FALCON
4985        || oldv == VariantFalcon ||
4986        gameInfo.variant == VariantFalcon
4987 #endif
4988                                          )
4989             InitDrawingSizes(-2 ,0);
4990
4991     if (redraw)
4992       DrawPosition(TRUE, boards[currentMove]);
4993 }
4994
4995 void
4996 SendBoard(cps, moveNum)
4997      ChessProgramState *cps;
4998      int moveNum;
4999 {
5000     char message[MSG_SIZ];
5001     
5002     if (cps->useSetboard) {
5003       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5004       sprintf(message, "setboard %s\n", fen);
5005       SendToProgram(message, cps);
5006       free(fen);
5007
5008     } else {
5009       ChessSquare *bp;
5010       int i, j;
5011       /* Kludge to set black to move, avoiding the troublesome and now
5012        * deprecated "black" command.
5013        */
5014       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5015
5016       SendToProgram("edit\n", cps);
5017       SendToProgram("#\n", cps);
5018       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5019         bp = &boards[moveNum][i][BOARD_LEFT];
5020         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5021           if ((int) *bp < (int) BlackPawn) {
5022             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5023                     AAA + j, ONE + i);
5024             if(message[0] == '+' || message[0] == '~') {
5025                 sprintf(message, "%c%c%c+\n",
5026                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5027                         AAA + j, ONE + i);
5028             }
5029             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5030                 message[1] = BOARD_RGHT   - 1 - j + '1';
5031                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5032             }
5033             SendToProgram(message, cps);
5034           }
5035         }
5036       }
5037     
5038       SendToProgram("c\n", cps);
5039       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5040         bp = &boards[moveNum][i][BOARD_LEFT];
5041         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5042           if (((int) *bp != (int) EmptySquare)
5043               && ((int) *bp >= (int) BlackPawn)) {
5044             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5045                     AAA + j, ONE + i);
5046             if(message[0] == '+' || message[0] == '~') {
5047                 sprintf(message, "%c%c%c+\n",
5048                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5049                         AAA + j, ONE + i);
5050             }
5051             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5052                 message[1] = BOARD_RGHT   - 1 - j + '1';
5053                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5054             }
5055             SendToProgram(message, cps);
5056           }
5057         }
5058       }
5059     
5060       SendToProgram(".\n", cps);
5061     }
5062     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5063 }
5064
5065 int
5066 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5067 {
5068     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5069     /* [HGM] add Shogi promotions */
5070     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5071     ChessSquare piece;
5072     ChessMove moveType;
5073     Boolean premove;
5074
5075     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5076     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5077
5078     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5079       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5080         return FALSE;
5081
5082     piece = boards[currentMove][fromY][fromX];
5083     if(gameInfo.variant == VariantShogi) {
5084         promotionZoneSize = 3;
5085         highestPromotingPiece = (int)WhiteFerz;
5086     }
5087
5088     // next weed out all moves that do not touch the promotion zone at all
5089     if((int)piece >= BlackPawn) {
5090         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5091              return FALSE;
5092         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5093     } else {
5094         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5095            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5096     }
5097
5098     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5099
5100     // weed out mandatory Shogi promotions
5101     if(gameInfo.variant == VariantShogi) {
5102         if(piece >= BlackPawn) {
5103             if(toY == 0 && piece == BlackPawn ||
5104                toY == 0 && piece == BlackQueen ||
5105                toY <= 1 && piece == BlackKnight) {
5106                 *promoChoice = '+';
5107                 return FALSE;
5108             }
5109         } else {
5110             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5111                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5112                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5113                 *promoChoice = '+';
5114                 return FALSE;
5115             }
5116         }
5117     }
5118
5119     // weed out obviously illegal Pawn moves
5120     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5121         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5122         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5123         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5124         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5125         // note we are not allowed to test for valid (non-)capture, due to premove
5126     }
5127
5128     // we either have a choice what to promote to, or (in Shogi) whether to promote
5129     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5130         *promoChoice = PieceToChar(BlackFerz);  // no choice
5131         return FALSE;
5132     }
5133     if(appData.alwaysPromoteToQueen) { // predetermined
5134         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5135              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5136         else *promoChoice = PieceToChar(BlackQueen);
5137         return FALSE;
5138     }
5139
5140     // suppress promotion popup on illegal moves that are not premoves
5141     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5142               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5143     if(appData.testLegality && !premove) {
5144         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5145                         fromY, fromX, toY, toX, NULLCHAR);
5146         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5147            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5148             return FALSE;
5149     }
5150
5151     return TRUE;
5152 }
5153
5154 int
5155 InPalace(row, column)
5156      int row, column;
5157 {   /* [HGM] for Xiangqi */
5158     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5159          column < (BOARD_WIDTH + 4)/2 &&
5160          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5161     return FALSE;
5162 }
5163
5164 int
5165 PieceForSquare (x, y)
5166      int x;
5167      int y;
5168 {
5169   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5170      return -1;
5171   else
5172      return boards[currentMove][y][x];
5173 }
5174
5175 int
5176 OKToStartUserMove(x, y)
5177      int x, y;
5178 {
5179     ChessSquare from_piece;
5180     int white_piece;
5181
5182     if (matchMode) return FALSE;
5183     if (gameMode == EditPosition) return TRUE;
5184
5185     if (x >= 0 && y >= 0)
5186       from_piece = boards[currentMove][y][x];
5187     else
5188       from_piece = EmptySquare;
5189
5190     if (from_piece == EmptySquare) return FALSE;
5191
5192     white_piece = (int)from_piece >= (int)WhitePawn &&
5193       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5194
5195     switch (gameMode) {
5196       case PlayFromGameFile:
5197       case AnalyzeFile:
5198       case TwoMachinesPlay:
5199       case EndOfGame:
5200         return FALSE;
5201
5202       case IcsObserving:
5203       case IcsIdle:
5204         return FALSE;
5205
5206       case MachinePlaysWhite:
5207       case IcsPlayingBlack:
5208         if (appData.zippyPlay) return FALSE;
5209         if (white_piece) {
5210             DisplayMoveError(_("You are playing Black"));
5211             return FALSE;
5212         }
5213         break;
5214
5215       case MachinePlaysBlack:
5216       case IcsPlayingWhite:
5217         if (appData.zippyPlay) return FALSE;
5218         if (!white_piece) {
5219             DisplayMoveError(_("You are playing White"));
5220             return FALSE;
5221         }
5222         break;
5223
5224       case EditGame:
5225         if (!white_piece && WhiteOnMove(currentMove)) {
5226             DisplayMoveError(_("It is White's turn"));
5227             return FALSE;
5228         }           
5229         if (white_piece && !WhiteOnMove(currentMove)) {
5230             DisplayMoveError(_("It is Black's turn"));
5231             return FALSE;
5232         }           
5233         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5234             /* Editing correspondence game history */
5235             /* Could disallow this or prompt for confirmation */
5236             cmailOldMove = -1;
5237         }
5238         break;
5239
5240       case BeginningOfGame:
5241         if (appData.icsActive) return FALSE;
5242         if (!appData.noChessProgram) {
5243             if (!white_piece) {
5244                 DisplayMoveError(_("You are playing White"));
5245                 return FALSE;
5246             }
5247         }
5248         break;
5249         
5250       case Training:
5251         if (!white_piece && WhiteOnMove(currentMove)) {
5252             DisplayMoveError(_("It is White's turn"));
5253             return FALSE;
5254         }           
5255         if (white_piece && !WhiteOnMove(currentMove)) {
5256             DisplayMoveError(_("It is Black's turn"));
5257             return FALSE;
5258         }           
5259         break;
5260
5261       default:
5262       case IcsExamining:
5263         break;
5264     }
5265     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5266         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5267         && gameMode != AnalyzeFile && gameMode != Training) {
5268         DisplayMoveError(_("Displayed position is not current"));
5269         return FALSE;
5270     }
5271     return TRUE;
5272 }
5273
5274 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5275 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5276 int lastLoadGameUseList = FALSE;
5277 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5278 ChessMove lastLoadGameStart = (ChessMove) 0;
5279
5280 ChessMove
5281 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5282      int fromX, fromY, toX, toY;
5283      int promoChar;
5284      Boolean captureOwn;
5285 {
5286     ChessMove moveType;
5287     ChessSquare pdown, pup;
5288
5289     /* Check if the user is playing in turn.  This is complicated because we
5290        let the user "pick up" a piece before it is his turn.  So the piece he
5291        tried to pick up may have been captured by the time he puts it down!
5292        Therefore we use the color the user is supposed to be playing in this
5293        test, not the color of the piece that is currently on the starting
5294        square---except in EditGame mode, where the user is playing both
5295        sides; fortunately there the capture race can't happen.  (It can
5296        now happen in IcsExamining mode, but that's just too bad.  The user
5297        will get a somewhat confusing message in that case.)
5298        */
5299
5300     switch (gameMode) {
5301       case PlayFromGameFile:
5302       case AnalyzeFile:
5303       case TwoMachinesPlay:
5304       case EndOfGame:
5305       case IcsObserving:
5306       case IcsIdle:
5307         /* We switched into a game mode where moves are not accepted,
5308            perhaps while the mouse button was down. */
5309         return ImpossibleMove;
5310
5311       case MachinePlaysWhite:
5312         /* User is moving for Black */
5313         if (WhiteOnMove(currentMove)) {
5314             DisplayMoveError(_("It is White's turn"));
5315             return ImpossibleMove;
5316         }
5317         break;
5318
5319       case MachinePlaysBlack:
5320         /* User is moving for White */
5321         if (!WhiteOnMove(currentMove)) {
5322             DisplayMoveError(_("It is Black's turn"));
5323             return ImpossibleMove;
5324         }
5325         break;
5326
5327       case EditGame:
5328       case IcsExamining:
5329       case BeginningOfGame:
5330       case AnalyzeMode:
5331       case Training:
5332         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5333             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5334             /* User is moving for Black */
5335             if (WhiteOnMove(currentMove)) {
5336                 DisplayMoveError(_("It is White's turn"));
5337                 return ImpossibleMove;
5338             }
5339         } else {
5340             /* User is moving for White */
5341             if (!WhiteOnMove(currentMove)) {
5342                 DisplayMoveError(_("It is Black's turn"));
5343                 return ImpossibleMove;
5344             }
5345         }
5346         break;
5347
5348       case IcsPlayingBlack:
5349         /* User is moving for Black */
5350         if (WhiteOnMove(currentMove)) {
5351             if (!appData.premove) {
5352                 DisplayMoveError(_("It is White's turn"));
5353             } else if (toX >= 0 && toY >= 0) {
5354                 premoveToX = toX;
5355                 premoveToY = toY;
5356                 premoveFromX = fromX;
5357                 premoveFromY = fromY;
5358                 premovePromoChar = promoChar;
5359                 gotPremove = 1;
5360                 if (appData.debugMode) 
5361                     fprintf(debugFP, "Got premove: fromX %d,"
5362                             "fromY %d, toX %d, toY %d\n",
5363                             fromX, fromY, toX, toY);
5364             }
5365             return ImpossibleMove;
5366         }
5367         break;
5368
5369       case IcsPlayingWhite:
5370         /* User is moving for White */
5371         if (!WhiteOnMove(currentMove)) {
5372             if (!appData.premove) {
5373                 DisplayMoveError(_("It is Black's turn"));
5374             } else if (toX >= 0 && toY >= 0) {
5375                 premoveToX = toX;
5376                 premoveToY = toY;
5377                 premoveFromX = fromX;
5378                 premoveFromY = fromY;
5379                 premovePromoChar = promoChar;
5380                 gotPremove = 1;
5381                 if (appData.debugMode) 
5382                     fprintf(debugFP, "Got premove: fromX %d,"
5383                             "fromY %d, toX %d, toY %d\n",
5384                             fromX, fromY, toX, toY);
5385             }
5386             return ImpossibleMove;
5387         }
5388         break;
5389
5390       default:
5391         break;
5392
5393       case EditPosition:
5394         /* EditPosition, empty square, or different color piece;
5395            click-click move is possible */
5396         if (toX == -2 || toY == -2) {
5397             boards[0][fromY][fromX] = EmptySquare;
5398             return AmbiguousMove;
5399         } else if (toX >= 0 && toY >= 0) {
5400             boards[0][toY][toX] = boards[0][fromY][fromX];
5401             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5402                 if(boards[0][fromY][0] != EmptySquare) {
5403                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5404                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5405                 }
5406             } else
5407             if(fromX == BOARD_RGHT+1) {
5408                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5409                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5410                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5411                 }
5412             } else
5413             boards[0][fromY][fromX] = EmptySquare;
5414             return AmbiguousMove;
5415         }
5416         return ImpossibleMove;
5417     }
5418
5419     if(toX < 0 || toY < 0) return ImpossibleMove;
5420     pdown = boards[currentMove][fromY][fromX];
5421     pup = boards[currentMove][toY][toX];
5422
5423     /* [HGM] If move started in holdings, it means a drop */
5424     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5425          if( pup != EmptySquare ) return ImpossibleMove;
5426          if(appData.testLegality) {
5427              /* it would be more logical if LegalityTest() also figured out
5428               * which drops are legal. For now we forbid pawns on back rank.
5429               * Shogi is on its own here...
5430               */
5431              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5432                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5433                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5434          }
5435          return WhiteDrop; /* Not needed to specify white or black yet */
5436     }
5437
5438     userOfferedDraw = FALSE;
5439         
5440     /* [HGM] always test for legality, to get promotion info */
5441     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5442                                          fromY, fromX, toY, toX, promoChar);
5443     /* [HGM] but possibly ignore an IllegalMove result */
5444     if (appData.testLegality) {
5445         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5446             DisplayMoveError(_("Illegal move"));
5447             return ImpossibleMove;
5448         }
5449     }
5450
5451     return moveType;
5452     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5453        function is made into one that returns an OK move type if FinishMove
5454        should be called. This to give the calling driver routine the
5455        opportunity to finish the userMove input with a promotion popup,
5456        without bothering the user with this for invalid or illegal moves */
5457
5458 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5459 }
5460
5461 /* Common tail of UserMoveEvent and DropMenuEvent */
5462 int
5463 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5464      ChessMove moveType;
5465      int fromX, fromY, toX, toY;
5466      /*char*/int promoChar;
5467 {
5468     char *bookHit = 0;
5469
5470     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5471         // [HGM] superchess: suppress promotions to non-available piece
5472         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5473         if(WhiteOnMove(currentMove)) {
5474             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5475         } else {
5476             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5477         }
5478     }
5479
5480     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5481        move type in caller when we know the move is a legal promotion */
5482     if(moveType == NormalMove && promoChar)
5483         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5484
5485     /* [HGM] convert drag-and-drop piece drops to standard form */
5486     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5487          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5488            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5489                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5490            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5491            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5492            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5493            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5494          fromY = DROP_RANK;
5495     }
5496
5497     /* [HGM] <popupFix> The following if has been moved here from
5498        UserMoveEvent(). Because it seemed to belong here (why not allow
5499        piece drops in training games?), and because it can only be
5500        performed after it is known to what we promote. */
5501     if (gameMode == Training) {
5502       /* compare the move played on the board to the next move in the
5503        * game. If they match, display the move and the opponent's response. 
5504        * If they don't match, display an error message.
5505        */
5506       int saveAnimate;
5507       Board testBoard;
5508       CopyBoard(testBoard, boards[currentMove]);
5509       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5510
5511       if (CompareBoards(testBoard, boards[currentMove+1])) {
5512         ForwardInner(currentMove+1);
5513
5514         /* Autoplay the opponent's response.
5515          * if appData.animate was TRUE when Training mode was entered,
5516          * the response will be animated.
5517          */
5518         saveAnimate = appData.animate;
5519         appData.animate = animateTraining;
5520         ForwardInner(currentMove+1);
5521         appData.animate = saveAnimate;
5522
5523         /* check for the end of the game */
5524         if (currentMove >= forwardMostMove) {
5525           gameMode = PlayFromGameFile;
5526           ModeHighlight();
5527           SetTrainingModeOff();
5528           DisplayInformation(_("End of game"));
5529         }
5530       } else {
5531         DisplayError(_("Incorrect move"), 0);
5532       }
5533       return 1;
5534     }
5535
5536   /* Ok, now we know that the move is good, so we can kill
5537      the previous line in Analysis Mode */
5538   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5539                                 && currentMove < forwardMostMove) {
5540     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5541   }
5542
5543   /* If we need the chess program but it's dead, restart it */
5544   ResurrectChessProgram();
5545
5546   /* A user move restarts a paused game*/
5547   if (pausing)
5548     PauseEvent();
5549
5550   thinkOutput[0] = NULLCHAR;
5551
5552   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5553
5554   if (gameMode == BeginningOfGame) {
5555     if (appData.noChessProgram) {
5556       gameMode = EditGame;
5557       SetGameInfo();
5558     } else {
5559       char buf[MSG_SIZ];
5560       gameMode = MachinePlaysBlack;
5561       StartClocks();
5562       SetGameInfo();
5563       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5564       DisplayTitle(buf);
5565       if (first.sendName) {
5566         sprintf(buf, "name %s\n", gameInfo.white);
5567         SendToProgram(buf, &first);
5568       }
5569       StartClocks();
5570     }
5571     ModeHighlight();
5572   }
5573
5574   /* Relay move to ICS or chess engine */
5575   if (appData.icsActive) {
5576     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5577         gameMode == IcsExamining) {
5578       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5579       ics_user_moved = 1;
5580     }
5581   } else {
5582     if (first.sendTime && (gameMode == BeginningOfGame ||
5583                            gameMode == MachinePlaysWhite ||
5584                            gameMode == MachinePlaysBlack)) {
5585       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5586     }
5587     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5588          // [HGM] book: if program might be playing, let it use book
5589         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5590         first.maybeThinking = TRUE;
5591     } else SendMoveToProgram(forwardMostMove-1, &first);
5592     if (currentMove == cmailOldMove + 1) {
5593       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5594     }
5595   }
5596
5597   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5598
5599   switch (gameMode) {
5600   case EditGame:
5601     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5602     case MT_NONE:
5603     case MT_CHECK:
5604       break;
5605     case MT_CHECKMATE:
5606     case MT_STAINMATE:
5607       if (WhiteOnMove(currentMove)) {
5608         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5609       } else {
5610         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5611       }
5612       break;
5613     case MT_STALEMATE:
5614       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5615       break;
5616     }
5617     break;
5618     
5619   case MachinePlaysBlack:
5620   case MachinePlaysWhite:
5621     /* disable certain menu options while machine is thinking */
5622     SetMachineThinkingEnables();
5623     break;
5624
5625   default:
5626     break;
5627   }
5628
5629   if(bookHit) { // [HGM] book: simulate book reply
5630         static char bookMove[MSG_SIZ]; // a bit generous?
5631
5632         programStats.nodes = programStats.depth = programStats.time = 
5633         programStats.score = programStats.got_only_move = 0;
5634         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5635
5636         strcpy(bookMove, "move ");
5637         strcat(bookMove, bookHit);
5638         HandleMachineMove(bookMove, &first);
5639   }
5640   return 1;
5641 }
5642
5643 void
5644 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5645      int fromX, fromY, toX, toY;
5646      int promoChar;
5647 {
5648     /* [HGM] This routine was added to allow calling of its two logical
5649        parts from other modules in the old way. Before, UserMoveEvent()
5650        automatically called FinishMove() if the move was OK, and returned
5651        otherwise. I separated the two, in order to make it possible to
5652        slip a promotion popup in between. But that it always needs two
5653        calls, to the first part, (now called UserMoveTest() ), and to
5654        FinishMove if the first part succeeded. Calls that do not need
5655        to do anything in between, can call this routine the old way. 
5656     */
5657     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5658 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5659     if(moveType == AmbiguousMove)
5660         DrawPosition(FALSE, boards[currentMove]);
5661     else if(moveType != ImpossibleMove && moveType != Comment)
5662         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5663 }
5664
5665 void
5666 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5667      Board board;
5668      int flags;
5669      ChessMove kind;
5670      int rf, ff, rt, ft;
5671      VOIDSTAR closure;
5672 {
5673     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5674     Markers *m = (Markers *) closure;
5675     if(rf == fromY && ff == fromX)
5676         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5677                          || kind == WhiteCapturesEnPassant
5678                          || kind == BlackCapturesEnPassant);
5679     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5680 }
5681
5682 void
5683 MarkTargetSquares(int clear)
5684 {
5685   int x, y;
5686   if(!appData.markers || !appData.highlightDragging || 
5687      !appData.testLegality || gameMode == EditPosition) return;
5688   if(clear) {
5689     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5690   } else {
5691     int capt = 0;
5692     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5693     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5694       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5695       if(capt)
5696       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5697     }
5698   }
5699   DrawPosition(TRUE, NULL);
5700 }
5701
5702 void LeftClick(ClickType clickType, int xPix, int yPix)
5703 {
5704     int x, y;
5705     Boolean saveAnimate;
5706     static int second = 0, promotionChoice = 0;
5707     char promoChoice = NULLCHAR;
5708
5709     if (clickType == Press) ErrorPopDown();
5710     MarkTargetSquares(1);
5711
5712     x = EventToSquare(xPix, BOARD_WIDTH);
5713     y = EventToSquare(yPix, BOARD_HEIGHT);
5714     if (!flipView && y >= 0) {
5715         y = BOARD_HEIGHT - 1 - y;
5716     }
5717     if (flipView && x >= 0) {
5718         x = BOARD_WIDTH - 1 - x;
5719     }
5720
5721     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5722         if(clickType == Release) return; // ignore upclick of click-click destination
5723         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5724         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5725         if(gameInfo.holdingsWidth && 
5726                 (WhiteOnMove(currentMove) 
5727                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5728                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5729             // click in right holdings, for determining promotion piece
5730             ChessSquare p = boards[currentMove][y][x];
5731             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5732             if(p != EmptySquare) {
5733                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5734                 fromX = fromY = -1;
5735                 return;
5736             }
5737         }
5738         DrawPosition(FALSE, boards[currentMove]);
5739         return;
5740     }
5741
5742     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5743     if(clickType == Press
5744             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5745               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5746               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5747         return;
5748
5749     if (fromX == -1) {
5750         if (clickType == Press) {
5751             /* First square */
5752             if (OKToStartUserMove(x, y)) {
5753                 fromX = x;
5754                 fromY = y;
5755                 second = 0;
5756                 MarkTargetSquares(0);
5757                 DragPieceBegin(xPix, yPix);
5758                 if (appData.highlightDragging) {
5759                     SetHighlights(x, y, -1, -1);
5760                 }
5761             }
5762         }
5763         return;
5764     }
5765
5766     /* fromX != -1 */
5767     if (clickType == Press && gameMode != EditPosition) {
5768         ChessSquare fromP;
5769         ChessSquare toP;
5770         int frc;
5771
5772         // ignore off-board to clicks
5773         if(y < 0 || x < 0) return;
5774
5775         /* Check if clicking again on the same color piece */
5776         fromP = boards[currentMove][fromY][fromX];
5777         toP = boards[currentMove][y][x];
5778         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5779         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5780              WhitePawn <= toP && toP <= WhiteKing &&
5781              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5782              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5783             (BlackPawn <= fromP && fromP <= BlackKing && 
5784              BlackPawn <= toP && toP <= BlackKing &&
5785              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5786              !(fromP == BlackKing && toP == BlackRook && frc))) {
5787             /* Clicked again on same color piece -- changed his mind */
5788             second = (x == fromX && y == fromY);
5789             if (appData.highlightDragging) {
5790                 SetHighlights(x, y, -1, -1);
5791             } else {
5792                 ClearHighlights();
5793             }
5794             if (OKToStartUserMove(x, y)) {
5795                 fromX = x;
5796                 fromY = y;
5797                 MarkTargetSquares(0);
5798                 DragPieceBegin(xPix, yPix);
5799             }
5800             return;
5801         }
5802         // ignore clicks on holdings
5803         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5804     }
5805
5806     if (clickType == Release && x == fromX && y == fromY) {
5807         DragPieceEnd(xPix, yPix);
5808         if (appData.animateDragging) {
5809             /* Undo animation damage if any */
5810             DrawPosition(FALSE, NULL);
5811         }
5812         if (second) {
5813             /* Second up/down in same square; just abort move */
5814             second = 0;
5815             fromX = fromY = -1;
5816             ClearHighlights();
5817             gotPremove = 0;
5818             ClearPremoveHighlights();
5819         } else {
5820             /* First upclick in same square; start click-click mode */
5821             SetHighlights(x, y, -1, -1);
5822         }
5823         return;
5824     }
5825
5826     /* we now have a different from- and (possibly off-board) to-square */
5827     /* Completed move */
5828     toX = x;
5829     toY = y;
5830     saveAnimate = appData.animate;
5831     if (clickType == Press) {
5832         /* Finish clickclick move */
5833         if (appData.animate || appData.highlightLastMove) {
5834             SetHighlights(fromX, fromY, toX, toY);
5835         } else {
5836             ClearHighlights();
5837         }
5838     } else {
5839         /* Finish drag move */
5840         if (appData.highlightLastMove) {
5841             SetHighlights(fromX, fromY, toX, toY);
5842         } else {
5843             ClearHighlights();
5844         }
5845         DragPieceEnd(xPix, yPix);
5846         /* Don't animate move and drag both */
5847         appData.animate = FALSE;
5848     }
5849
5850     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5851     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5852         ChessSquare piece = boards[currentMove][fromY][fromX];
5853         if(gameMode == EditPosition && piece != EmptySquare &&
5854            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5855             int n;
5856              
5857             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5858                 n = PieceToNumber(piece - (int)BlackPawn);
5859                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5860                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5861                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5862             } else
5863             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5864                 n = PieceToNumber(piece);
5865                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5866                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5867                 boards[currentMove][n][BOARD_WIDTH-2]++;
5868             }
5869             boards[currentMove][fromY][fromX] = EmptySquare;
5870         }
5871         ClearHighlights();
5872         fromX = fromY = -1;
5873         DrawPosition(TRUE, boards[currentMove]);
5874         return;
5875     }
5876
5877     // off-board moves should not be highlighted
5878     if(x < 0 || x < 0) ClearHighlights();
5879
5880     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5881         SetHighlights(fromX, fromY, toX, toY);
5882         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5883             // [HGM] super: promotion to captured piece selected from holdings
5884             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5885             promotionChoice = TRUE;
5886             // kludge follows to temporarily execute move on display, without promoting yet
5887             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5888             boards[currentMove][toY][toX] = p;
5889             DrawPosition(FALSE, boards[currentMove]);
5890             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5891             boards[currentMove][toY][toX] = q;
5892             DisplayMessage("Click in holdings to choose piece", "");
5893             return;
5894         }
5895         PromotionPopUp();
5896     } else {
5897         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5898         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5899         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5900         fromX = fromY = -1;
5901     }
5902     appData.animate = saveAnimate;
5903     if (appData.animate || appData.animateDragging) {
5904         /* Undo animation damage if needed */
5905         DrawPosition(FALSE, NULL);
5906     }
5907 }
5908
5909 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5910 {
5911 //    char * hint = lastHint;
5912     FrontEndProgramStats stats;
5913
5914     stats.which = cps == &first ? 0 : 1;
5915     stats.depth = cpstats->depth;
5916     stats.nodes = cpstats->nodes;
5917     stats.score = cpstats->score;
5918     stats.time = cpstats->time;
5919     stats.pv = cpstats->movelist;
5920     stats.hint = lastHint;
5921     stats.an_move_index = 0;
5922     stats.an_move_count = 0;
5923
5924     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5925         stats.hint = cpstats->move_name;
5926         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5927         stats.an_move_count = cpstats->nr_moves;
5928     }
5929
5930     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5931
5932     SetProgramStats( &stats );
5933 }
5934
5935 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5936 {   // [HGM] book: this routine intercepts moves to simulate book replies
5937     char *bookHit = NULL;
5938
5939     //first determine if the incoming move brings opponent into his book
5940     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5941         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5942     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5943     if(bookHit != NULL && !cps->bookSuspend) {
5944         // make sure opponent is not going to reply after receiving move to book position
5945         SendToProgram("force\n", cps);
5946         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5947     }
5948     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5949     // now arrange restart after book miss
5950     if(bookHit) {
5951         // after a book hit we never send 'go', and the code after the call to this routine
5952         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5953         char buf[MSG_SIZ];
5954         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5955         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5956         SendToProgram(buf, cps);
5957         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5958     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5959         SendToProgram("go\n", cps);
5960         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5961     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5962         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5963             SendToProgram("go\n", cps); 
5964         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5965     }
5966     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5967 }
5968
5969 char *savedMessage;
5970 ChessProgramState *savedState;
5971 void DeferredBookMove(void)
5972 {
5973         if(savedState->lastPing != savedState->lastPong)
5974                     ScheduleDelayedEvent(DeferredBookMove, 10);
5975         else
5976         HandleMachineMove(savedMessage, savedState);
5977 }
5978
5979 void
5980 HandleMachineMove(message, cps)
5981      char *message;
5982      ChessProgramState *cps;
5983 {
5984     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5985     char realname[MSG_SIZ];
5986     int fromX, fromY, toX, toY;
5987     ChessMove moveType;
5988     char promoChar;
5989     char *p;
5990     int machineWhite;
5991     char *bookHit;
5992
5993     cps->userError = 0;
5994
5995 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5996     /*
5997      * Kludge to ignore BEL characters
5998      */
5999     while (*message == '\007') message++;
6000
6001     /*
6002      * [HGM] engine debug message: ignore lines starting with '#' character
6003      */
6004     if(cps->debug && *message == '#') return;
6005
6006     /*
6007      * Look for book output
6008      */
6009     if (cps == &first && bookRequested) {
6010         if (message[0] == '\t' || message[0] == ' ') {
6011             /* Part of the book output is here; append it */
6012             strcat(bookOutput, message);
6013             strcat(bookOutput, "  \n");
6014             return;
6015         } else if (bookOutput[0] != NULLCHAR) {
6016             /* All of book output has arrived; display it */
6017             char *p = bookOutput;
6018             while (*p != NULLCHAR) {
6019                 if (*p == '\t') *p = ' ';
6020                 p++;
6021             }
6022             DisplayInformation(bookOutput);
6023             bookRequested = FALSE;
6024             /* Fall through to parse the current output */
6025         }
6026     }
6027
6028     /*
6029      * Look for machine move.
6030      */
6031     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6032         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6033     {
6034         /* This method is only useful on engines that support ping */
6035         if (cps->lastPing != cps->lastPong) {
6036           if (gameMode == BeginningOfGame) {
6037             /* Extra move from before last new; ignore */
6038             if (appData.debugMode) {
6039                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6040             }
6041           } else {
6042             if (appData.debugMode) {
6043                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6044                         cps->which, gameMode);
6045             }
6046
6047             SendToProgram("undo\n", cps);
6048           }
6049           return;
6050         }
6051
6052         switch (gameMode) {
6053           case BeginningOfGame:
6054             /* Extra move from before last reset; ignore */
6055             if (appData.debugMode) {
6056                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6057             }
6058             return;
6059
6060           case EndOfGame:
6061           case IcsIdle:
6062           default:
6063             /* Extra move after we tried to stop.  The mode test is
6064                not a reliable way of detecting this problem, but it's
6065                the best we can do on engines that don't support ping.
6066             */
6067             if (appData.debugMode) {
6068                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6069                         cps->which, gameMode);
6070             }
6071             SendToProgram("undo\n", cps);
6072             return;
6073
6074           case MachinePlaysWhite:
6075           case IcsPlayingWhite:
6076             machineWhite = TRUE;
6077             break;
6078
6079           case MachinePlaysBlack:
6080           case IcsPlayingBlack:
6081             machineWhite = FALSE;
6082             break;
6083
6084           case TwoMachinesPlay:
6085             machineWhite = (cps->twoMachinesColor[0] == 'w');
6086             break;
6087         }
6088         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6089             if (appData.debugMode) {
6090                 fprintf(debugFP,
6091                         "Ignoring move out of turn by %s, gameMode %d"
6092                         ", forwardMost %d\n",
6093                         cps->which, gameMode, forwardMostMove);
6094             }
6095             return;
6096         }
6097
6098     if (appData.debugMode) { int f = forwardMostMove;
6099         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6100                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6101                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6102     }
6103         if(cps->alphaRank) AlphaRank(machineMove, 4);
6104         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6105                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6106             /* Machine move could not be parsed; ignore it. */
6107             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6108                     machineMove, cps->which);
6109             DisplayError(buf1, 0);
6110             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6111                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6112             if (gameMode == TwoMachinesPlay) {
6113               GameEnds(machineWhite ? BlackWins : WhiteWins,
6114                        buf1, GE_XBOARD);
6115             }
6116             return;
6117         }
6118
6119         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6120         /* So we have to redo legality test with true e.p. status here,  */
6121         /* to make sure an illegal e.p. capture does not slip through,   */
6122         /* to cause a forfeit on a justified illegal-move complaint      */
6123         /* of the opponent.                                              */
6124         if( gameMode==TwoMachinesPlay && appData.testLegality
6125             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6126                                                               ) {
6127            ChessMove moveType;
6128            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6129                              fromY, fromX, toY, toX, promoChar);
6130             if (appData.debugMode) {
6131                 int i;
6132                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6133                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6134                 fprintf(debugFP, "castling rights\n");
6135             }
6136             if(moveType == IllegalMove) {
6137                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6138                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6139                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6140                            buf1, GE_XBOARD);
6141                 return;
6142            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6143            /* [HGM] Kludge to handle engines that send FRC-style castling
6144               when they shouldn't (like TSCP-Gothic) */
6145            switch(moveType) {
6146              case WhiteASideCastleFR:
6147              case BlackASideCastleFR:
6148                toX+=2;
6149                currentMoveString[2]++;
6150                break;
6151              case WhiteHSideCastleFR:
6152              case BlackHSideCastleFR:
6153                toX--;
6154                currentMoveString[2]--;
6155                break;
6156              default: ; // nothing to do, but suppresses warning of pedantic compilers
6157            }
6158         }
6159         hintRequested = FALSE;
6160         lastHint[0] = NULLCHAR;
6161         bookRequested = FALSE;
6162         /* Program may be pondering now */
6163         cps->maybeThinking = TRUE;
6164         if (cps->sendTime == 2) cps->sendTime = 1;
6165         if (cps->offeredDraw) cps->offeredDraw--;
6166
6167 #if ZIPPY
6168         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6169             first.initDone) {
6170           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6171           ics_user_moved = 1;
6172           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6173                 char buf[3*MSG_SIZ];
6174
6175                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6176                         programStats.score / 100.,
6177                         programStats.depth,
6178                         programStats.time / 100.,
6179                         (unsigned int)programStats.nodes,
6180                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6181                         programStats.movelist);
6182                 SendToICS(buf);
6183 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6184           }
6185         }
6186 #endif
6187         /* currentMoveString is set as a side-effect of ParseOneMove */
6188         strcpy(machineMove, currentMoveString);
6189         strcat(machineMove, "\n");
6190         strcpy(moveList[forwardMostMove], machineMove);
6191
6192         /* [AS] Save move info and clear stats for next move */
6193         pvInfoList[ forwardMostMove ].score = programStats.score;
6194         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6195         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6196         ClearProgramStats();
6197         thinkOutput[0] = NULLCHAR;
6198         hiddenThinkOutputState = 0;
6199
6200         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6201
6202         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6203         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6204             int count = 0;
6205
6206             while( count < adjudicateLossPlies ) {
6207                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6208
6209                 if( count & 1 ) {
6210                     score = -score; /* Flip score for winning side */
6211                 }
6212
6213                 if( score > adjudicateLossThreshold ) {
6214                     break;
6215                 }
6216
6217                 count++;
6218             }
6219
6220             if( count >= adjudicateLossPlies ) {
6221                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6222
6223                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6224                     "Xboard adjudication", 
6225                     GE_XBOARD );
6226
6227                 return;
6228             }
6229         }
6230
6231         if( gameMode == TwoMachinesPlay ) {
6232           // [HGM] some adjudications useful with buggy engines
6233             int k, count = 0; static int bare = 1;
6234           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6235
6236
6237             if( appData.testLegality )
6238             {   /* [HGM] Some more adjudications for obstinate engines */
6239                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6240                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6241                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6242                 static int moveCount = 6;
6243                 ChessMove result;
6244                 char *reason = NULL;
6245
6246                 /* Count what is on board. */
6247                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6248                 {   ChessSquare p = boards[forwardMostMove][i][j];
6249                     int m=i;
6250
6251                     switch((int) p)
6252                     {   /* count B,N,R and other of each side */
6253                         case WhiteKing:
6254                         case BlackKing:
6255                              NrK++; break; // [HGM] atomic: count Kings
6256                         case WhiteKnight:
6257                              NrWN++; break;
6258                         case WhiteBishop:
6259                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6260                              bishopsColor |= 1 << ((i^j)&1);
6261                              NrWB++; break;
6262                         case BlackKnight:
6263                              NrBN++; break;
6264                         case BlackBishop:
6265                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6266                              bishopsColor |= 1 << ((i^j)&1);
6267                              NrBB++; break;
6268                         case WhiteRook:
6269                              NrWR++; break;
6270                         case BlackRook:
6271                              NrBR++; break;
6272                         case WhiteQueen:
6273                              NrWQ++; break;
6274                         case BlackQueen:
6275                              NrBQ++; break;
6276                         case EmptySquare: 
6277                              break;
6278                         case BlackPawn:
6279                              m = 7-i;
6280                         case WhitePawn:
6281                              PawnAdvance += m; NrPawns++;
6282                     }
6283                     NrPieces += (p != EmptySquare);
6284                     NrW += ((int)p < (int)BlackPawn);
6285                     if(gameInfo.variant == VariantXiangqi && 
6286                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6287                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6288                         NrW -= ((int)p < (int)BlackPawn);
6289                     }
6290                 }
6291
6292                 /* Some material-based adjudications that have to be made before stalemate test */
6293                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6294                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6295                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6296                      if(appData.checkMates) {
6297                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6298                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6299                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6300                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6301                          return;
6302                      }
6303                 }
6304
6305                 /* Bare King in Shatranj (loses) or Losers (wins) */
6306                 if( NrW == 1 || NrPieces - NrW == 1) {
6307                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6308                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6309                      if(appData.checkMates) {
6310                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6311                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6312                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6313                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6314                          return;
6315                      }
6316                   } else
6317                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6318                   {    /* bare King */
6319                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6320                         if(appData.checkMates) {
6321                             /* but only adjudicate if adjudication enabled */
6322                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6323                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6324                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6325                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6326                             return;
6327                         }
6328                   }
6329                 } else bare = 1;
6330
6331
6332             // don't wait for engine to announce game end if we can judge ourselves
6333             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6334               case MT_CHECK:
6335                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6336                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6337                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6338                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6339                             checkCnt++;
6340                         if(checkCnt >= 2) {
6341                             reason = "Xboard adjudication: 3rd check";
6342                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6343                             break;
6344                         }
6345                     }
6346                 }
6347               case MT_NONE:
6348               default:
6349                 break;
6350               case MT_STALEMATE:
6351               case MT_STAINMATE:
6352                 reason = "Xboard adjudication: Stalemate";
6353                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6354                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6355                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6356                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6357                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6358                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6359                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6360                                                                         EP_CHECKMATE : EP_WINS);
6361                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6362                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6363                 }
6364                 break;
6365               case MT_CHECKMATE:
6366                 reason = "Xboard adjudication: Checkmate";
6367                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6368                 break;
6369             }
6370
6371                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6372                     case EP_STALEMATE:
6373                         result = GameIsDrawn; break;
6374                     case EP_CHECKMATE:
6375                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6376                     case EP_WINS:
6377                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6378                     default:
6379                         result = (ChessMove) 0;
6380                 }
6381                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6382                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6383                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6384                     GameEnds( result, reason, GE_XBOARD );
6385                     return;
6386                 }
6387
6388                 /* Next absolutely insufficient mating material. */
6389                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6390                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6391                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6392                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6393                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6394
6395                      /* always flag draws, for judging claims */
6396                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6397
6398                      if(appData.materialDraws) {
6399                          /* but only adjudicate them if adjudication enabled */
6400                          SendToProgram("force\n", cps->other); // suppress reply
6401                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6402                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6403                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6404                          return;
6405                      }
6406                 }
6407
6408                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6409                 if(NrPieces == 4 && 
6410                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6411                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6412                    || NrWN==2 || NrBN==2     /* KNNK */
6413                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6414                   ) ) {
6415                      if(--moveCount < 0 && appData.trivialDraws)
6416                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6417                           SendToProgram("force\n", cps->other); // suppress reply
6418                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6419                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6420                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6421                           return;
6422                      }
6423                 } else moveCount = 6;
6424             }
6425           }
6426           
6427           if (appData.debugMode) { int i;
6428             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6429                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6430                     appData.drawRepeats);
6431             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6432               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6433             
6434           }
6435
6436                 /* Check for rep-draws */
6437                 count = 0;
6438                 for(k = forwardMostMove-2;
6439                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6440                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6441                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6442                     k-=2)
6443                 {   int rights=0;
6444                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6445                         /* compare castling rights */
6446                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6447                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6448                                 rights++; /* King lost rights, while rook still had them */
6449                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6450                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6451                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6452                                    rights++; /* but at least one rook lost them */
6453                         }
6454                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6455                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6456                                 rights++; 
6457                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6458                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6459                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6460                                    rights++;
6461                         }
6462                         if( rights == 0 && ++count > appData.drawRepeats-2
6463                             && appData.drawRepeats > 1) {
6464                              /* adjudicate after user-specified nr of repeats */
6465                              SendToProgram("force\n", cps->other); // suppress reply
6466                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6467                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6468                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6469                                 // [HGM] xiangqi: check for forbidden perpetuals
6470                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6471                                 for(m=forwardMostMove; m>k; m-=2) {
6472                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6473                                         ourPerpetual = 0; // the current mover did not always check
6474                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6475                                         hisPerpetual = 0; // the opponent did not always check
6476                                 }
6477                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6478                                                                         ourPerpetual, hisPerpetual);
6479                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6480                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6481                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6482                                     return;
6483                                 }
6484                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6485                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6486                                 // Now check for perpetual chases
6487                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6488                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6489                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6490                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6491                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6492                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6493                                         return;
6494                                     }
6495                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6496                                         break; // Abort repetition-checking loop.
6497                                 }
6498                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6499                              }
6500                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6501                              return;
6502                         }
6503                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6504                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6505                     }
6506                 }
6507
6508                 /* Now we test for 50-move draws. Determine ply count */
6509                 count = forwardMostMove;
6510                 /* look for last irreversble move */
6511                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6512                     count--;
6513                 /* if we hit starting position, add initial plies */
6514                 if( count == backwardMostMove )
6515                     count -= initialRulePlies;
6516                 count = forwardMostMove - count; 
6517                 if( count >= 100)
6518                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6519                          /* this is used to judge if draw claims are legal */
6520                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6521                          SendToProgram("force\n", cps->other); // suppress reply
6522                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6523                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6524                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6525                          return;
6526                 }
6527
6528                 /* if draw offer is pending, treat it as a draw claim
6529                  * when draw condition present, to allow engines a way to
6530                  * claim draws before making their move to avoid a race
6531                  * condition occurring after their move
6532                  */
6533                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6534                          char *p = NULL;
6535                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6536                              p = "Draw claim: 50-move rule";
6537                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6538                              p = "Draw claim: 3-fold repetition";
6539                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6540                              p = "Draw claim: insufficient mating material";
6541                          if( p != NULL ) {
6542                              SendToProgram("force\n", cps->other); // suppress reply
6543                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6544                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6545                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6546                              return;
6547                          }
6548                 }
6549
6550
6551                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6552                     SendToProgram("force\n", cps->other); // suppress reply
6553                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6554                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6555
6556                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6557
6558                     return;
6559                 }
6560         }
6561
6562         bookHit = NULL;
6563         if (gameMode == TwoMachinesPlay) {
6564             /* [HGM] relaying draw offers moved to after reception of move */
6565             /* and interpreting offer as claim if it brings draw condition */
6566             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6567                 SendToProgram("draw\n", cps->other);
6568             }
6569             if (cps->other->sendTime) {
6570                 SendTimeRemaining(cps->other,
6571                                   cps->other->twoMachinesColor[0] == 'w');
6572             }
6573             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6574             if (firstMove && !bookHit) {
6575                 firstMove = FALSE;
6576                 if (cps->other->useColors) {
6577                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6578                 }
6579                 SendToProgram("go\n", cps->other);
6580             }
6581             cps->other->maybeThinking = TRUE;
6582         }
6583
6584         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6585         
6586         if (!pausing && appData.ringBellAfterMoves) {
6587             RingBell();
6588         }
6589
6590         /* 
6591          * Reenable menu items that were disabled while
6592          * machine was thinking
6593          */
6594         if (gameMode != TwoMachinesPlay)
6595             SetUserThinkingEnables();
6596
6597         // [HGM] book: after book hit opponent has received move and is now in force mode
6598         // force the book reply into it, and then fake that it outputted this move by jumping
6599         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6600         if(bookHit) {
6601                 static char bookMove[MSG_SIZ]; // a bit generous?
6602
6603                 strcpy(bookMove, "move ");
6604                 strcat(bookMove, bookHit);
6605                 message = bookMove;
6606                 cps = cps->other;
6607                 programStats.nodes = programStats.depth = programStats.time = 
6608                 programStats.score = programStats.got_only_move = 0;
6609                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6610
6611                 if(cps->lastPing != cps->lastPong) {
6612                     savedMessage = message; // args for deferred call
6613                     savedState = cps;
6614                     ScheduleDelayedEvent(DeferredBookMove, 10);
6615                     return;
6616                 }
6617                 goto FakeBookMove;
6618         }
6619
6620         return;
6621     }
6622
6623     /* Set special modes for chess engines.  Later something general
6624      *  could be added here; for now there is just one kludge feature,
6625      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6626      *  when "xboard" is given as an interactive command.
6627      */
6628     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6629         cps->useSigint = FALSE;
6630         cps->useSigterm = FALSE;
6631     }
6632     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6633       ParseFeatures(message+8, cps);
6634       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6635     }
6636
6637     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6638      * want this, I was asked to put it in, and obliged.
6639      */
6640     if (!strncmp(message, "setboard ", 9)) {
6641         Board initial_position;
6642
6643         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6644
6645         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6646             DisplayError(_("Bad FEN received from engine"), 0);
6647             return ;
6648         } else {
6649            Reset(TRUE, FALSE);
6650            CopyBoard(boards[0], initial_position);
6651            initialRulePlies = FENrulePlies;
6652            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6653            else gameMode = MachinePlaysBlack;                 
6654            DrawPosition(FALSE, boards[currentMove]);
6655         }
6656         return;
6657     }
6658
6659     /*
6660      * Look for communication commands
6661      */
6662     if (!strncmp(message, "telluser ", 9)) {
6663         DisplayNote(message + 9);
6664         return;
6665     }
6666     if (!strncmp(message, "tellusererror ", 14)) {
6667         cps->userError = 1;
6668         DisplayError(message + 14, 0);
6669         return;
6670     }
6671     if (!strncmp(message, "tellopponent ", 13)) {
6672       if (appData.icsActive) {
6673         if (loggedOn) {
6674           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6675           SendToICS(buf1);
6676         }
6677       } else {
6678         DisplayNote(message + 13);
6679       }
6680       return;
6681     }
6682     if (!strncmp(message, "tellothers ", 11)) {
6683       if (appData.icsActive) {
6684         if (loggedOn) {
6685           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6686           SendToICS(buf1);
6687         }
6688       }
6689       return;
6690     }
6691     if (!strncmp(message, "tellall ", 8)) {
6692       if (appData.icsActive) {
6693         if (loggedOn) {
6694           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6695           SendToICS(buf1);
6696         }
6697       } else {
6698         DisplayNote(message + 8);
6699       }
6700       return;
6701     }
6702     if (strncmp(message, "warning", 7) == 0) {
6703         /* Undocumented feature, use tellusererror in new code */
6704         DisplayError(message, 0);
6705         return;
6706     }
6707     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6708         strcpy(realname, cps->tidy);
6709         strcat(realname, " query");
6710         AskQuestion(realname, buf2, buf1, cps->pr);
6711         return;
6712     }
6713     /* Commands from the engine directly to ICS.  We don't allow these to be 
6714      *  sent until we are logged on. Crafty kibitzes have been known to 
6715      *  interfere with the login process.
6716      */
6717     if (loggedOn) {
6718         if (!strncmp(message, "tellics ", 8)) {
6719             SendToICS(message + 8);
6720             SendToICS("\n");
6721             return;
6722         }
6723         if (!strncmp(message, "tellicsnoalias ", 15)) {
6724             SendToICS(ics_prefix);
6725             SendToICS(message + 15);
6726             SendToICS("\n");
6727             return;
6728         }
6729         /* The following are for backward compatibility only */
6730         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6731             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6732             SendToICS(ics_prefix);
6733             SendToICS(message);
6734             SendToICS("\n");
6735             return;
6736         }
6737     }
6738     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6739         return;
6740     }
6741     /*
6742      * If the move is illegal, cancel it and redraw the board.
6743      * Also deal with other error cases.  Matching is rather loose
6744      * here to accommodate engines written before the spec.
6745      */
6746     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6747         strncmp(message, "Error", 5) == 0) {
6748         if (StrStr(message, "name") || 
6749             StrStr(message, "rating") || StrStr(message, "?") ||
6750             StrStr(message, "result") || StrStr(message, "board") ||
6751             StrStr(message, "bk") || StrStr(message, "computer") ||
6752             StrStr(message, "variant") || StrStr(message, "hint") ||
6753             StrStr(message, "random") || StrStr(message, "depth") ||
6754             StrStr(message, "accepted")) {
6755             return;
6756         }
6757         if (StrStr(message, "protover")) {
6758           /* Program is responding to input, so it's apparently done
6759              initializing, and this error message indicates it is
6760              protocol version 1.  So we don't need to wait any longer
6761              for it to initialize and send feature commands. */
6762           FeatureDone(cps, 1);
6763           cps->protocolVersion = 1;
6764           return;
6765         }
6766         cps->maybeThinking = FALSE;
6767
6768         if (StrStr(message, "draw")) {
6769             /* Program doesn't have "draw" command */
6770             cps->sendDrawOffers = 0;
6771             return;
6772         }
6773         if (cps->sendTime != 1 &&
6774             (StrStr(message, "time") || StrStr(message, "otim"))) {
6775           /* Program apparently doesn't have "time" or "otim" command */
6776           cps->sendTime = 0;
6777           return;
6778         }
6779         if (StrStr(message, "analyze")) {
6780             cps->analysisSupport = FALSE;
6781             cps->analyzing = FALSE;
6782             Reset(FALSE, TRUE);
6783             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6784             DisplayError(buf2, 0);
6785             return;
6786         }
6787         if (StrStr(message, "(no matching move)st")) {
6788           /* Special kludge for GNU Chess 4 only */
6789           cps->stKludge = TRUE;
6790           SendTimeControl(cps, movesPerSession, timeControl,
6791                           timeIncrement, appData.searchDepth,
6792                           searchTime);
6793           return;
6794         }
6795         if (StrStr(message, "(no matching move)sd")) {
6796           /* Special kludge for GNU Chess 4 only */
6797           cps->sdKludge = TRUE;
6798           SendTimeControl(cps, movesPerSession, timeControl,
6799                           timeIncrement, appData.searchDepth,
6800                           searchTime);
6801           return;
6802         }
6803         if (!StrStr(message, "llegal")) {
6804             return;
6805         }
6806         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6807             gameMode == IcsIdle) return;
6808         if (forwardMostMove <= backwardMostMove) return;
6809         if (pausing) PauseEvent();
6810       if(appData.forceIllegal) {
6811             // [HGM] illegal: machine refused move; force position after move into it
6812           SendToProgram("force\n", cps);
6813           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6814                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6815                 // when black is to move, while there might be nothing on a2 or black
6816                 // might already have the move. So send the board as if white has the move.
6817                 // But first we must change the stm of the engine, as it refused the last move
6818                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6819                 if(WhiteOnMove(forwardMostMove)) {
6820                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6821                     SendBoard(cps, forwardMostMove); // kludgeless board
6822                 } else {
6823                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6824                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6825                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6826                 }
6827           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6828             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6829                  gameMode == TwoMachinesPlay)
6830               SendToProgram("go\n", cps);
6831             return;
6832       } else
6833         if (gameMode == PlayFromGameFile) {
6834             /* Stop reading this game file */
6835             gameMode = EditGame;
6836             ModeHighlight();
6837         }
6838         currentMove = --forwardMostMove;
6839         DisplayMove(currentMove-1); /* before DisplayMoveError */
6840         SwitchClocks();
6841         DisplayBothClocks();
6842         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6843                 parseList[currentMove], cps->which);
6844         DisplayMoveError(buf1);
6845         DrawPosition(FALSE, boards[currentMove]);
6846
6847         /* [HGM] illegal-move claim should forfeit game when Xboard */
6848         /* only passes fully legal moves                            */
6849         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6850             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6851                                 "False illegal-move claim", GE_XBOARD );
6852         }
6853         return;
6854     }
6855     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6856         /* Program has a broken "time" command that
6857            outputs a string not ending in newline.
6858            Don't use it. */
6859         cps->sendTime = 0;
6860     }
6861     
6862     /*
6863      * If chess program startup fails, exit with an error message.
6864      * Attempts to recover here are futile.
6865      */
6866     if ((StrStr(message, "unknown host") != NULL)
6867         || (StrStr(message, "No remote directory") != NULL)
6868         || (StrStr(message, "not found") != NULL)
6869         || (StrStr(message, "No such file") != NULL)
6870         || (StrStr(message, "can't alloc") != NULL)
6871         || (StrStr(message, "Permission denied") != NULL)) {
6872
6873         cps->maybeThinking = FALSE;
6874         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6875                 cps->which, cps->program, cps->host, message);
6876         RemoveInputSource(cps->isr);
6877         DisplayFatalError(buf1, 0, 1);
6878         return;
6879     }
6880     
6881     /* 
6882      * Look for hint output
6883      */
6884     if (sscanf(message, "Hint: %s", buf1) == 1) {
6885         if (cps == &first && hintRequested) {
6886             hintRequested = FALSE;
6887             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6888                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6889                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6890                                     PosFlags(forwardMostMove),
6891                                     fromY, fromX, toY, toX, promoChar, buf1);
6892                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6893                 DisplayInformation(buf2);
6894             } else {
6895                 /* Hint move could not be parsed!? */
6896               snprintf(buf2, sizeof(buf2),
6897                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6898                         buf1, cps->which);
6899                 DisplayError(buf2, 0);
6900             }
6901         } else {
6902             strcpy(lastHint, buf1);
6903         }
6904         return;
6905     }
6906
6907     /*
6908      * Ignore other messages if game is not in progress
6909      */
6910     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6911         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6912
6913     /*
6914      * look for win, lose, draw, or draw offer
6915      */
6916     if (strncmp(message, "1-0", 3) == 0) {
6917         char *p, *q, *r = "";
6918         p = strchr(message, '{');
6919         if (p) {
6920             q = strchr(p, '}');
6921             if (q) {
6922                 *q = NULLCHAR;
6923                 r = p + 1;
6924             }
6925         }
6926         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6927         return;
6928     } else if (strncmp(message, "0-1", 3) == 0) {
6929         char *p, *q, *r = "";
6930         p = strchr(message, '{');
6931         if (p) {
6932             q = strchr(p, '}');
6933             if (q) {
6934                 *q = NULLCHAR;
6935                 r = p + 1;
6936             }
6937         }
6938         /* Kludge for Arasan 4.1 bug */
6939         if (strcmp(r, "Black resigns") == 0) {
6940             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6941             return;
6942         }
6943         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6944         return;
6945     } else if (strncmp(message, "1/2", 3) == 0) {
6946         char *p, *q, *r = "";
6947         p = strchr(message, '{');
6948         if (p) {
6949             q = strchr(p, '}');
6950             if (q) {
6951                 *q = NULLCHAR;
6952                 r = p + 1;
6953             }
6954         }
6955             
6956         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6957         return;
6958
6959     } else if (strncmp(message, "White resign", 12) == 0) {
6960         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6961         return;
6962     } else if (strncmp(message, "Black resign", 12) == 0) {
6963         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6964         return;
6965     } else if (strncmp(message, "White matches", 13) == 0 ||
6966                strncmp(message, "Black matches", 13) == 0   ) {
6967         /* [HGM] ignore GNUShogi noises */
6968         return;
6969     } else if (strncmp(message, "White", 5) == 0 &&
6970                message[5] != '(' &&
6971                StrStr(message, "Black") == NULL) {
6972         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6973         return;
6974     } else if (strncmp(message, "Black", 5) == 0 &&
6975                message[5] != '(') {
6976         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6977         return;
6978     } else if (strcmp(message, "resign") == 0 ||
6979                strcmp(message, "computer resigns") == 0) {
6980         switch (gameMode) {
6981           case MachinePlaysBlack:
6982           case IcsPlayingBlack:
6983             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6984             break;
6985           case MachinePlaysWhite:
6986           case IcsPlayingWhite:
6987             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6988             break;
6989           case TwoMachinesPlay:
6990             if (cps->twoMachinesColor[0] == 'w')
6991               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6992             else
6993               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6994             break;
6995           default:
6996             /* can't happen */
6997             break;
6998         }
6999         return;
7000     } else if (strncmp(message, "opponent mates", 14) == 0) {
7001         switch (gameMode) {
7002           case MachinePlaysBlack:
7003           case IcsPlayingBlack:
7004             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7005             break;
7006           case MachinePlaysWhite:
7007           case IcsPlayingWhite:
7008             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7009             break;
7010           case TwoMachinesPlay:
7011             if (cps->twoMachinesColor[0] == 'w')
7012               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7013             else
7014               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7015             break;
7016           default:
7017             /* can't happen */
7018             break;
7019         }
7020         return;
7021     } else if (strncmp(message, "computer mates", 14) == 0) {
7022         switch (gameMode) {
7023           case MachinePlaysBlack:
7024           case IcsPlayingBlack:
7025             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7026             break;
7027           case MachinePlaysWhite:
7028           case IcsPlayingWhite:
7029             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7030             break;
7031           case TwoMachinesPlay:
7032             if (cps->twoMachinesColor[0] == 'w')
7033               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7034             else
7035               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7036             break;
7037           default:
7038             /* can't happen */
7039             break;
7040         }
7041         return;
7042     } else if (strncmp(message, "checkmate", 9) == 0) {
7043         if (WhiteOnMove(forwardMostMove)) {
7044             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7045         } else {
7046             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7047         }
7048         return;
7049     } else if (strstr(message, "Draw") != NULL ||
7050                strstr(message, "game is a draw") != NULL) {
7051         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7052         return;
7053     } else if (strstr(message, "offer") != NULL &&
7054                strstr(message, "draw") != NULL) {
7055 #if ZIPPY
7056         if (appData.zippyPlay && first.initDone) {
7057             /* Relay offer to ICS */
7058             SendToICS(ics_prefix);
7059             SendToICS("draw\n");
7060         }
7061 #endif
7062         cps->offeredDraw = 2; /* valid until this engine moves twice */
7063         if (gameMode == TwoMachinesPlay) {
7064             if (cps->other->offeredDraw) {
7065                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7066             /* [HGM] in two-machine mode we delay relaying draw offer      */
7067             /* until after we also have move, to see if it is really claim */
7068             }
7069         } else if (gameMode == MachinePlaysWhite ||
7070                    gameMode == MachinePlaysBlack) {
7071           if (userOfferedDraw) {
7072             DisplayInformation(_("Machine accepts your draw offer"));
7073             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7074           } else {
7075             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7076           }
7077         }
7078     }
7079
7080     
7081     /*
7082      * Look for thinking output
7083      */
7084     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7085           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7086                                 ) {
7087         int plylev, mvleft, mvtot, curscore, time;
7088         char mvname[MOVE_LEN];
7089         u64 nodes; // [DM]
7090         char plyext;
7091         int ignore = FALSE;
7092         int prefixHint = FALSE;
7093         mvname[0] = NULLCHAR;
7094
7095         switch (gameMode) {
7096           case MachinePlaysBlack:
7097           case IcsPlayingBlack:
7098             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7099             break;
7100           case MachinePlaysWhite:
7101           case IcsPlayingWhite:
7102             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7103             break;
7104           case AnalyzeMode:
7105           case AnalyzeFile:
7106             break;
7107           case IcsObserving: /* [DM] icsEngineAnalyze */
7108             if (!appData.icsEngineAnalyze) ignore = TRUE;
7109             break;
7110           case TwoMachinesPlay:
7111             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7112                 ignore = TRUE;
7113             }
7114             break;
7115           default:
7116             ignore = TRUE;
7117             break;
7118         }
7119
7120         if (!ignore) {
7121             buf1[0] = NULLCHAR;
7122             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7123                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7124
7125                 if (plyext != ' ' && plyext != '\t') {
7126                     time *= 100;
7127                 }
7128
7129                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7130                 if( cps->scoreIsAbsolute && 
7131                     ( gameMode == MachinePlaysBlack ||
7132                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7133                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7134                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7135                      !WhiteOnMove(currentMove)
7136                     ) )
7137                 {
7138                     curscore = -curscore;
7139                 }
7140
7141
7142                 programStats.depth = plylev;
7143                 programStats.nodes = nodes;
7144                 programStats.time = time;
7145                 programStats.score = curscore;
7146                 programStats.got_only_move = 0;
7147
7148                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7149                         int ticklen;
7150
7151                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7152                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7153                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7154                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7155                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7156                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7157                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7158                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7159                 }
7160
7161                 /* Buffer overflow protection */
7162                 if (buf1[0] != NULLCHAR) {
7163                     if (strlen(buf1) >= sizeof(programStats.movelist)
7164                         && appData.debugMode) {
7165                         fprintf(debugFP,
7166                                 "PV is too long; using the first %u bytes.\n",
7167                                 (unsigned) sizeof(programStats.movelist) - 1);
7168                     }
7169
7170                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7171                 } else {
7172                     sprintf(programStats.movelist, " no PV\n");
7173                 }
7174
7175                 if (programStats.seen_stat) {
7176                     programStats.ok_to_send = 1;
7177                 }
7178
7179                 if (strchr(programStats.movelist, '(') != NULL) {
7180                     programStats.line_is_book = 1;
7181                     programStats.nr_moves = 0;
7182                     programStats.moves_left = 0;
7183                 } else {
7184                     programStats.line_is_book = 0;
7185                 }
7186
7187                 SendProgramStatsToFrontend( cps, &programStats );
7188
7189                 /* 
7190                     [AS] Protect the thinkOutput buffer from overflow... this
7191                     is only useful if buf1 hasn't overflowed first!
7192                 */
7193                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7194                         plylev, 
7195                         (gameMode == TwoMachinesPlay ?
7196                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7197                         ((double) curscore) / 100.0,
7198                         prefixHint ? lastHint : "",
7199                         prefixHint ? " " : "" );
7200
7201                 if( buf1[0] != NULLCHAR ) {
7202                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7203
7204                     if( strlen(buf1) > max_len ) {
7205                         if( appData.debugMode) {
7206                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7207                         }
7208                         buf1[max_len+1] = '\0';
7209                     }
7210
7211                     strcat( thinkOutput, buf1 );
7212                 }
7213
7214                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7215                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7216                     DisplayMove(currentMove - 1);
7217                 }
7218                 return;
7219
7220             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7221                 /* crafty (9.25+) says "(only move) <move>"
7222                  * if there is only 1 legal move
7223                  */
7224                 sscanf(p, "(only move) %s", buf1);
7225                 sprintf(thinkOutput, "%s (only move)", buf1);
7226                 sprintf(programStats.movelist, "%s (only move)", buf1);
7227                 programStats.depth = 1;
7228                 programStats.nr_moves = 1;
7229                 programStats.moves_left = 1;
7230                 programStats.nodes = 1;
7231                 programStats.time = 1;
7232                 programStats.got_only_move = 1;
7233
7234                 /* Not really, but we also use this member to
7235                    mean "line isn't going to change" (Crafty
7236                    isn't searching, so stats won't change) */
7237                 programStats.line_is_book = 1;
7238
7239                 SendProgramStatsToFrontend( cps, &programStats );
7240                 
7241                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7242                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7243                     DisplayMove(currentMove - 1);
7244                 }
7245                 return;
7246             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7247                               &time, &nodes, &plylev, &mvleft,
7248                               &mvtot, mvname) >= 5) {
7249                 /* The stat01: line is from Crafty (9.29+) in response
7250                    to the "." command */
7251                 programStats.seen_stat = 1;
7252                 cps->maybeThinking = TRUE;
7253
7254                 if (programStats.got_only_move || !appData.periodicUpdates)
7255                   return;
7256
7257                 programStats.depth = plylev;
7258                 programStats.time = time;
7259                 programStats.nodes = nodes;
7260                 programStats.moves_left = mvleft;
7261                 programStats.nr_moves = mvtot;
7262                 strcpy(programStats.move_name, mvname);
7263                 programStats.ok_to_send = 1;
7264                 programStats.movelist[0] = '\0';
7265
7266                 SendProgramStatsToFrontend( cps, &programStats );
7267
7268                 return;
7269
7270             } else if (strncmp(message,"++",2) == 0) {
7271                 /* Crafty 9.29+ outputs this */
7272                 programStats.got_fail = 2;
7273                 return;
7274
7275             } else if (strncmp(message,"--",2) == 0) {
7276                 /* Crafty 9.29+ outputs this */
7277                 programStats.got_fail = 1;
7278                 return;
7279
7280             } else if (thinkOutput[0] != NULLCHAR &&
7281                        strncmp(message, "    ", 4) == 0) {
7282                 unsigned message_len;
7283
7284                 p = message;
7285                 while (*p && *p == ' ') p++;
7286
7287                 message_len = strlen( p );
7288
7289                 /* [AS] Avoid buffer overflow */
7290                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7291                     strcat(thinkOutput, " ");
7292                     strcat(thinkOutput, p);
7293                 }
7294
7295                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7296                     strcat(programStats.movelist, " ");
7297                     strcat(programStats.movelist, p);
7298                 }
7299
7300                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7301                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7302                     DisplayMove(currentMove - 1);
7303                 }
7304                 return;
7305             }
7306         }
7307         else {
7308             buf1[0] = NULLCHAR;
7309
7310             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7311                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7312             {
7313                 ChessProgramStats cpstats;
7314
7315                 if (plyext != ' ' && plyext != '\t') {
7316                     time *= 100;
7317                 }
7318
7319                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7320                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7321                     curscore = -curscore;
7322                 }
7323
7324                 cpstats.depth = plylev;
7325                 cpstats.nodes = nodes;
7326                 cpstats.time = time;
7327                 cpstats.score = curscore;
7328                 cpstats.got_only_move = 0;
7329                 cpstats.movelist[0] = '\0';
7330
7331                 if (buf1[0] != NULLCHAR) {
7332                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7333                 }
7334
7335                 cpstats.ok_to_send = 0;
7336                 cpstats.line_is_book = 0;
7337                 cpstats.nr_moves = 0;
7338                 cpstats.moves_left = 0;
7339
7340                 SendProgramStatsToFrontend( cps, &cpstats );
7341             }
7342         }
7343     }
7344 }
7345
7346
7347 /* Parse a game score from the character string "game", and
7348    record it as the history of the current game.  The game
7349    score is NOT assumed to start from the standard position. 
7350    The display is not updated in any way.
7351    */
7352 void
7353 ParseGameHistory(game)
7354      char *game;
7355 {
7356     ChessMove moveType;
7357     int fromX, fromY, toX, toY, boardIndex;
7358     char promoChar;
7359     char *p, *q;
7360     char buf[MSG_SIZ];
7361
7362     if (appData.debugMode)
7363       fprintf(debugFP, "Parsing game history: %s\n", game);
7364
7365     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7366     gameInfo.site = StrSave(appData.icsHost);
7367     gameInfo.date = PGNDate();
7368     gameInfo.round = StrSave("-");
7369
7370     /* Parse out names of players */
7371     while (*game == ' ') game++;
7372     p = buf;
7373     while (*game != ' ') *p++ = *game++;
7374     *p = NULLCHAR;
7375     gameInfo.white = StrSave(buf);
7376     while (*game == ' ') game++;
7377     p = buf;
7378     while (*game != ' ' && *game != '\n') *p++ = *game++;
7379     *p = NULLCHAR;
7380     gameInfo.black = StrSave(buf);
7381
7382     /* Parse moves */
7383     boardIndex = blackPlaysFirst ? 1 : 0;
7384     yynewstr(game);
7385     for (;;) {
7386         yyboardindex = boardIndex;
7387         moveType = (ChessMove) yylex();
7388         switch (moveType) {
7389           case IllegalMove:             /* maybe suicide chess, etc. */
7390   if (appData.debugMode) {
7391     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7392     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7393     setbuf(debugFP, NULL);
7394   }
7395           case WhitePromotionChancellor:
7396           case BlackPromotionChancellor:
7397           case WhitePromotionArchbishop:
7398           case BlackPromotionArchbishop:
7399           case WhitePromotionQueen:
7400           case BlackPromotionQueen:
7401           case WhitePromotionRook:
7402           case BlackPromotionRook:
7403           case WhitePromotionBishop:
7404           case BlackPromotionBishop:
7405           case WhitePromotionKnight:
7406           case BlackPromotionKnight:
7407           case WhitePromotionKing:
7408           case BlackPromotionKing:
7409           case NormalMove:
7410           case WhiteCapturesEnPassant:
7411           case BlackCapturesEnPassant:
7412           case WhiteKingSideCastle:
7413           case WhiteQueenSideCastle:
7414           case BlackKingSideCastle:
7415           case BlackQueenSideCastle:
7416           case WhiteKingSideCastleWild:
7417           case WhiteQueenSideCastleWild:
7418           case BlackKingSideCastleWild:
7419           case BlackQueenSideCastleWild:
7420           /* PUSH Fabien */
7421           case WhiteHSideCastleFR:
7422           case WhiteASideCastleFR:
7423           case BlackHSideCastleFR:
7424           case BlackASideCastleFR:
7425           /* POP Fabien */
7426             fromX = currentMoveString[0] - AAA;
7427             fromY = currentMoveString[1] - ONE;
7428             toX = currentMoveString[2] - AAA;
7429             toY = currentMoveString[3] - ONE;
7430             promoChar = currentMoveString[4];
7431             break;
7432           case WhiteDrop:
7433           case BlackDrop:
7434             fromX = moveType == WhiteDrop ?
7435               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7436             (int) CharToPiece(ToLower(currentMoveString[0]));
7437             fromY = DROP_RANK;
7438             toX = currentMoveString[2] - AAA;
7439             toY = currentMoveString[3] - ONE;
7440             promoChar = NULLCHAR;
7441             break;
7442           case AmbiguousMove:
7443             /* bug? */
7444             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7445   if (appData.debugMode) {
7446     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7447     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7448     setbuf(debugFP, NULL);
7449   }
7450             DisplayError(buf, 0);
7451             return;
7452           case ImpossibleMove:
7453             /* bug? */
7454             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7455   if (appData.debugMode) {
7456     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7457     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7458     setbuf(debugFP, NULL);
7459   }
7460             DisplayError(buf, 0);
7461             return;
7462           case (ChessMove) 0:   /* end of file */
7463             if (boardIndex < backwardMostMove) {
7464                 /* Oops, gap.  How did that happen? */
7465                 DisplayError(_("Gap in move list"), 0);
7466                 return;
7467             }
7468             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7469             if (boardIndex > forwardMostMove) {
7470                 forwardMostMove = boardIndex;
7471             }
7472             return;
7473           case ElapsedTime:
7474             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7475                 strcat(parseList[boardIndex-1], " ");
7476                 strcat(parseList[boardIndex-1], yy_text);
7477             }
7478             continue;
7479           case Comment:
7480           case PGNTag:
7481           case NAG:
7482           default:
7483             /* ignore */
7484             continue;
7485           case WhiteWins:
7486           case BlackWins:
7487           case GameIsDrawn:
7488           case GameUnfinished:
7489             if (gameMode == IcsExamining) {
7490                 if (boardIndex < backwardMostMove) {
7491                     /* Oops, gap.  How did that happen? */
7492                     return;
7493                 }
7494                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7495                 return;
7496             }
7497             gameInfo.result = moveType;
7498             p = strchr(yy_text, '{');
7499             if (p == NULL) p = strchr(yy_text, '(');
7500             if (p == NULL) {
7501                 p = yy_text;
7502                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7503             } else {
7504                 q = strchr(p, *p == '{' ? '}' : ')');
7505                 if (q != NULL) *q = NULLCHAR;
7506                 p++;
7507             }
7508             gameInfo.resultDetails = StrSave(p);
7509             continue;
7510         }
7511         if (boardIndex >= forwardMostMove &&
7512             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7513             backwardMostMove = blackPlaysFirst ? 1 : 0;
7514             return;
7515         }
7516         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7517                                  fromY, fromX, toY, toX, promoChar,
7518                                  parseList[boardIndex]);
7519         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7520         /* currentMoveString is set as a side-effect of yylex */
7521         strcpy(moveList[boardIndex], currentMoveString);
7522         strcat(moveList[boardIndex], "\n");
7523         boardIndex++;
7524         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7525         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7526           case MT_NONE:
7527           case MT_STALEMATE:
7528           default:
7529             break;
7530           case MT_CHECK:
7531             if(gameInfo.variant != VariantShogi)
7532                 strcat(parseList[boardIndex - 1], "+");
7533             break;
7534           case MT_CHECKMATE:
7535           case MT_STAINMATE:
7536             strcat(parseList[boardIndex - 1], "#");
7537             break;
7538         }
7539     }
7540 }
7541
7542
7543 /* Apply a move to the given board  */
7544 void
7545 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7546      int fromX, fromY, toX, toY;
7547      int promoChar;
7548      Board board;
7549 {
7550   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7551
7552     /* [HGM] compute & store e.p. status and castling rights for new position */
7553     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7554     { int i;
7555
7556       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7557       oldEP = (signed char)board[EP_STATUS];
7558       board[EP_STATUS] = EP_NONE;
7559
7560       if( board[toY][toX] != EmptySquare ) 
7561            board[EP_STATUS] = EP_CAPTURE;  
7562
7563       if( board[fromY][fromX] == WhitePawn ) {
7564            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7565                board[EP_STATUS] = EP_PAWN_MOVE;
7566            if( toY-fromY==2) {
7567                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7568                         gameInfo.variant != VariantBerolina || toX < fromX)
7569                       board[EP_STATUS] = toX | berolina;
7570                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7571                         gameInfo.variant != VariantBerolina || toX > fromX) 
7572                       board[EP_STATUS] = toX;
7573            }
7574       } else 
7575       if( board[fromY][fromX] == BlackPawn ) {
7576            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7577                board[EP_STATUS] = EP_PAWN_MOVE; 
7578            if( toY-fromY== -2) {
7579                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7580                         gameInfo.variant != VariantBerolina || toX < fromX)
7581                       board[EP_STATUS] = toX | berolina;
7582                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7583                         gameInfo.variant != VariantBerolina || toX > fromX) 
7584                       board[EP_STATUS] = toX;
7585            }
7586        }
7587
7588        for(i=0; i<nrCastlingRights; i++) {
7589            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7590               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7591              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7592        }
7593
7594     }
7595
7596   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7597   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7598        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7599          
7600   if (fromX == toX && fromY == toY) return;
7601
7602   if (fromY == DROP_RANK) {
7603         /* must be first */
7604         piece = board[toY][toX] = (ChessSquare) fromX;
7605   } else {
7606      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7607      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7608      if(gameInfo.variant == VariantKnightmate)
7609          king += (int) WhiteUnicorn - (int) WhiteKing;
7610
7611     /* Code added by Tord: */
7612     /* FRC castling assumed when king captures friendly rook. */
7613     if (board[fromY][fromX] == WhiteKing &&
7614              board[toY][toX] == WhiteRook) {
7615       board[fromY][fromX] = EmptySquare;
7616       board[toY][toX] = EmptySquare;
7617       if(toX > fromX) {
7618         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7619       } else {
7620         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7621       }
7622     } else if (board[fromY][fromX] == BlackKing &&
7623                board[toY][toX] == BlackRook) {
7624       board[fromY][fromX] = EmptySquare;
7625       board[toY][toX] = EmptySquare;
7626       if(toX > fromX) {
7627         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7628       } else {
7629         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7630       }
7631     /* End of code added by Tord */
7632
7633     } else if (board[fromY][fromX] == king
7634         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7635         && toY == fromY && toX > fromX+1) {
7636         board[fromY][fromX] = EmptySquare;
7637         board[toY][toX] = king;
7638         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7639         board[fromY][BOARD_RGHT-1] = EmptySquare;
7640     } else if (board[fromY][fromX] == king
7641         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7642                && toY == fromY && toX < fromX-1) {
7643         board[fromY][fromX] = EmptySquare;
7644         board[toY][toX] = king;
7645         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7646         board[fromY][BOARD_LEFT] = EmptySquare;
7647     } else if (board[fromY][fromX] == WhitePawn
7648                && toY == BOARD_HEIGHT-1
7649                && gameInfo.variant != VariantXiangqi
7650                ) {
7651         /* white pawn promotion */
7652         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7653         if (board[toY][toX] == EmptySquare) {
7654             board[toY][toX] = WhiteQueen;
7655         }
7656         if(gameInfo.variant==VariantBughouse ||
7657            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7658             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7659         board[fromY][fromX] = EmptySquare;
7660     } else if ((fromY == BOARD_HEIGHT-4)
7661                && (toX != fromX)
7662                && gameInfo.variant != VariantXiangqi
7663                && gameInfo.variant != VariantBerolina
7664                && (board[fromY][fromX] == WhitePawn)
7665                && (board[toY][toX] == EmptySquare)) {
7666         board[fromY][fromX] = EmptySquare;
7667         board[toY][toX] = WhitePawn;
7668         captured = board[toY - 1][toX];
7669         board[toY - 1][toX] = EmptySquare;
7670     } else if ((fromY == BOARD_HEIGHT-4)
7671                && (toX == fromX)
7672                && gameInfo.variant == VariantBerolina
7673                && (board[fromY][fromX] == WhitePawn)
7674                && (board[toY][toX] == EmptySquare)) {
7675         board[fromY][fromX] = EmptySquare;
7676         board[toY][toX] = WhitePawn;
7677         if(oldEP & EP_BEROLIN_A) {
7678                 captured = board[fromY][fromX-1];
7679                 board[fromY][fromX-1] = EmptySquare;
7680         }else{  captured = board[fromY][fromX+1];
7681                 board[fromY][fromX+1] = EmptySquare;
7682         }
7683     } else if (board[fromY][fromX] == king
7684         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7685                && toY == fromY && toX > fromX+1) {
7686         board[fromY][fromX] = EmptySquare;
7687         board[toY][toX] = king;
7688         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7689         board[fromY][BOARD_RGHT-1] = EmptySquare;
7690     } else if (board[fromY][fromX] == king
7691         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7692                && toY == fromY && toX < fromX-1) {
7693         board[fromY][fromX] = EmptySquare;
7694         board[toY][toX] = king;
7695         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7696         board[fromY][BOARD_LEFT] = EmptySquare;
7697     } else if (fromY == 7 && fromX == 3
7698                && board[fromY][fromX] == BlackKing
7699                && toY == 7 && toX == 5) {
7700         board[fromY][fromX] = EmptySquare;
7701         board[toY][toX] = BlackKing;
7702         board[fromY][7] = EmptySquare;
7703         board[toY][4] = BlackRook;
7704     } else if (fromY == 7 && fromX == 3
7705                && board[fromY][fromX] == BlackKing
7706                && toY == 7 && toX == 1) {
7707         board[fromY][fromX] = EmptySquare;
7708         board[toY][toX] = BlackKing;
7709         board[fromY][0] = EmptySquare;
7710         board[toY][2] = BlackRook;
7711     } else if (board[fromY][fromX] == BlackPawn
7712                && toY == 0
7713                && gameInfo.variant != VariantXiangqi
7714                ) {
7715         /* black pawn promotion */
7716         board[0][toX] = CharToPiece(ToLower(promoChar));
7717         if (board[0][toX] == EmptySquare) {
7718             board[0][toX] = BlackQueen;
7719         }
7720         if(gameInfo.variant==VariantBughouse ||
7721            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7722             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7723         board[fromY][fromX] = EmptySquare;
7724     } else if ((fromY == 3)
7725                && (toX != fromX)
7726                && gameInfo.variant != VariantXiangqi
7727                && gameInfo.variant != VariantBerolina
7728                && (board[fromY][fromX] == BlackPawn)
7729                && (board[toY][toX] == EmptySquare)) {
7730         board[fromY][fromX] = EmptySquare;
7731         board[toY][toX] = BlackPawn;
7732         captured = board[toY + 1][toX];
7733         board[toY + 1][toX] = EmptySquare;
7734     } else if ((fromY == 3)
7735                && (toX == fromX)
7736                && gameInfo.variant == VariantBerolina
7737                && (board[fromY][fromX] == BlackPawn)
7738                && (board[toY][toX] == EmptySquare)) {
7739         board[fromY][fromX] = EmptySquare;
7740         board[toY][toX] = BlackPawn;
7741         if(oldEP & EP_BEROLIN_A) {
7742                 captured = board[fromY][fromX-1];
7743                 board[fromY][fromX-1] = EmptySquare;
7744         }else{  captured = board[fromY][fromX+1];
7745                 board[fromY][fromX+1] = EmptySquare;
7746         }
7747     } else {
7748         board[toY][toX] = board[fromY][fromX];
7749         board[fromY][fromX] = EmptySquare;
7750     }
7751
7752     /* [HGM] now we promote for Shogi, if needed */
7753     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7754         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7755   }
7756
7757     if (gameInfo.holdingsWidth != 0) {
7758
7759       /* !!A lot more code needs to be written to support holdings  */
7760       /* [HGM] OK, so I have written it. Holdings are stored in the */
7761       /* penultimate board files, so they are automaticlly stored   */
7762       /* in the game history.                                       */
7763       if (fromY == DROP_RANK) {
7764         /* Delete from holdings, by decreasing count */
7765         /* and erasing image if necessary            */
7766         p = (int) fromX;
7767         if(p < (int) BlackPawn) { /* white drop */
7768              p -= (int)WhitePawn;
7769                  p = PieceToNumber((ChessSquare)p);
7770              if(p >= gameInfo.holdingsSize) p = 0;
7771              if(--board[p][BOARD_WIDTH-2] <= 0)
7772                   board[p][BOARD_WIDTH-1] = EmptySquare;
7773              if((int)board[p][BOARD_WIDTH-2] < 0)
7774                         board[p][BOARD_WIDTH-2] = 0;
7775         } else {                  /* black drop */
7776              p -= (int)BlackPawn;
7777                  p = PieceToNumber((ChessSquare)p);
7778              if(p >= gameInfo.holdingsSize) p = 0;
7779              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7780                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7781              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7782                         board[BOARD_HEIGHT-1-p][1] = 0;
7783         }
7784       }
7785       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7786           && gameInfo.variant != VariantBughouse        ) {
7787         /* [HGM] holdings: Add to holdings, if holdings exist */
7788         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7789                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7790                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7791         }
7792         p = (int) captured;
7793         if (p >= (int) BlackPawn) {
7794           p -= (int)BlackPawn;
7795           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7796                   /* in Shogi restore piece to its original  first */
7797                   captured = (ChessSquare) (DEMOTED captured);
7798                   p = DEMOTED p;
7799           }
7800           p = PieceToNumber((ChessSquare)p);
7801           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7802           board[p][BOARD_WIDTH-2]++;
7803           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7804         } else {
7805           p -= (int)WhitePawn;
7806           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7807                   captured = (ChessSquare) (DEMOTED captured);
7808                   p = DEMOTED p;
7809           }
7810           p = PieceToNumber((ChessSquare)p);
7811           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7812           board[BOARD_HEIGHT-1-p][1]++;
7813           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7814         }
7815       }
7816     } else if (gameInfo.variant == VariantAtomic) {
7817       if (captured != EmptySquare) {
7818         int y, x;
7819         for (y = toY-1; y <= toY+1; y++) {
7820           for (x = toX-1; x <= toX+1; x++) {
7821             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7822                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7823               board[y][x] = EmptySquare;
7824             }
7825           }
7826         }
7827         board[toY][toX] = EmptySquare;
7828       }
7829     }
7830     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7831         /* [HGM] Shogi promotions */
7832         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7833     }
7834
7835     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7836                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7837         // [HGM] superchess: take promotion piece out of holdings
7838         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7839         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7840             if(!--board[k][BOARD_WIDTH-2])
7841                 board[k][BOARD_WIDTH-1] = EmptySquare;
7842         } else {
7843             if(!--board[BOARD_HEIGHT-1-k][1])
7844                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7845         }
7846     }
7847
7848 }
7849
7850 /* Updates forwardMostMove */
7851 void
7852 MakeMove(fromX, fromY, toX, toY, promoChar)
7853      int fromX, fromY, toX, toY;
7854      int promoChar;
7855 {
7856 //    forwardMostMove++; // [HGM] bare: moved downstream
7857
7858     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7859         int timeLeft; static int lastLoadFlag=0; int king, piece;
7860         piece = boards[forwardMostMove][fromY][fromX];
7861         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7862         if(gameInfo.variant == VariantKnightmate)
7863             king += (int) WhiteUnicorn - (int) WhiteKing;
7864         if(forwardMostMove == 0) {
7865             if(blackPlaysFirst) 
7866                 fprintf(serverMoves, "%s;", second.tidy);
7867             fprintf(serverMoves, "%s;", first.tidy);
7868             if(!blackPlaysFirst) 
7869                 fprintf(serverMoves, "%s;", second.tidy);
7870         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7871         lastLoadFlag = loadFlag;
7872         // print base move
7873         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7874         // print castling suffix
7875         if( toY == fromY && piece == king ) {
7876             if(toX-fromX > 1)
7877                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7878             if(fromX-toX >1)
7879                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7880         }
7881         // e.p. suffix
7882         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7883              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7884              boards[forwardMostMove][toY][toX] == EmptySquare
7885              && fromX != toX )
7886                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7887         // promotion suffix
7888         if(promoChar != NULLCHAR)
7889                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7890         if(!loadFlag) {
7891             fprintf(serverMoves, "/%d/%d",
7892                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7893             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7894             else                      timeLeft = blackTimeRemaining/1000;
7895             fprintf(serverMoves, "/%d", timeLeft);
7896         }
7897         fflush(serverMoves);
7898     }
7899
7900     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7901       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7902                         0, 1);
7903       return;
7904     }
7905     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7906     if (commentList[forwardMostMove+1] != NULL) {
7907         free(commentList[forwardMostMove+1]);
7908         commentList[forwardMostMove+1] = NULL;
7909     }
7910     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7911     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7912     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7913     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7914     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7915     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7916     gameInfo.result = GameUnfinished;
7917     if (gameInfo.resultDetails != NULL) {
7918         free(gameInfo.resultDetails);
7919         gameInfo.resultDetails = NULL;
7920     }
7921     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7922                               moveList[forwardMostMove - 1]);
7923     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7924                              PosFlags(forwardMostMove - 1),
7925                              fromY, fromX, toY, toX, promoChar,
7926                              parseList[forwardMostMove - 1]);
7927     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7928       case MT_NONE:
7929       case MT_STALEMATE:
7930       default:
7931         break;
7932       case MT_CHECK:
7933         if(gameInfo.variant != VariantShogi)
7934             strcat(parseList[forwardMostMove - 1], "+");
7935         break;
7936       case MT_CHECKMATE:
7937       case MT_STAINMATE:
7938         strcat(parseList[forwardMostMove - 1], "#");
7939         break;
7940     }
7941     if (appData.debugMode) {
7942         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7943     }
7944
7945 }
7946
7947 /* Updates currentMove if not pausing */
7948 void
7949 ShowMove(fromX, fromY, toX, toY)
7950 {
7951     int instant = (gameMode == PlayFromGameFile) ?
7952         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7953     if(appData.noGUI) return;
7954     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7955         if (!instant) {
7956             if (forwardMostMove == currentMove + 1) {
7957                 AnimateMove(boards[forwardMostMove - 1],
7958                             fromX, fromY, toX, toY);
7959             }
7960             if (appData.highlightLastMove) {
7961                 SetHighlights(fromX, fromY, toX, toY);
7962             }
7963         }
7964         currentMove = forwardMostMove;
7965     }
7966
7967     if (instant) return;
7968
7969     DisplayMove(currentMove - 1);
7970     DrawPosition(FALSE, boards[currentMove]);
7971     DisplayBothClocks();
7972     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7973 }
7974
7975 void SendEgtPath(ChessProgramState *cps)
7976 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7977         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7978
7979         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7980
7981         while(*p) {
7982             char c, *q = name+1, *r, *s;
7983
7984             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7985             while(*p && *p != ',') *q++ = *p++;
7986             *q++ = ':'; *q = 0;
7987             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7988                 strcmp(name, ",nalimov:") == 0 ) {
7989                 // take nalimov path from the menu-changeable option first, if it is defined
7990                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7991                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7992             } else
7993             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7994                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7995                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7996                 s = r = StrStr(s, ":") + 1; // beginning of path info
7997                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7998                 c = *r; *r = 0;             // temporarily null-terminate path info
7999                     *--q = 0;               // strip of trailig ':' from name
8000                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8001                 *r = c;
8002                 SendToProgram(buf,cps);     // send egtbpath command for this format
8003             }
8004             if(*p == ',') p++; // read away comma to position for next format name
8005         }
8006 }
8007
8008 void
8009 InitChessProgram(cps, setup)
8010      ChessProgramState *cps;
8011      int setup; /* [HGM] needed to setup FRC opening position */
8012 {
8013     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8014     if (appData.noChessProgram) return;
8015     hintRequested = FALSE;
8016     bookRequested = FALSE;
8017
8018     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8019     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8020     if(cps->memSize) { /* [HGM] memory */
8021         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8022         SendToProgram(buf, cps);
8023     }
8024     SendEgtPath(cps); /* [HGM] EGT */
8025     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8026         sprintf(buf, "cores %d\n", appData.smpCores);
8027         SendToProgram(buf, cps);
8028     }
8029
8030     SendToProgram(cps->initString, cps);
8031     if (gameInfo.variant != VariantNormal &&
8032         gameInfo.variant != VariantLoadable
8033         /* [HGM] also send variant if board size non-standard */
8034         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8035                                             ) {
8036       char *v = VariantName(gameInfo.variant);
8037       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8038         /* [HGM] in protocol 1 we have to assume all variants valid */
8039         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8040         DisplayFatalError(buf, 0, 1);
8041         return;
8042       }
8043
8044       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8045       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8046       if( gameInfo.variant == VariantXiangqi )
8047            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8048       if( gameInfo.variant == VariantShogi )
8049            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8050       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8051            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8052       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8053                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8054            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8055       if( gameInfo.variant == VariantCourier )
8056            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8057       if( gameInfo.variant == VariantSuper )
8058            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8059       if( gameInfo.variant == VariantGreat )
8060            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8061
8062       if(overruled) {
8063            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8064                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8065            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8066            if(StrStr(cps->variants, b) == NULL) { 
8067                // specific sized variant not known, check if general sizing allowed
8068                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8069                    if(StrStr(cps->variants, "boardsize") == NULL) {
8070                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8071                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8072                        DisplayFatalError(buf, 0, 1);
8073                        return;
8074                    }
8075                    /* [HGM] here we really should compare with the maximum supported board size */
8076                }
8077            }
8078       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8079       sprintf(buf, "variant %s\n", b);
8080       SendToProgram(buf, cps);
8081     }
8082     currentlyInitializedVariant = gameInfo.variant;
8083
8084     /* [HGM] send opening position in FRC to first engine */
8085     if(setup) {
8086           SendToProgram("force\n", cps);
8087           SendBoard(cps, 0);
8088           /* engine is now in force mode! Set flag to wake it up after first move. */
8089           setboardSpoiledMachineBlack = 1;
8090     }
8091
8092     if (cps->sendICS) {
8093       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8094       SendToProgram(buf, cps);
8095     }
8096     cps->maybeThinking = FALSE;
8097     cps->offeredDraw = 0;
8098     if (!appData.icsActive) {
8099         SendTimeControl(cps, movesPerSession, timeControl,
8100                         timeIncrement, appData.searchDepth,
8101                         searchTime);
8102     }
8103     if (appData.showThinking 
8104         // [HGM] thinking: four options require thinking output to be sent
8105         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8106                                 ) {
8107         SendToProgram("post\n", cps);
8108     }
8109     SendToProgram("hard\n", cps);
8110     if (!appData.ponderNextMove) {
8111         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8112            it without being sure what state we are in first.  "hard"
8113            is not a toggle, so that one is OK.
8114          */
8115         SendToProgram("easy\n", cps);
8116     }
8117     if (cps->usePing) {
8118       sprintf(buf, "ping %d\n", ++cps->lastPing);
8119       SendToProgram(buf, cps);
8120     }
8121     cps->initDone = TRUE;
8122 }   
8123
8124
8125 void
8126 StartChessProgram(cps)
8127      ChessProgramState *cps;
8128 {
8129     char buf[MSG_SIZ];
8130     int err;
8131
8132     if (appData.noChessProgram) return;
8133     cps->initDone = FALSE;
8134
8135     if (strcmp(cps->host, "localhost") == 0) {
8136         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8137     } else if (*appData.remoteShell == NULLCHAR) {
8138         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8139     } else {
8140         if (*appData.remoteUser == NULLCHAR) {
8141           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8142                     cps->program);
8143         } else {
8144           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8145                     cps->host, appData.remoteUser, cps->program);
8146         }
8147         err = StartChildProcess(buf, "", &cps->pr);
8148     }
8149     
8150     if (err != 0) {
8151         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8152         DisplayFatalError(buf, err, 1);
8153         cps->pr = NoProc;
8154         cps->isr = NULL;
8155         return;
8156     }
8157     
8158     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8159     if (cps->protocolVersion > 1) {
8160       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8161       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8162       cps->comboCnt = 0;  //                and values of combo boxes
8163       SendToProgram(buf, cps);
8164     } else {
8165       SendToProgram("xboard\n", cps);
8166     }
8167 }
8168
8169
8170 void
8171 TwoMachinesEventIfReady P((void))
8172 {
8173   if (first.lastPing != first.lastPong) {
8174     DisplayMessage("", _("Waiting for first chess program"));
8175     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8176     return;
8177   }
8178   if (second.lastPing != second.lastPong) {
8179     DisplayMessage("", _("Waiting for second chess program"));
8180     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8181     return;
8182   }
8183   ThawUI();
8184   TwoMachinesEvent();
8185 }
8186
8187 void
8188 NextMatchGame P((void))
8189 {
8190     int index; /* [HGM] autoinc: step load index during match */
8191     Reset(FALSE, TRUE);
8192     if (*appData.loadGameFile != NULLCHAR) {
8193         index = appData.loadGameIndex;
8194         if(index < 0) { // [HGM] autoinc
8195             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8196             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8197         } 
8198         LoadGameFromFile(appData.loadGameFile,
8199                          index,
8200                          appData.loadGameFile, FALSE);
8201     } else if (*appData.loadPositionFile != NULLCHAR) {
8202         index = appData.loadPositionIndex;
8203         if(index < 0) { // [HGM] autoinc
8204             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8205             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8206         } 
8207         LoadPositionFromFile(appData.loadPositionFile,
8208                              index,
8209                              appData.loadPositionFile);
8210     }
8211     TwoMachinesEventIfReady();
8212 }
8213
8214 void UserAdjudicationEvent( int result )
8215 {
8216     ChessMove gameResult = GameIsDrawn;
8217
8218     if( result > 0 ) {
8219         gameResult = WhiteWins;
8220     }
8221     else if( result < 0 ) {
8222         gameResult = BlackWins;
8223     }
8224
8225     if( gameMode == TwoMachinesPlay ) {
8226         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8227     }
8228 }
8229
8230
8231 // [HGM] save: calculate checksum of game to make games easily identifiable
8232 int StringCheckSum(char *s)
8233 {
8234         int i = 0;
8235         if(s==NULL) return 0;
8236         while(*s) i = i*259 + *s++;
8237         return i;
8238 }
8239
8240 int GameCheckSum()
8241 {
8242         int i, sum=0;
8243         for(i=backwardMostMove; i<forwardMostMove; i++) {
8244                 sum += pvInfoList[i].depth;
8245                 sum += StringCheckSum(parseList[i]);
8246                 sum += StringCheckSum(commentList[i]);
8247                 sum *= 261;
8248         }
8249         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8250         return sum + StringCheckSum(commentList[i]);
8251 } // end of save patch
8252
8253 void
8254 GameEnds(result, resultDetails, whosays)
8255      ChessMove result;
8256      char *resultDetails;
8257      int whosays;
8258 {
8259     GameMode nextGameMode;
8260     int isIcsGame;
8261     char buf[MSG_SIZ];
8262
8263     if(endingGame) return; /* [HGM] crash: forbid recursion */
8264     endingGame = 1;
8265
8266     if (appData.debugMode) {
8267       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8268               result, resultDetails ? resultDetails : "(null)", whosays);
8269     }
8270
8271     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8272         /* If we are playing on ICS, the server decides when the
8273            game is over, but the engine can offer to draw, claim 
8274            a draw, or resign. 
8275          */
8276 #if ZIPPY
8277         if (appData.zippyPlay && first.initDone) {
8278             if (result == GameIsDrawn) {
8279                 /* In case draw still needs to be claimed */
8280                 SendToICS(ics_prefix);
8281                 SendToICS("draw\n");
8282             } else if (StrCaseStr(resultDetails, "resign")) {
8283                 SendToICS(ics_prefix);
8284                 SendToICS("resign\n");
8285             }
8286         }
8287 #endif
8288         endingGame = 0; /* [HGM] crash */
8289         return;
8290     }
8291
8292     /* If we're loading the game from a file, stop */
8293     if (whosays == GE_FILE) {
8294       (void) StopLoadGameTimer();
8295       gameFileFP = NULL;
8296     }
8297
8298     /* Cancel draw offers */
8299     first.offeredDraw = second.offeredDraw = 0;
8300
8301     /* If this is an ICS game, only ICS can really say it's done;
8302        if not, anyone can. */
8303     isIcsGame = (gameMode == IcsPlayingWhite || 
8304                  gameMode == IcsPlayingBlack || 
8305                  gameMode == IcsObserving    || 
8306                  gameMode == IcsExamining);
8307
8308     if (!isIcsGame || whosays == GE_ICS) {
8309         /* OK -- not an ICS game, or ICS said it was done */
8310         StopClocks();
8311         if (!isIcsGame && !appData.noChessProgram) 
8312           SetUserThinkingEnables();
8313     
8314         /* [HGM] if a machine claims the game end we verify this claim */
8315         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8316             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8317                 char claimer;
8318                 ChessMove trueResult = (ChessMove) -1;
8319
8320                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8321                                             first.twoMachinesColor[0] :
8322                                             second.twoMachinesColor[0] ;
8323
8324                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8325                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8326                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8327                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8328                 } else
8329                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8330                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8331                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8332                 } else
8333                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8334                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8335                 }
8336
8337                 // now verify win claims, but not in drop games, as we don't understand those yet
8338                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8339                                                  || gameInfo.variant == VariantGreat) &&
8340                     (result == WhiteWins && claimer == 'w' ||
8341                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8342                       if (appData.debugMode) {
8343                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8344                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8345                       }
8346                       if(result != trueResult) {
8347                               sprintf(buf, "False win claim: '%s'", resultDetails);
8348                               result = claimer == 'w' ? BlackWins : WhiteWins;
8349                               resultDetails = buf;
8350                       }
8351                 } else
8352                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8353                     && (forwardMostMove <= backwardMostMove ||
8354                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8355                         (claimer=='b')==(forwardMostMove&1))
8356                                                                                   ) {
8357                       /* [HGM] verify: draws that were not flagged are false claims */
8358                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8359                       result = claimer == 'w' ? BlackWins : WhiteWins;
8360                       resultDetails = buf;
8361                 }
8362                 /* (Claiming a loss is accepted no questions asked!) */
8363             }
8364             /* [HGM] bare: don't allow bare King to win */
8365             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8366                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8367                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8368                && result != GameIsDrawn)
8369             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8370                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8371                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8372                         if(p >= 0 && p <= (int)WhiteKing) k++;
8373                 }
8374                 if (appData.debugMode) {
8375                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8376                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8377                 }
8378                 if(k <= 1) {
8379                         result = GameIsDrawn;
8380                         sprintf(buf, "%s but bare king", resultDetails);
8381                         resultDetails = buf;
8382                 }
8383             }
8384         }
8385
8386
8387         if(serverMoves != NULL && !loadFlag) { char c = '=';
8388             if(result==WhiteWins) c = '+';
8389             if(result==BlackWins) c = '-';
8390             if(resultDetails != NULL)
8391                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8392         }
8393         if (resultDetails != NULL) {
8394             gameInfo.result = result;
8395             gameInfo.resultDetails = StrSave(resultDetails);
8396
8397             /* display last move only if game was not loaded from file */
8398             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8399                 DisplayMove(currentMove - 1);
8400     
8401             if (forwardMostMove != 0) {
8402                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8403                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8404                                                                 ) {
8405                     if (*appData.saveGameFile != NULLCHAR) {
8406                         SaveGameToFile(appData.saveGameFile, TRUE);
8407                     } else if (appData.autoSaveGames) {
8408                         AutoSaveGame();
8409                     }
8410                     if (*appData.savePositionFile != NULLCHAR) {
8411                         SavePositionToFile(appData.savePositionFile);
8412                     }
8413                 }
8414             }
8415
8416             /* Tell program how game ended in case it is learning */
8417             /* [HGM] Moved this to after saving the PGN, just in case */
8418             /* engine died and we got here through time loss. In that */
8419             /* case we will get a fatal error writing the pipe, which */
8420             /* would otherwise lose us the PGN.                       */
8421             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8422             /* output during GameEnds should never be fatal anymore   */
8423             if (gameMode == MachinePlaysWhite ||
8424                 gameMode == MachinePlaysBlack ||
8425                 gameMode == TwoMachinesPlay ||
8426                 gameMode == IcsPlayingWhite ||
8427                 gameMode == IcsPlayingBlack ||
8428                 gameMode == BeginningOfGame) {
8429                 char buf[MSG_SIZ];
8430                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8431                         resultDetails);
8432                 if (first.pr != NoProc) {
8433                     SendToProgram(buf, &first);
8434                 }
8435                 if (second.pr != NoProc &&
8436                     gameMode == TwoMachinesPlay) {
8437                     SendToProgram(buf, &second);
8438                 }
8439             }
8440         }
8441
8442         if (appData.icsActive) {
8443             if (appData.quietPlay &&
8444                 (gameMode == IcsPlayingWhite ||
8445                  gameMode == IcsPlayingBlack)) {
8446                 SendToICS(ics_prefix);
8447                 SendToICS("set shout 1\n");
8448             }
8449             nextGameMode = IcsIdle;
8450             ics_user_moved = FALSE;
8451             /* clean up premove.  It's ugly when the game has ended and the
8452              * premove highlights are still on the board.
8453              */
8454             if (gotPremove) {
8455               gotPremove = FALSE;
8456               ClearPremoveHighlights();
8457               DrawPosition(FALSE, boards[currentMove]);
8458             }
8459             if (whosays == GE_ICS) {
8460                 switch (result) {
8461                 case WhiteWins:
8462                     if (gameMode == IcsPlayingWhite)
8463                         PlayIcsWinSound();
8464                     else if(gameMode == IcsPlayingBlack)
8465                         PlayIcsLossSound();
8466                     break;
8467                 case BlackWins:
8468                     if (gameMode == IcsPlayingBlack)
8469                         PlayIcsWinSound();
8470                     else if(gameMode == IcsPlayingWhite)
8471                         PlayIcsLossSound();
8472                     break;
8473                 case GameIsDrawn:
8474                     PlayIcsDrawSound();
8475                     break;
8476                 default:
8477                     PlayIcsUnfinishedSound();
8478                 }
8479             }
8480         } else if (gameMode == EditGame ||
8481                    gameMode == PlayFromGameFile || 
8482                    gameMode == AnalyzeMode || 
8483                    gameMode == AnalyzeFile) {
8484             nextGameMode = gameMode;
8485         } else {
8486             nextGameMode = EndOfGame;
8487         }
8488         pausing = FALSE;
8489         ModeHighlight();
8490     } else {
8491         nextGameMode = gameMode;
8492     }
8493
8494     if (appData.noChessProgram) {
8495         gameMode = nextGameMode;
8496         ModeHighlight();
8497         endingGame = 0; /* [HGM] crash */
8498         return;
8499     }
8500
8501     if (first.reuse) {
8502         /* Put first chess program into idle state */
8503         if (first.pr != NoProc &&
8504             (gameMode == MachinePlaysWhite ||
8505              gameMode == MachinePlaysBlack ||
8506              gameMode == TwoMachinesPlay ||
8507              gameMode == IcsPlayingWhite ||
8508              gameMode == IcsPlayingBlack ||
8509              gameMode == BeginningOfGame)) {
8510             SendToProgram("force\n", &first);
8511             if (first.usePing) {
8512               char buf[MSG_SIZ];
8513               sprintf(buf, "ping %d\n", ++first.lastPing);
8514               SendToProgram(buf, &first);
8515             }
8516         }
8517     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8518         /* Kill off first chess program */
8519         if (first.isr != NULL)
8520           RemoveInputSource(first.isr);
8521         first.isr = NULL;
8522     
8523         if (first.pr != NoProc) {
8524             ExitAnalyzeMode();
8525             DoSleep( appData.delayBeforeQuit );
8526             SendToProgram("quit\n", &first);
8527             DoSleep( appData.delayAfterQuit );
8528             DestroyChildProcess(first.pr, first.useSigterm);
8529         }
8530         first.pr = NoProc;
8531     }
8532     if (second.reuse) {
8533         /* Put second chess program into idle state */
8534         if (second.pr != NoProc &&
8535             gameMode == TwoMachinesPlay) {
8536             SendToProgram("force\n", &second);
8537             if (second.usePing) {
8538               char buf[MSG_SIZ];
8539               sprintf(buf, "ping %d\n", ++second.lastPing);
8540               SendToProgram(buf, &second);
8541             }
8542         }
8543     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8544         /* Kill off second chess program */
8545         if (second.isr != NULL)
8546           RemoveInputSource(second.isr);
8547         second.isr = NULL;
8548     
8549         if (second.pr != NoProc) {
8550             DoSleep( appData.delayBeforeQuit );
8551             SendToProgram("quit\n", &second);
8552             DoSleep( appData.delayAfterQuit );
8553             DestroyChildProcess(second.pr, second.useSigterm);
8554         }
8555         second.pr = NoProc;
8556     }
8557
8558     if (matchMode && gameMode == TwoMachinesPlay) {
8559         switch (result) {
8560         case WhiteWins:
8561           if (first.twoMachinesColor[0] == 'w') {
8562             first.matchWins++;
8563           } else {
8564             second.matchWins++;
8565           }
8566           break;
8567         case BlackWins:
8568           if (first.twoMachinesColor[0] == 'b') {
8569             first.matchWins++;
8570           } else {
8571             second.matchWins++;
8572           }
8573           break;
8574         default:
8575           break;
8576         }
8577         if (matchGame < appData.matchGames) {
8578             char *tmp;
8579             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8580                 tmp = first.twoMachinesColor;
8581                 first.twoMachinesColor = second.twoMachinesColor;
8582                 second.twoMachinesColor = tmp;
8583             }
8584             gameMode = nextGameMode;
8585             matchGame++;
8586             if(appData.matchPause>10000 || appData.matchPause<10)
8587                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8588             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8589             endingGame = 0; /* [HGM] crash */
8590             return;
8591         } else {
8592             char buf[MSG_SIZ];
8593             gameMode = nextGameMode;
8594             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8595                     first.tidy, second.tidy,
8596                     first.matchWins, second.matchWins,
8597                     appData.matchGames - (first.matchWins + second.matchWins));
8598             DisplayFatalError(buf, 0, 0);
8599         }
8600     }
8601     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8602         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8603       ExitAnalyzeMode();
8604     gameMode = nextGameMode;
8605     ModeHighlight();
8606     endingGame = 0;  /* [HGM] crash */
8607 }
8608
8609 /* Assumes program was just initialized (initString sent).
8610    Leaves program in force mode. */
8611 void
8612 FeedMovesToProgram(cps, upto) 
8613      ChessProgramState *cps;
8614      int upto;
8615 {
8616     int i;
8617     
8618     if (appData.debugMode)
8619       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8620               startedFromSetupPosition ? "position and " : "",
8621               backwardMostMove, upto, cps->which);
8622     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8623         // [HGM] variantswitch: make engine aware of new variant
8624         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8625                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8626         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8627         SendToProgram(buf, cps);
8628         currentlyInitializedVariant = gameInfo.variant;
8629     }
8630     SendToProgram("force\n", cps);
8631     if (startedFromSetupPosition) {
8632         SendBoard(cps, backwardMostMove);
8633     if (appData.debugMode) {
8634         fprintf(debugFP, "feedMoves\n");
8635     }
8636     }
8637     for (i = backwardMostMove; i < upto; i++) {
8638         SendMoveToProgram(i, cps);
8639     }
8640 }
8641
8642
8643 void
8644 ResurrectChessProgram()
8645 {
8646      /* The chess program may have exited.
8647         If so, restart it and feed it all the moves made so far. */
8648
8649     if (appData.noChessProgram || first.pr != NoProc) return;
8650     
8651     StartChessProgram(&first);
8652     InitChessProgram(&first, FALSE);
8653     FeedMovesToProgram(&first, currentMove);
8654
8655     if (!first.sendTime) {
8656         /* can't tell gnuchess what its clock should read,
8657            so we bow to its notion. */
8658         ResetClocks();
8659         timeRemaining[0][currentMove] = whiteTimeRemaining;
8660         timeRemaining[1][currentMove] = blackTimeRemaining;
8661     }
8662
8663     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8664                 appData.icsEngineAnalyze) && first.analysisSupport) {
8665       SendToProgram("analyze\n", &first);
8666       first.analyzing = TRUE;
8667     }
8668 }
8669
8670 /*
8671  * Button procedures
8672  */
8673 void
8674 Reset(redraw, init)
8675      int redraw, init;
8676 {
8677     int i;
8678
8679     if (appData.debugMode) {
8680         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8681                 redraw, init, gameMode);
8682     }
8683     CleanupTail(); // [HGM] vari: delete any stored variations
8684     pausing = pauseExamInvalid = FALSE;
8685     startedFromSetupPosition = blackPlaysFirst = FALSE;
8686     firstMove = TRUE;
8687     whiteFlag = blackFlag = FALSE;
8688     userOfferedDraw = FALSE;
8689     hintRequested = bookRequested = FALSE;
8690     first.maybeThinking = FALSE;
8691     second.maybeThinking = FALSE;
8692     first.bookSuspend = FALSE; // [HGM] book
8693     second.bookSuspend = FALSE;
8694     thinkOutput[0] = NULLCHAR;
8695     lastHint[0] = NULLCHAR;
8696     ClearGameInfo(&gameInfo);
8697     gameInfo.variant = StringToVariant(appData.variant);
8698     ics_user_moved = ics_clock_paused = FALSE;
8699     ics_getting_history = H_FALSE;
8700     ics_gamenum = -1;
8701     white_holding[0] = black_holding[0] = NULLCHAR;
8702     ClearProgramStats();
8703     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8704     
8705     ResetFrontEnd();
8706     ClearHighlights();
8707     flipView = appData.flipView;
8708     ClearPremoveHighlights();
8709     gotPremove = FALSE;
8710     alarmSounded = FALSE;
8711
8712     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8713     if(appData.serverMovesName != NULL) {
8714         /* [HGM] prepare to make moves file for broadcasting */
8715         clock_t t = clock();
8716         if(serverMoves != NULL) fclose(serverMoves);
8717         serverMoves = fopen(appData.serverMovesName, "r");
8718         if(serverMoves != NULL) {
8719             fclose(serverMoves);
8720             /* delay 15 sec before overwriting, so all clients can see end */
8721             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8722         }
8723         serverMoves = fopen(appData.serverMovesName, "w");
8724     }
8725
8726     ExitAnalyzeMode();
8727     gameMode = BeginningOfGame;
8728     ModeHighlight();
8729     if(appData.icsActive) gameInfo.variant = VariantNormal;
8730     currentMove = forwardMostMove = backwardMostMove = 0;
8731     InitPosition(redraw);
8732     for (i = 0; i < MAX_MOVES; i++) {
8733         if (commentList[i] != NULL) {
8734             free(commentList[i]);
8735             commentList[i] = NULL;
8736         }
8737     }
8738     ResetClocks();
8739     timeRemaining[0][0] = whiteTimeRemaining;
8740     timeRemaining[1][0] = blackTimeRemaining;
8741     if (first.pr == NULL) {
8742         StartChessProgram(&first);
8743     }
8744     if (init) {
8745             InitChessProgram(&first, startedFromSetupPosition);
8746     }
8747     DisplayTitle("");
8748     DisplayMessage("", "");
8749     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8750     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8751 }
8752
8753 void
8754 AutoPlayGameLoop()
8755 {
8756     for (;;) {
8757         if (!AutoPlayOneMove())
8758           return;
8759         if (matchMode || appData.timeDelay == 0)
8760           continue;
8761         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8762           return;
8763         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8764         break;
8765     }
8766 }
8767
8768
8769 int
8770 AutoPlayOneMove()
8771 {
8772     int fromX, fromY, toX, toY;
8773
8774     if (appData.debugMode) {
8775       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8776     }
8777
8778     if (gameMode != PlayFromGameFile)
8779       return FALSE;
8780
8781     if (currentMove >= forwardMostMove) {
8782       gameMode = EditGame;
8783       ModeHighlight();
8784
8785       /* [AS] Clear current move marker at the end of a game */
8786       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8787
8788       return FALSE;
8789     }
8790     
8791     toX = moveList[currentMove][2] - AAA;
8792     toY = moveList[currentMove][3] - ONE;
8793
8794     if (moveList[currentMove][1] == '@') {
8795         if (appData.highlightLastMove) {
8796             SetHighlights(-1, -1, toX, toY);
8797         }
8798     } else {
8799         fromX = moveList[currentMove][0] - AAA;
8800         fromY = moveList[currentMove][1] - ONE;
8801
8802         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8803
8804         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8805
8806         if (appData.highlightLastMove) {
8807             SetHighlights(fromX, fromY, toX, toY);
8808         }
8809     }
8810     DisplayMove(currentMove);
8811     SendMoveToProgram(currentMove++, &first);
8812     DisplayBothClocks();
8813     DrawPosition(FALSE, boards[currentMove]);
8814     // [HGM] PV info: always display, routine tests if empty
8815     DisplayComment(currentMove - 1, commentList[currentMove]);
8816     return TRUE;
8817 }
8818
8819
8820 int
8821 LoadGameOneMove(readAhead)
8822      ChessMove readAhead;
8823 {
8824     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8825     char promoChar = NULLCHAR;
8826     ChessMove moveType;
8827     char move[MSG_SIZ];
8828     char *p, *q;
8829     
8830     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8831         gameMode != AnalyzeMode && gameMode != Training) {
8832         gameFileFP = NULL;
8833         return FALSE;
8834     }
8835     
8836     yyboardindex = forwardMostMove;
8837     if (readAhead != (ChessMove)0) {
8838       moveType = readAhead;
8839     } else {
8840       if (gameFileFP == NULL)
8841           return FALSE;
8842       moveType = (ChessMove) yylex();
8843     }
8844     
8845     done = FALSE;
8846     switch (moveType) {
8847       case Comment:
8848         if (appData.debugMode) 
8849           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8850         p = yy_text;
8851
8852         /* append the comment but don't display it */
8853         AppendComment(currentMove, p, FALSE);
8854         return TRUE;
8855
8856       case WhiteCapturesEnPassant:
8857       case BlackCapturesEnPassant:
8858       case WhitePromotionChancellor:
8859       case BlackPromotionChancellor:
8860       case WhitePromotionArchbishop:
8861       case BlackPromotionArchbishop:
8862       case WhitePromotionCentaur:
8863       case BlackPromotionCentaur:
8864       case WhitePromotionQueen:
8865       case BlackPromotionQueen:
8866       case WhitePromotionRook:
8867       case BlackPromotionRook:
8868       case WhitePromotionBishop:
8869       case BlackPromotionBishop:
8870       case WhitePromotionKnight:
8871       case BlackPromotionKnight:
8872       case WhitePromotionKing:
8873       case BlackPromotionKing:
8874       case NormalMove:
8875       case WhiteKingSideCastle:
8876       case WhiteQueenSideCastle:
8877       case BlackKingSideCastle:
8878       case BlackQueenSideCastle:
8879       case WhiteKingSideCastleWild:
8880       case WhiteQueenSideCastleWild:
8881       case BlackKingSideCastleWild:
8882       case BlackQueenSideCastleWild:
8883       /* PUSH Fabien */
8884       case WhiteHSideCastleFR:
8885       case WhiteASideCastleFR:
8886       case BlackHSideCastleFR:
8887       case BlackASideCastleFR:
8888       /* POP Fabien */
8889         if (appData.debugMode)
8890           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8891         fromX = currentMoveString[0] - AAA;
8892         fromY = currentMoveString[1] - ONE;
8893         toX = currentMoveString[2] - AAA;
8894         toY = currentMoveString[3] - ONE;
8895         promoChar = currentMoveString[4];
8896         break;
8897
8898       case WhiteDrop:
8899       case BlackDrop:
8900         if (appData.debugMode)
8901           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8902         fromX = moveType == WhiteDrop ?
8903           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8904         (int) CharToPiece(ToLower(currentMoveString[0]));
8905         fromY = DROP_RANK;
8906         toX = currentMoveString[2] - AAA;
8907         toY = currentMoveString[3] - ONE;
8908         break;
8909
8910       case WhiteWins:
8911       case BlackWins:
8912       case GameIsDrawn:
8913       case GameUnfinished:
8914         if (appData.debugMode)
8915           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8916         p = strchr(yy_text, '{');
8917         if (p == NULL) p = strchr(yy_text, '(');
8918         if (p == NULL) {
8919             p = yy_text;
8920             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8921         } else {
8922             q = strchr(p, *p == '{' ? '}' : ')');
8923             if (q != NULL) *q = NULLCHAR;
8924             p++;
8925         }
8926         GameEnds(moveType, p, GE_FILE);
8927         done = TRUE;
8928         if (cmailMsgLoaded) {
8929             ClearHighlights();
8930             flipView = WhiteOnMove(currentMove);
8931             if (moveType == GameUnfinished) flipView = !flipView;
8932             if (appData.debugMode)
8933               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8934         }
8935         break;
8936
8937       case (ChessMove) 0:       /* end of file */
8938         if (appData.debugMode)
8939           fprintf(debugFP, "Parser hit end of file\n");
8940         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8941           case MT_NONE:
8942           case MT_CHECK:
8943             break;
8944           case MT_CHECKMATE:
8945           case MT_STAINMATE:
8946             if (WhiteOnMove(currentMove)) {
8947                 GameEnds(BlackWins, "Black mates", GE_FILE);
8948             } else {
8949                 GameEnds(WhiteWins, "White mates", GE_FILE);
8950             }
8951             break;
8952           case MT_STALEMATE:
8953             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8954             break;
8955         }
8956         done = TRUE;
8957         break;
8958
8959       case MoveNumberOne:
8960         if (lastLoadGameStart == GNUChessGame) {
8961             /* GNUChessGames have numbers, but they aren't move numbers */
8962             if (appData.debugMode)
8963               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8964                       yy_text, (int) moveType);
8965             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8966         }
8967         /* else fall thru */
8968
8969       case XBoardGame:
8970       case GNUChessGame:
8971       case PGNTag:
8972         /* Reached start of next game in file */
8973         if (appData.debugMode)
8974           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8975         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8976           case MT_NONE:
8977           case MT_CHECK:
8978             break;
8979           case MT_CHECKMATE:
8980           case MT_STAINMATE:
8981             if (WhiteOnMove(currentMove)) {
8982                 GameEnds(BlackWins, "Black mates", GE_FILE);
8983             } else {
8984                 GameEnds(WhiteWins, "White mates", GE_FILE);
8985             }
8986             break;
8987           case MT_STALEMATE:
8988             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8989             break;
8990         }
8991         done = TRUE;
8992         break;
8993
8994       case PositionDiagram:     /* should not happen; ignore */
8995       case ElapsedTime:         /* ignore */
8996       case NAG:                 /* ignore */
8997         if (appData.debugMode)
8998           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8999                   yy_text, (int) moveType);
9000         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9001
9002       case IllegalMove:
9003         if (appData.testLegality) {
9004             if (appData.debugMode)
9005               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9006             sprintf(move, _("Illegal move: %d.%s%s"),
9007                     (forwardMostMove / 2) + 1,
9008                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9009             DisplayError(move, 0);
9010             done = TRUE;
9011         } else {
9012             if (appData.debugMode)
9013               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9014                       yy_text, currentMoveString);
9015             fromX = currentMoveString[0] - AAA;
9016             fromY = currentMoveString[1] - ONE;
9017             toX = currentMoveString[2] - AAA;
9018             toY = currentMoveString[3] - ONE;
9019             promoChar = currentMoveString[4];
9020         }
9021         break;
9022
9023       case AmbiguousMove:
9024         if (appData.debugMode)
9025           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9026         sprintf(move, _("Ambiguous move: %d.%s%s"),
9027                 (forwardMostMove / 2) + 1,
9028                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9029         DisplayError(move, 0);
9030         done = TRUE;
9031         break;
9032
9033       default:
9034       case ImpossibleMove:
9035         if (appData.debugMode)
9036           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9037         sprintf(move, _("Illegal move: %d.%s%s"),
9038                 (forwardMostMove / 2) + 1,
9039                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9040         DisplayError(move, 0);
9041         done = TRUE;
9042         break;
9043     }
9044
9045     if (done) {
9046         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9047             DrawPosition(FALSE, boards[currentMove]);
9048             DisplayBothClocks();
9049             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9050               DisplayComment(currentMove - 1, commentList[currentMove]);
9051         }
9052         (void) StopLoadGameTimer();
9053         gameFileFP = NULL;
9054         cmailOldMove = forwardMostMove;
9055         return FALSE;
9056     } else {
9057         /* currentMoveString is set as a side-effect of yylex */
9058         strcat(currentMoveString, "\n");
9059         strcpy(moveList[forwardMostMove], currentMoveString);
9060         
9061         thinkOutput[0] = NULLCHAR;
9062         MakeMove(fromX, fromY, toX, toY, promoChar);
9063         currentMove = forwardMostMove;
9064         return TRUE;
9065     }
9066 }
9067
9068 /* Load the nth game from the given file */
9069 int
9070 LoadGameFromFile(filename, n, title, useList)
9071      char *filename;
9072      int n;
9073      char *title;
9074      /*Boolean*/ int useList;
9075 {
9076     FILE *f;
9077     char buf[MSG_SIZ];
9078
9079     if (strcmp(filename, "-") == 0) {
9080         f = stdin;
9081         title = "stdin";
9082     } else {
9083         f = fopen(filename, "rb");
9084         if (f == NULL) {
9085           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9086             DisplayError(buf, errno);
9087             return FALSE;
9088         }
9089     }
9090     if (fseek(f, 0, 0) == -1) {
9091         /* f is not seekable; probably a pipe */
9092         useList = FALSE;
9093     }
9094     if (useList && n == 0) {
9095         int error = GameListBuild(f);
9096         if (error) {
9097             DisplayError(_("Cannot build game list"), error);
9098         } else if (!ListEmpty(&gameList) &&
9099                    ((ListGame *) gameList.tailPred)->number > 1) {
9100             GameListPopUp(f, title);
9101             return TRUE;
9102         }
9103         GameListDestroy();
9104         n = 1;
9105     }
9106     if (n == 0) n = 1;
9107     return LoadGame(f, n, title, FALSE);
9108 }
9109
9110
9111 void
9112 MakeRegisteredMove()
9113 {
9114     int fromX, fromY, toX, toY;
9115     char promoChar;
9116     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9117         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9118           case CMAIL_MOVE:
9119           case CMAIL_DRAW:
9120             if (appData.debugMode)
9121               fprintf(debugFP, "Restoring %s for game %d\n",
9122                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9123     
9124             thinkOutput[0] = NULLCHAR;
9125             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9126             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9127             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9128             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9129             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9130             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9131             MakeMove(fromX, fromY, toX, toY, promoChar);
9132             ShowMove(fromX, fromY, toX, toY);
9133               
9134             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9135               case MT_NONE:
9136               case MT_CHECK:
9137                 break;
9138                 
9139               case MT_CHECKMATE:
9140               case MT_STAINMATE:
9141                 if (WhiteOnMove(currentMove)) {
9142                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9143                 } else {
9144                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9145                 }
9146                 break;
9147                 
9148               case MT_STALEMATE:
9149                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9150                 break;
9151             }
9152
9153             break;
9154             
9155           case CMAIL_RESIGN:
9156             if (WhiteOnMove(currentMove)) {
9157                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9158             } else {
9159                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9160             }
9161             break;
9162             
9163           case CMAIL_ACCEPT:
9164             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9165             break;
9166               
9167           default:
9168             break;
9169         }
9170     }
9171
9172     return;
9173 }
9174
9175 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9176 int
9177 CmailLoadGame(f, gameNumber, title, useList)
9178      FILE *f;
9179      int gameNumber;
9180      char *title;
9181      int useList;
9182 {
9183     int retVal;
9184
9185     if (gameNumber > nCmailGames) {
9186         DisplayError(_("No more games in this message"), 0);
9187         return FALSE;
9188     }
9189     if (f == lastLoadGameFP) {
9190         int offset = gameNumber - lastLoadGameNumber;
9191         if (offset == 0) {
9192             cmailMsg[0] = NULLCHAR;
9193             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9194                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9195                 nCmailMovesRegistered--;
9196             }
9197             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9198             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9199                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9200             }
9201         } else {
9202             if (! RegisterMove()) return FALSE;
9203         }
9204     }
9205
9206     retVal = LoadGame(f, gameNumber, title, useList);
9207
9208     /* Make move registered during previous look at this game, if any */
9209     MakeRegisteredMove();
9210
9211     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9212         commentList[currentMove]
9213           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9214         DisplayComment(currentMove - 1, commentList[currentMove]);
9215     }
9216
9217     return retVal;
9218 }
9219
9220 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9221 int
9222 ReloadGame(offset)
9223      int offset;
9224 {
9225     int gameNumber = lastLoadGameNumber + offset;
9226     if (lastLoadGameFP == NULL) {
9227         DisplayError(_("No game has been loaded yet"), 0);
9228         return FALSE;
9229     }
9230     if (gameNumber <= 0) {
9231         DisplayError(_("Can't back up any further"), 0);
9232         return FALSE;
9233     }
9234     if (cmailMsgLoaded) {
9235         return CmailLoadGame(lastLoadGameFP, gameNumber,
9236                              lastLoadGameTitle, lastLoadGameUseList);
9237     } else {
9238         return LoadGame(lastLoadGameFP, gameNumber,
9239                         lastLoadGameTitle, lastLoadGameUseList);
9240     }
9241 }
9242
9243
9244
9245 /* Load the nth game from open file f */
9246 int
9247 LoadGame(f, gameNumber, title, useList)
9248      FILE *f;
9249      int gameNumber;
9250      char *title;
9251      int useList;
9252 {
9253     ChessMove cm;
9254     char buf[MSG_SIZ];
9255     int gn = gameNumber;
9256     ListGame *lg = NULL;
9257     int numPGNTags = 0;
9258     int err;
9259     GameMode oldGameMode;
9260     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9261
9262     if (appData.debugMode) 
9263         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9264
9265     if (gameMode == Training )
9266         SetTrainingModeOff();
9267
9268     oldGameMode = gameMode;
9269     if (gameMode != BeginningOfGame) {
9270       Reset(FALSE, TRUE);
9271     }
9272
9273     gameFileFP = f;
9274     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9275         fclose(lastLoadGameFP);
9276     }
9277
9278     if (useList) {
9279         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9280         
9281         if (lg) {
9282             fseek(f, lg->offset, 0);
9283             GameListHighlight(gameNumber);
9284             gn = 1;
9285         }
9286         else {
9287             DisplayError(_("Game number out of range"), 0);
9288             return FALSE;
9289         }
9290     } else {
9291         GameListDestroy();
9292         if (fseek(f, 0, 0) == -1) {
9293             if (f == lastLoadGameFP ?
9294                 gameNumber == lastLoadGameNumber + 1 :
9295                 gameNumber == 1) {
9296                 gn = 1;
9297             } else {
9298                 DisplayError(_("Can't seek on game file"), 0);
9299                 return FALSE;
9300             }
9301         }
9302     }
9303     lastLoadGameFP = f;
9304     lastLoadGameNumber = gameNumber;
9305     strcpy(lastLoadGameTitle, title);
9306     lastLoadGameUseList = useList;
9307
9308     yynewfile(f);
9309
9310     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9311       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9312                 lg->gameInfo.black);
9313             DisplayTitle(buf);
9314     } else if (*title != NULLCHAR) {
9315         if (gameNumber > 1) {
9316             sprintf(buf, "%s %d", title, gameNumber);
9317             DisplayTitle(buf);
9318         } else {
9319             DisplayTitle(title);
9320         }
9321     }
9322
9323     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9324         gameMode = PlayFromGameFile;
9325         ModeHighlight();
9326     }
9327
9328     currentMove = forwardMostMove = backwardMostMove = 0;
9329     CopyBoard(boards[0], initialPosition);
9330     StopClocks();
9331
9332     /*
9333      * Skip the first gn-1 games in the file.
9334      * Also skip over anything that precedes an identifiable 
9335      * start of game marker, to avoid being confused by 
9336      * garbage at the start of the file.  Currently 
9337      * recognized start of game markers are the move number "1",
9338      * the pattern "gnuchess .* game", the pattern
9339      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9340      * A game that starts with one of the latter two patterns
9341      * will also have a move number 1, possibly
9342      * following a position diagram.
9343      * 5-4-02: Let's try being more lenient and allowing a game to
9344      * start with an unnumbered move.  Does that break anything?
9345      */
9346     cm = lastLoadGameStart = (ChessMove) 0;
9347     while (gn > 0) {
9348         yyboardindex = forwardMostMove;
9349         cm = (ChessMove) yylex();
9350         switch (cm) {
9351           case (ChessMove) 0:
9352             if (cmailMsgLoaded) {
9353                 nCmailGames = CMAIL_MAX_GAMES - gn;
9354             } else {
9355                 Reset(TRUE, TRUE);
9356                 DisplayError(_("Game not found in file"), 0);
9357             }
9358             return FALSE;
9359
9360           case GNUChessGame:
9361           case XBoardGame:
9362             gn--;
9363             lastLoadGameStart = cm;
9364             break;
9365             
9366           case MoveNumberOne:
9367             switch (lastLoadGameStart) {
9368               case GNUChessGame:
9369               case XBoardGame:
9370               case PGNTag:
9371                 break;
9372               case MoveNumberOne:
9373               case (ChessMove) 0:
9374                 gn--;           /* count this game */
9375                 lastLoadGameStart = cm;
9376                 break;
9377               default:
9378                 /* impossible */
9379                 break;
9380             }
9381             break;
9382
9383           case PGNTag:
9384             switch (lastLoadGameStart) {
9385               case GNUChessGame:
9386               case PGNTag:
9387               case MoveNumberOne:
9388               case (ChessMove) 0:
9389                 gn--;           /* count this game */
9390                 lastLoadGameStart = cm;
9391                 break;
9392               case XBoardGame:
9393                 lastLoadGameStart = cm; /* game counted already */
9394                 break;
9395               default:
9396                 /* impossible */
9397                 break;
9398             }
9399             if (gn > 0) {
9400                 do {
9401                     yyboardindex = forwardMostMove;
9402                     cm = (ChessMove) yylex();
9403                 } while (cm == PGNTag || cm == Comment);
9404             }
9405             break;
9406
9407           case WhiteWins:
9408           case BlackWins:
9409           case GameIsDrawn:
9410             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9411                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9412                     != CMAIL_OLD_RESULT) {
9413                     nCmailResults ++ ;
9414                     cmailResult[  CMAIL_MAX_GAMES
9415                                 - gn - 1] = CMAIL_OLD_RESULT;
9416                 }
9417             }
9418             break;
9419
9420           case NormalMove:
9421             /* Only a NormalMove can be at the start of a game
9422              * without a position diagram. */
9423             if (lastLoadGameStart == (ChessMove) 0) {
9424               gn--;
9425               lastLoadGameStart = MoveNumberOne;
9426             }
9427             break;
9428
9429           default:
9430             break;
9431         }
9432     }
9433     
9434     if (appData.debugMode)
9435       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9436
9437     if (cm == XBoardGame) {
9438         /* Skip any header junk before position diagram and/or move 1 */
9439         for (;;) {
9440             yyboardindex = forwardMostMove;
9441             cm = (ChessMove) yylex();
9442
9443             if (cm == (ChessMove) 0 ||
9444                 cm == GNUChessGame || cm == XBoardGame) {
9445                 /* Empty game; pretend end-of-file and handle later */
9446                 cm = (ChessMove) 0;
9447                 break;
9448             }
9449
9450             if (cm == MoveNumberOne || cm == PositionDiagram ||
9451                 cm == PGNTag || cm == Comment)
9452               break;
9453         }
9454     } else if (cm == GNUChessGame) {
9455         if (gameInfo.event != NULL) {
9456             free(gameInfo.event);
9457         }
9458         gameInfo.event = StrSave(yy_text);
9459     }   
9460
9461     startedFromSetupPosition = FALSE;
9462     while (cm == PGNTag) {
9463         if (appData.debugMode) 
9464           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9465         err = ParsePGNTag(yy_text, &gameInfo);
9466         if (!err) numPGNTags++;
9467
9468         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9469         if(gameInfo.variant != oldVariant) {
9470             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9471             InitPosition(TRUE);
9472             oldVariant = gameInfo.variant;
9473             if (appData.debugMode) 
9474               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9475         }
9476
9477
9478         if (gameInfo.fen != NULL) {
9479           Board initial_position;
9480           startedFromSetupPosition = TRUE;
9481           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9482             Reset(TRUE, TRUE);
9483             DisplayError(_("Bad FEN position in file"), 0);
9484             return FALSE;
9485           }
9486           CopyBoard(boards[0], initial_position);
9487           if (blackPlaysFirst) {
9488             currentMove = forwardMostMove = backwardMostMove = 1;
9489             CopyBoard(boards[1], initial_position);
9490             strcpy(moveList[0], "");
9491             strcpy(parseList[0], "");
9492             timeRemaining[0][1] = whiteTimeRemaining;
9493             timeRemaining[1][1] = blackTimeRemaining;
9494             if (commentList[0] != NULL) {
9495               commentList[1] = commentList[0];
9496               commentList[0] = NULL;
9497             }
9498           } else {
9499             currentMove = forwardMostMove = backwardMostMove = 0;
9500           }
9501           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9502           {   int i;
9503               initialRulePlies = FENrulePlies;
9504               for( i=0; i< nrCastlingRights; i++ )
9505                   initialRights[i] = initial_position[CASTLING][i];
9506           }
9507           yyboardindex = forwardMostMove;
9508           free(gameInfo.fen);
9509           gameInfo.fen = NULL;
9510         }
9511
9512         yyboardindex = forwardMostMove;
9513         cm = (ChessMove) yylex();
9514
9515         /* Handle comments interspersed among the tags */
9516         while (cm == Comment) {
9517             char *p;
9518             if (appData.debugMode) 
9519               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9520             p = yy_text;
9521             AppendComment(currentMove, p, FALSE);
9522             yyboardindex = forwardMostMove;
9523             cm = (ChessMove) yylex();
9524         }
9525     }
9526
9527     /* don't rely on existence of Event tag since if game was
9528      * pasted from clipboard the Event tag may not exist
9529      */
9530     if (numPGNTags > 0){
9531         char *tags;
9532         if (gameInfo.variant == VariantNormal) {
9533           gameInfo.variant = StringToVariant(gameInfo.event);
9534         }
9535         if (!matchMode) {
9536           if( appData.autoDisplayTags ) {
9537             tags = PGNTags(&gameInfo);
9538             TagsPopUp(tags, CmailMsg());
9539             free(tags);
9540           }
9541         }
9542     } else {
9543         /* Make something up, but don't display it now */
9544         SetGameInfo();
9545         TagsPopDown();
9546     }
9547
9548     if (cm == PositionDiagram) {
9549         int i, j;
9550         char *p;
9551         Board initial_position;
9552
9553         if (appData.debugMode)
9554           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9555
9556         if (!startedFromSetupPosition) {
9557             p = yy_text;
9558             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9559               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9560                 switch (*p) {
9561                   case '[':
9562                   case '-':
9563                   case ' ':
9564                   case '\t':
9565                   case '\n':
9566                   case '\r':
9567                     break;
9568                   default:
9569                     initial_position[i][j++] = CharToPiece(*p);
9570                     break;
9571                 }
9572             while (*p == ' ' || *p == '\t' ||
9573                    *p == '\n' || *p == '\r') p++;
9574         
9575             if (strncmp(p, "black", strlen("black"))==0)
9576               blackPlaysFirst = TRUE;
9577             else
9578               blackPlaysFirst = FALSE;
9579             startedFromSetupPosition = TRUE;
9580         
9581             CopyBoard(boards[0], initial_position);
9582             if (blackPlaysFirst) {
9583                 currentMove = forwardMostMove = backwardMostMove = 1;
9584                 CopyBoard(boards[1], initial_position);
9585                 strcpy(moveList[0], "");
9586                 strcpy(parseList[0], "");
9587                 timeRemaining[0][1] = whiteTimeRemaining;
9588                 timeRemaining[1][1] = blackTimeRemaining;
9589                 if (commentList[0] != NULL) {
9590                     commentList[1] = commentList[0];
9591                     commentList[0] = NULL;
9592                 }
9593             } else {
9594                 currentMove = forwardMostMove = backwardMostMove = 0;
9595             }
9596         }
9597         yyboardindex = forwardMostMove;
9598         cm = (ChessMove) yylex();
9599     }
9600
9601     if (first.pr == NoProc) {
9602         StartChessProgram(&first);
9603     }
9604     InitChessProgram(&first, FALSE);
9605     SendToProgram("force\n", &first);
9606     if (startedFromSetupPosition) {
9607         SendBoard(&first, forwardMostMove);
9608     if (appData.debugMode) {
9609         fprintf(debugFP, "Load Game\n");
9610     }
9611         DisplayBothClocks();
9612     }      
9613
9614     /* [HGM] server: flag to write setup moves in broadcast file as one */
9615     loadFlag = appData.suppressLoadMoves;
9616
9617     while (cm == Comment) {
9618         char *p;
9619         if (appData.debugMode) 
9620           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9621         p = yy_text;
9622         AppendComment(currentMove, p, FALSE);
9623         yyboardindex = forwardMostMove;
9624         cm = (ChessMove) yylex();
9625     }
9626
9627     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9628         cm == WhiteWins || cm == BlackWins ||
9629         cm == GameIsDrawn || cm == GameUnfinished) {
9630         DisplayMessage("", _("No moves in game"));
9631         if (cmailMsgLoaded) {
9632             if (appData.debugMode)
9633               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9634             ClearHighlights();
9635             flipView = FALSE;
9636         }
9637         DrawPosition(FALSE, boards[currentMove]);
9638         DisplayBothClocks();
9639         gameMode = EditGame;
9640         ModeHighlight();
9641         gameFileFP = NULL;
9642         cmailOldMove = 0;
9643         return TRUE;
9644     }
9645
9646     // [HGM] PV info: routine tests if comment empty
9647     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9648         DisplayComment(currentMove - 1, commentList[currentMove]);
9649     }
9650     if (!matchMode && appData.timeDelay != 0) 
9651       DrawPosition(FALSE, boards[currentMove]);
9652
9653     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9654       programStats.ok_to_send = 1;
9655     }
9656
9657     /* if the first token after the PGN tags is a move
9658      * and not move number 1, retrieve it from the parser 
9659      */
9660     if (cm != MoveNumberOne)
9661         LoadGameOneMove(cm);
9662
9663     /* load the remaining moves from the file */
9664     while (LoadGameOneMove((ChessMove)0)) {
9665       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9666       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9667     }
9668
9669     /* rewind to the start of the game */
9670     currentMove = backwardMostMove;
9671
9672     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9673
9674     if (oldGameMode == AnalyzeFile ||
9675         oldGameMode == AnalyzeMode) {
9676       AnalyzeFileEvent();
9677     }
9678
9679     if (matchMode || appData.timeDelay == 0) {
9680       ToEndEvent();
9681       gameMode = EditGame;
9682       ModeHighlight();
9683     } else if (appData.timeDelay > 0) {
9684       AutoPlayGameLoop();
9685     }
9686
9687     if (appData.debugMode) 
9688         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9689
9690     loadFlag = 0; /* [HGM] true game starts */
9691     return TRUE;
9692 }
9693
9694 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9695 int
9696 ReloadPosition(offset)
9697      int offset;
9698 {
9699     int positionNumber = lastLoadPositionNumber + offset;
9700     if (lastLoadPositionFP == NULL) {
9701         DisplayError(_("No position has been loaded yet"), 0);
9702         return FALSE;
9703     }
9704     if (positionNumber <= 0) {
9705         DisplayError(_("Can't back up any further"), 0);
9706         return FALSE;
9707     }
9708     return LoadPosition(lastLoadPositionFP, positionNumber,
9709                         lastLoadPositionTitle);
9710 }
9711
9712 /* Load the nth position from the given file */
9713 int
9714 LoadPositionFromFile(filename, n, title)
9715      char *filename;
9716      int n;
9717      char *title;
9718 {
9719     FILE *f;
9720     char buf[MSG_SIZ];
9721
9722     if (strcmp(filename, "-") == 0) {
9723         return LoadPosition(stdin, n, "stdin");
9724     } else {
9725         f = fopen(filename, "rb");
9726         if (f == NULL) {
9727             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9728             DisplayError(buf, errno);
9729             return FALSE;
9730         } else {
9731             return LoadPosition(f, n, title);
9732         }
9733     }
9734 }
9735
9736 /* Load the nth position from the given open file, and close it */
9737 int
9738 LoadPosition(f, positionNumber, title)
9739      FILE *f;
9740      int positionNumber;
9741      char *title;
9742 {
9743     char *p, line[MSG_SIZ];
9744     Board initial_position;
9745     int i, j, fenMode, pn;
9746     
9747     if (gameMode == Training )
9748         SetTrainingModeOff();
9749
9750     if (gameMode != BeginningOfGame) {
9751         Reset(FALSE, TRUE);
9752     }
9753     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9754         fclose(lastLoadPositionFP);
9755     }
9756     if (positionNumber == 0) positionNumber = 1;
9757     lastLoadPositionFP = f;
9758     lastLoadPositionNumber = positionNumber;
9759     strcpy(lastLoadPositionTitle, title);
9760     if (first.pr == NoProc) {
9761       StartChessProgram(&first);
9762       InitChessProgram(&first, FALSE);
9763     }    
9764     pn = positionNumber;
9765     if (positionNumber < 0) {
9766         /* Negative position number means to seek to that byte offset */
9767         if (fseek(f, -positionNumber, 0) == -1) {
9768             DisplayError(_("Can't seek on position file"), 0);
9769             return FALSE;
9770         };
9771         pn = 1;
9772     } else {
9773         if (fseek(f, 0, 0) == -1) {
9774             if (f == lastLoadPositionFP ?
9775                 positionNumber == lastLoadPositionNumber + 1 :
9776                 positionNumber == 1) {
9777                 pn = 1;
9778             } else {
9779                 DisplayError(_("Can't seek on position file"), 0);
9780                 return FALSE;
9781             }
9782         }
9783     }
9784     /* See if this file is FEN or old-style xboard */
9785     if (fgets(line, MSG_SIZ, f) == NULL) {
9786         DisplayError(_("Position not found in file"), 0);
9787         return FALSE;
9788     }
9789     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9790     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9791
9792     if (pn >= 2) {
9793         if (fenMode || line[0] == '#') pn--;
9794         while (pn > 0) {
9795             /* skip positions before number pn */
9796             if (fgets(line, MSG_SIZ, f) == NULL) {
9797                 Reset(TRUE, TRUE);
9798                 DisplayError(_("Position not found in file"), 0);
9799                 return FALSE;
9800             }
9801             if (fenMode || line[0] == '#') pn--;
9802         }
9803     }
9804
9805     if (fenMode) {
9806         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9807             DisplayError(_("Bad FEN position in file"), 0);
9808             return FALSE;
9809         }
9810     } else {
9811         (void) fgets(line, MSG_SIZ, f);
9812         (void) fgets(line, MSG_SIZ, f);
9813     
9814         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9815             (void) fgets(line, MSG_SIZ, f);
9816             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9817                 if (*p == ' ')
9818                   continue;
9819                 initial_position[i][j++] = CharToPiece(*p);
9820             }
9821         }
9822     
9823         blackPlaysFirst = FALSE;
9824         if (!feof(f)) {
9825             (void) fgets(line, MSG_SIZ, f);
9826             if (strncmp(line, "black", strlen("black"))==0)
9827               blackPlaysFirst = TRUE;
9828         }
9829     }
9830     startedFromSetupPosition = TRUE;
9831     
9832     SendToProgram("force\n", &first);
9833     CopyBoard(boards[0], initial_position);
9834     if (blackPlaysFirst) {
9835         currentMove = forwardMostMove = backwardMostMove = 1;
9836         strcpy(moveList[0], "");
9837         strcpy(parseList[0], "");
9838         CopyBoard(boards[1], initial_position);
9839         DisplayMessage("", _("Black to play"));
9840     } else {
9841         currentMove = forwardMostMove = backwardMostMove = 0;
9842         DisplayMessage("", _("White to play"));
9843     }
9844     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9845     SendBoard(&first, forwardMostMove);
9846     if (appData.debugMode) {
9847 int i, j;
9848   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9849   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9850         fprintf(debugFP, "Load Position\n");
9851     }
9852
9853     if (positionNumber > 1) {
9854         sprintf(line, "%s %d", title, positionNumber);
9855         DisplayTitle(line);
9856     } else {
9857         DisplayTitle(title);
9858     }
9859     gameMode = EditGame;
9860     ModeHighlight();
9861     ResetClocks();
9862     timeRemaining[0][1] = whiteTimeRemaining;
9863     timeRemaining[1][1] = blackTimeRemaining;
9864     DrawPosition(FALSE, boards[currentMove]);
9865    
9866     return TRUE;
9867 }
9868
9869
9870 void
9871 CopyPlayerNameIntoFileName(dest, src)
9872      char **dest, *src;
9873 {
9874     while (*src != NULLCHAR && *src != ',') {
9875         if (*src == ' ') {
9876             *(*dest)++ = '_';
9877             src++;
9878         } else {
9879             *(*dest)++ = *src++;
9880         }
9881     }
9882 }
9883
9884 char *DefaultFileName(ext)
9885      char *ext;
9886 {
9887     static char def[MSG_SIZ];
9888     char *p;
9889
9890     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9891         p = def;
9892         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9893         *p++ = '-';
9894         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9895         *p++ = '.';
9896         strcpy(p, ext);
9897     } else {
9898         def[0] = NULLCHAR;
9899     }
9900     return def;
9901 }
9902
9903 /* Save the current game to the given file */
9904 int
9905 SaveGameToFile(filename, append)
9906      char *filename;
9907      int append;
9908 {
9909     FILE *f;
9910     char buf[MSG_SIZ];
9911
9912     if (strcmp(filename, "-") == 0) {
9913         return SaveGame(stdout, 0, NULL);
9914     } else {
9915         f = fopen(filename, append ? "a" : "w");
9916         if (f == NULL) {
9917             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9918             DisplayError(buf, errno);
9919             return FALSE;
9920         } else {
9921             return SaveGame(f, 0, NULL);
9922         }
9923     }
9924 }
9925
9926 char *
9927 SavePart(str)
9928      char *str;
9929 {
9930     static char buf[MSG_SIZ];
9931     char *p;
9932     
9933     p = strchr(str, ' ');
9934     if (p == NULL) return str;
9935     strncpy(buf, str, p - str);
9936     buf[p - str] = NULLCHAR;
9937     return buf;
9938 }
9939
9940 #define PGN_MAX_LINE 75
9941
9942 #define PGN_SIDE_WHITE  0
9943 #define PGN_SIDE_BLACK  1
9944
9945 /* [AS] */
9946 static int FindFirstMoveOutOfBook( int side )
9947 {
9948     int result = -1;
9949
9950     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9951         int index = backwardMostMove;
9952         int has_book_hit = 0;
9953
9954         if( (index % 2) != side ) {
9955             index++;
9956         }
9957
9958         while( index < forwardMostMove ) {
9959             /* Check to see if engine is in book */
9960             int depth = pvInfoList[index].depth;
9961             int score = pvInfoList[index].score;
9962             int in_book = 0;
9963
9964             if( depth <= 2 ) {
9965                 in_book = 1;
9966             }
9967             else if( score == 0 && depth == 63 ) {
9968                 in_book = 1; /* Zappa */
9969             }
9970             else if( score == 2 && depth == 99 ) {
9971                 in_book = 1; /* Abrok */
9972             }
9973
9974             has_book_hit += in_book;
9975
9976             if( ! in_book ) {
9977                 result = index;
9978
9979                 break;
9980             }
9981
9982             index += 2;
9983         }
9984     }
9985
9986     return result;
9987 }
9988
9989 /* [AS] */
9990 void GetOutOfBookInfo( char * buf )
9991 {
9992     int oob[2];
9993     int i;
9994     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9995
9996     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9997     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9998
9999     *buf = '\0';
10000
10001     if( oob[0] >= 0 || oob[1] >= 0 ) {
10002         for( i=0; i<2; i++ ) {
10003             int idx = oob[i];
10004
10005             if( idx >= 0 ) {
10006                 if( i > 0 && oob[0] >= 0 ) {
10007                     strcat( buf, "   " );
10008                 }
10009
10010                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10011                 sprintf( buf+strlen(buf), "%s%.2f", 
10012                     pvInfoList[idx].score >= 0 ? "+" : "",
10013                     pvInfoList[idx].score / 100.0 );
10014             }
10015         }
10016     }
10017 }
10018
10019 /* Save game in PGN style and close the file */
10020 int
10021 SaveGamePGN(f)
10022      FILE *f;
10023 {
10024     int i, offset, linelen, newblock;
10025     time_t tm;
10026 //    char *movetext;
10027     char numtext[32];
10028     int movelen, numlen, blank;
10029     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10030
10031     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10032     
10033     tm = time((time_t *) NULL);
10034     
10035     PrintPGNTags(f, &gameInfo);
10036     
10037     if (backwardMostMove > 0 || startedFromSetupPosition) {
10038         char *fen = PositionToFEN(backwardMostMove, NULL);
10039         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10040         fprintf(f, "\n{--------------\n");
10041         PrintPosition(f, backwardMostMove);
10042         fprintf(f, "--------------}\n");
10043         free(fen);
10044     }
10045     else {
10046         /* [AS] Out of book annotation */
10047         if( appData.saveOutOfBookInfo ) {
10048             char buf[64];
10049
10050             GetOutOfBookInfo( buf );
10051
10052             if( buf[0] != '\0' ) {
10053                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10054             }
10055         }
10056
10057         fprintf(f, "\n");
10058     }
10059
10060     i = backwardMostMove;
10061     linelen = 0;
10062     newblock = TRUE;
10063
10064     while (i < forwardMostMove) {
10065         /* Print comments preceding this move */
10066         if (commentList[i] != NULL) {
10067             if (linelen > 0) fprintf(f, "\n");
10068             fprintf(f, "%s", commentList[i]);
10069             linelen = 0;
10070             newblock = TRUE;
10071         }
10072
10073         /* Format move number */
10074         if ((i % 2) == 0) {
10075             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10076         } else {
10077             if (newblock) {
10078                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10079             } else {
10080                 numtext[0] = NULLCHAR;
10081             }
10082         }
10083         numlen = strlen(numtext);
10084         newblock = FALSE;
10085
10086         /* Print move number */
10087         blank = linelen > 0 && numlen > 0;
10088         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10089             fprintf(f, "\n");
10090             linelen = 0;
10091             blank = 0;
10092         }
10093         if (blank) {
10094             fprintf(f, " ");
10095             linelen++;
10096         }
10097         fprintf(f, "%s", numtext);
10098         linelen += numlen;
10099
10100         /* Get move */
10101         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10102         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10103
10104         /* Print move */
10105         blank = linelen > 0 && movelen > 0;
10106         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10107             fprintf(f, "\n");
10108             linelen = 0;
10109             blank = 0;
10110         }
10111         if (blank) {
10112             fprintf(f, " ");
10113             linelen++;
10114         }
10115         fprintf(f, "%s", move_buffer);
10116         linelen += movelen;
10117
10118         /* [AS] Add PV info if present */
10119         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10120             /* [HGM] add time */
10121             char buf[MSG_SIZ]; int seconds;
10122
10123             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10124
10125             if( seconds <= 0) buf[0] = 0; else
10126             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10127                 seconds = (seconds + 4)/10; // round to full seconds
10128                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10129                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10130             }
10131
10132             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10133                 pvInfoList[i].score >= 0 ? "+" : "",
10134                 pvInfoList[i].score / 100.0,
10135                 pvInfoList[i].depth,
10136                 buf );
10137
10138             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10139
10140             /* Print score/depth */
10141             blank = linelen > 0 && movelen > 0;
10142             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10143                 fprintf(f, "\n");
10144                 linelen = 0;
10145                 blank = 0;
10146             }
10147             if (blank) {
10148                 fprintf(f, " ");
10149                 linelen++;
10150             }
10151             fprintf(f, "%s", move_buffer);
10152             linelen += movelen;
10153         }
10154
10155         i++;
10156     }
10157     
10158     /* Start a new line */
10159     if (linelen > 0) fprintf(f, "\n");
10160
10161     /* Print comments after last move */
10162     if (commentList[i] != NULL) {
10163         fprintf(f, "%s\n", commentList[i]);
10164     }
10165
10166     /* Print result */
10167     if (gameInfo.resultDetails != NULL &&
10168         gameInfo.resultDetails[0] != NULLCHAR) {
10169         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10170                 PGNResult(gameInfo.result));
10171     } else {
10172         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10173     }
10174
10175     fclose(f);
10176     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10177     return TRUE;
10178 }
10179
10180 /* Save game in old style and close the file */
10181 int
10182 SaveGameOldStyle(f)
10183      FILE *f;
10184 {
10185     int i, offset;
10186     time_t tm;
10187     
10188     tm = time((time_t *) NULL);
10189     
10190     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10191     PrintOpponents(f);
10192     
10193     if (backwardMostMove > 0 || startedFromSetupPosition) {
10194         fprintf(f, "\n[--------------\n");
10195         PrintPosition(f, backwardMostMove);
10196         fprintf(f, "--------------]\n");
10197     } else {
10198         fprintf(f, "\n");
10199     }
10200
10201     i = backwardMostMove;
10202     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10203
10204     while (i < forwardMostMove) {
10205         if (commentList[i] != NULL) {
10206             fprintf(f, "[%s]\n", commentList[i]);
10207         }
10208
10209         if ((i % 2) == 1) {
10210             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10211             i++;
10212         } else {
10213             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10214             i++;
10215             if (commentList[i] != NULL) {
10216                 fprintf(f, "\n");
10217                 continue;
10218             }
10219             if (i >= forwardMostMove) {
10220                 fprintf(f, "\n");
10221                 break;
10222             }
10223             fprintf(f, "%s\n", parseList[i]);
10224             i++;
10225         }
10226     }
10227     
10228     if (commentList[i] != NULL) {
10229         fprintf(f, "[%s]\n", commentList[i]);
10230     }
10231
10232     /* This isn't really the old style, but it's close enough */
10233     if (gameInfo.resultDetails != NULL &&
10234         gameInfo.resultDetails[0] != NULLCHAR) {
10235         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10236                 gameInfo.resultDetails);
10237     } else {
10238         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10239     }
10240
10241     fclose(f);
10242     return TRUE;
10243 }
10244
10245 /* Save the current game to open file f and close the file */
10246 int
10247 SaveGame(f, dummy, dummy2)
10248      FILE *f;
10249      int dummy;
10250      char *dummy2;
10251 {
10252     if (gameMode == EditPosition) EditPositionDone(TRUE);
10253     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10254     if (appData.oldSaveStyle)
10255       return SaveGameOldStyle(f);
10256     else
10257       return SaveGamePGN(f);
10258 }
10259
10260 /* Save the current position to the given file */
10261 int
10262 SavePositionToFile(filename)
10263      char *filename;
10264 {
10265     FILE *f;
10266     char buf[MSG_SIZ];
10267
10268     if (strcmp(filename, "-") == 0) {
10269         return SavePosition(stdout, 0, NULL);
10270     } else {
10271         f = fopen(filename, "a");
10272         if (f == NULL) {
10273             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10274             DisplayError(buf, errno);
10275             return FALSE;
10276         } else {
10277             SavePosition(f, 0, NULL);
10278             return TRUE;
10279         }
10280     }
10281 }
10282
10283 /* Save the current position to the given open file and close the file */
10284 int
10285 SavePosition(f, dummy, dummy2)
10286      FILE *f;
10287      int dummy;
10288      char *dummy2;
10289 {
10290     time_t tm;
10291     char *fen;
10292     
10293     if (gameMode == EditPosition) EditPositionDone(TRUE);
10294     if (appData.oldSaveStyle) {
10295         tm = time((time_t *) NULL);
10296     
10297         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10298         PrintOpponents(f);
10299         fprintf(f, "[--------------\n");
10300         PrintPosition(f, currentMove);
10301         fprintf(f, "--------------]\n");
10302     } else {
10303         fen = PositionToFEN(currentMove, NULL);
10304         fprintf(f, "%s\n", fen);
10305         free(fen);
10306     }
10307     fclose(f);
10308     return TRUE;
10309 }
10310
10311 void
10312 ReloadCmailMsgEvent(unregister)
10313      int unregister;
10314 {
10315 #if !WIN32
10316     static char *inFilename = NULL;
10317     static char *outFilename;
10318     int i;
10319     struct stat inbuf, outbuf;
10320     int status;
10321     
10322     /* Any registered moves are unregistered if unregister is set, */
10323     /* i.e. invoked by the signal handler */
10324     if (unregister) {
10325         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10326             cmailMoveRegistered[i] = FALSE;
10327             if (cmailCommentList[i] != NULL) {
10328                 free(cmailCommentList[i]);
10329                 cmailCommentList[i] = NULL;
10330             }
10331         }
10332         nCmailMovesRegistered = 0;
10333     }
10334
10335     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10336         cmailResult[i] = CMAIL_NOT_RESULT;
10337     }
10338     nCmailResults = 0;
10339
10340     if (inFilename == NULL) {
10341         /* Because the filenames are static they only get malloced once  */
10342         /* and they never get freed                                      */
10343         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10344         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10345
10346         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10347         sprintf(outFilename, "%s.out", appData.cmailGameName);
10348     }
10349     
10350     status = stat(outFilename, &outbuf);
10351     if (status < 0) {
10352         cmailMailedMove = FALSE;
10353     } else {
10354         status = stat(inFilename, &inbuf);
10355         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10356     }
10357     
10358     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10359        counts the games, notes how each one terminated, etc.
10360        
10361        It would be nice to remove this kludge and instead gather all
10362        the information while building the game list.  (And to keep it
10363        in the game list nodes instead of having a bunch of fixed-size
10364        parallel arrays.)  Note this will require getting each game's
10365        termination from the PGN tags, as the game list builder does
10366        not process the game moves.  --mann
10367        */
10368     cmailMsgLoaded = TRUE;
10369     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10370     
10371     /* Load first game in the file or popup game menu */
10372     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10373
10374 #endif /* !WIN32 */
10375     return;
10376 }
10377
10378 int
10379 RegisterMove()
10380 {
10381     FILE *f;
10382     char string[MSG_SIZ];
10383
10384     if (   cmailMailedMove
10385         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10386         return TRUE;            /* Allow free viewing  */
10387     }
10388
10389     /* Unregister move to ensure that we don't leave RegisterMove        */
10390     /* with the move registered when the conditions for registering no   */
10391     /* longer hold                                                       */
10392     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10393         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10394         nCmailMovesRegistered --;
10395
10396         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10397           {
10398               free(cmailCommentList[lastLoadGameNumber - 1]);
10399               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10400           }
10401     }
10402
10403     if (cmailOldMove == -1) {
10404         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10405         return FALSE;
10406     }
10407
10408     if (currentMove > cmailOldMove + 1) {
10409         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10410         return FALSE;
10411     }
10412
10413     if (currentMove < cmailOldMove) {
10414         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10415         return FALSE;
10416     }
10417
10418     if (forwardMostMove > currentMove) {
10419         /* Silently truncate extra moves */
10420         TruncateGame();
10421     }
10422
10423     if (   (currentMove == cmailOldMove + 1)
10424         || (   (currentMove == cmailOldMove)
10425             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10426                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10427         if (gameInfo.result != GameUnfinished) {
10428             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10429         }
10430
10431         if (commentList[currentMove] != NULL) {
10432             cmailCommentList[lastLoadGameNumber - 1]
10433               = StrSave(commentList[currentMove]);
10434         }
10435         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10436
10437         if (appData.debugMode)
10438           fprintf(debugFP, "Saving %s for game %d\n",
10439                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10440
10441         sprintf(string,
10442                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10443         
10444         f = fopen(string, "w");
10445         if (appData.oldSaveStyle) {
10446             SaveGameOldStyle(f); /* also closes the file */
10447             
10448             sprintf(string, "%s.pos.out", appData.cmailGameName);
10449             f = fopen(string, "w");
10450             SavePosition(f, 0, NULL); /* also closes the file */
10451         } else {
10452             fprintf(f, "{--------------\n");
10453             PrintPosition(f, currentMove);
10454             fprintf(f, "--------------}\n\n");
10455             
10456             SaveGame(f, 0, NULL); /* also closes the file*/
10457         }
10458         
10459         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10460         nCmailMovesRegistered ++;
10461     } else if (nCmailGames == 1) {
10462         DisplayError(_("You have not made a move yet"), 0);
10463         return FALSE;
10464     }
10465
10466     return TRUE;
10467 }
10468
10469 void
10470 MailMoveEvent()
10471 {
10472 #if !WIN32
10473     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10474     FILE *commandOutput;
10475     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10476     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10477     int nBuffers;
10478     int i;
10479     int archived;
10480     char *arcDir;
10481
10482     if (! cmailMsgLoaded) {
10483         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10484         return;
10485     }
10486
10487     if (nCmailGames == nCmailResults) {
10488         DisplayError(_("No unfinished games"), 0);
10489         return;
10490     }
10491
10492 #if CMAIL_PROHIBIT_REMAIL
10493     if (cmailMailedMove) {
10494         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);
10495         DisplayError(msg, 0);
10496         return;
10497     }
10498 #endif
10499
10500     if (! (cmailMailedMove || RegisterMove())) return;
10501     
10502     if (   cmailMailedMove
10503         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10504         sprintf(string, partCommandString,
10505                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10506         commandOutput = popen(string, "r");
10507
10508         if (commandOutput == NULL) {
10509             DisplayError(_("Failed to invoke cmail"), 0);
10510         } else {
10511             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10512                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10513             }
10514             if (nBuffers > 1) {
10515                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10516                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10517                 nBytes = MSG_SIZ - 1;
10518             } else {
10519                 (void) memcpy(msg, buffer, nBytes);
10520             }
10521             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10522
10523             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10524                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10525
10526                 archived = TRUE;
10527                 for (i = 0; i < nCmailGames; i ++) {
10528                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10529                         archived = FALSE;
10530                     }
10531                 }
10532                 if (   archived
10533                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10534                         != NULL)) {
10535                     sprintf(buffer, "%s/%s.%s.archive",
10536                             arcDir,
10537                             appData.cmailGameName,
10538                             gameInfo.date);
10539                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10540                     cmailMsgLoaded = FALSE;
10541                 }
10542             }
10543
10544             DisplayInformation(msg);
10545             pclose(commandOutput);
10546         }
10547     } else {
10548         if ((*cmailMsg) != '\0') {
10549             DisplayInformation(cmailMsg);
10550         }
10551     }
10552
10553     return;
10554 #endif /* !WIN32 */
10555 }
10556
10557 char *
10558 CmailMsg()
10559 {
10560 #if WIN32
10561     return NULL;
10562 #else
10563     int  prependComma = 0;
10564     char number[5];
10565     char string[MSG_SIZ];       /* Space for game-list */
10566     int  i;
10567     
10568     if (!cmailMsgLoaded) return "";
10569
10570     if (cmailMailedMove) {
10571         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10572     } else {
10573         /* Create a list of games left */
10574         sprintf(string, "[");
10575         for (i = 0; i < nCmailGames; i ++) {
10576             if (! (   cmailMoveRegistered[i]
10577                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10578                 if (prependComma) {
10579                     sprintf(number, ",%d", i + 1);
10580                 } else {
10581                     sprintf(number, "%d", i + 1);
10582                     prependComma = 1;
10583                 }
10584                 
10585                 strcat(string, number);
10586             }
10587         }
10588         strcat(string, "]");
10589
10590         if (nCmailMovesRegistered + nCmailResults == 0) {
10591             switch (nCmailGames) {
10592               case 1:
10593                 sprintf(cmailMsg,
10594                         _("Still need to make move for game\n"));
10595                 break;
10596                 
10597               case 2:
10598                 sprintf(cmailMsg,
10599                         _("Still need to make moves for both games\n"));
10600                 break;
10601                 
10602               default:
10603                 sprintf(cmailMsg,
10604                         _("Still need to make moves for all %d games\n"),
10605                         nCmailGames);
10606                 break;
10607             }
10608         } else {
10609             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10610               case 1:
10611                 sprintf(cmailMsg,
10612                         _("Still need to make a move for game %s\n"),
10613                         string);
10614                 break;
10615                 
10616               case 0:
10617                 if (nCmailResults == nCmailGames) {
10618                     sprintf(cmailMsg, _("No unfinished games\n"));
10619                 } else {
10620                     sprintf(cmailMsg, _("Ready to send mail\n"));
10621                 }
10622                 break;
10623                 
10624               default:
10625                 sprintf(cmailMsg,
10626                         _("Still need to make moves for games %s\n"),
10627                         string);
10628             }
10629         }
10630     }
10631     return cmailMsg;
10632 #endif /* WIN32 */
10633 }
10634
10635 void
10636 ResetGameEvent()
10637 {
10638     if (gameMode == Training)
10639       SetTrainingModeOff();
10640
10641     Reset(TRUE, TRUE);
10642     cmailMsgLoaded = FALSE;
10643     if (appData.icsActive) {
10644       SendToICS(ics_prefix);
10645       SendToICS("refresh\n");
10646     }
10647 }
10648
10649 void
10650 ExitEvent(status)
10651      int status;
10652 {
10653     exiting++;
10654     if (exiting > 2) {
10655       /* Give up on clean exit */
10656       exit(status);
10657     }
10658     if (exiting > 1) {
10659       /* Keep trying for clean exit */
10660       return;
10661     }
10662
10663     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10664
10665     if (telnetISR != NULL) {
10666       RemoveInputSource(telnetISR);
10667     }
10668     if (icsPR != NoProc) {
10669       DestroyChildProcess(icsPR, TRUE);
10670     }
10671
10672     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10673     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10674
10675     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10676     /* make sure this other one finishes before killing it!                  */
10677     if(endingGame) { int count = 0;
10678         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10679         while(endingGame && count++ < 10) DoSleep(1);
10680         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10681     }
10682
10683     /* Kill off chess programs */
10684     if (first.pr != NoProc) {
10685         ExitAnalyzeMode();
10686         
10687         DoSleep( appData.delayBeforeQuit );
10688         SendToProgram("quit\n", &first);
10689         DoSleep( appData.delayAfterQuit );
10690         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10691     }
10692     if (second.pr != NoProc) {
10693         DoSleep( appData.delayBeforeQuit );
10694         SendToProgram("quit\n", &second);
10695         DoSleep( appData.delayAfterQuit );
10696         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10697     }
10698     if (first.isr != NULL) {
10699         RemoveInputSource(first.isr);
10700     }
10701     if (second.isr != NULL) {
10702         RemoveInputSource(second.isr);
10703     }
10704
10705     ShutDownFrontEnd();
10706     exit(status);
10707 }
10708
10709 void
10710 PauseEvent()
10711 {
10712     if (appData.debugMode)
10713         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10714     if (pausing) {
10715         pausing = FALSE;
10716         ModeHighlight();
10717         if (gameMode == MachinePlaysWhite ||
10718             gameMode == MachinePlaysBlack) {
10719             StartClocks();
10720         } else {
10721             DisplayBothClocks();
10722         }
10723         if (gameMode == PlayFromGameFile) {
10724             if (appData.timeDelay >= 0) 
10725                 AutoPlayGameLoop();
10726         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10727             Reset(FALSE, TRUE);
10728             SendToICS(ics_prefix);
10729             SendToICS("refresh\n");
10730         } else if (currentMove < forwardMostMove) {
10731             ForwardInner(forwardMostMove);
10732         }
10733         pauseExamInvalid = FALSE;
10734     } else {
10735         switch (gameMode) {
10736           default:
10737             return;
10738           case IcsExamining:
10739             pauseExamForwardMostMove = forwardMostMove;
10740             pauseExamInvalid = FALSE;
10741             /* fall through */
10742           case IcsObserving:
10743           case IcsPlayingWhite:
10744           case IcsPlayingBlack:
10745             pausing = TRUE;
10746             ModeHighlight();
10747             return;
10748           case PlayFromGameFile:
10749             (void) StopLoadGameTimer();
10750             pausing = TRUE;
10751             ModeHighlight();
10752             break;
10753           case BeginningOfGame:
10754             if (appData.icsActive) return;
10755             /* else fall through */
10756           case MachinePlaysWhite:
10757           case MachinePlaysBlack:
10758           case TwoMachinesPlay:
10759             if (forwardMostMove == 0)
10760               return;           /* don't pause if no one has moved */
10761             if ((gameMode == MachinePlaysWhite &&
10762                  !WhiteOnMove(forwardMostMove)) ||
10763                 (gameMode == MachinePlaysBlack &&
10764                  WhiteOnMove(forwardMostMove))) {
10765                 StopClocks();
10766             }
10767             pausing = TRUE;
10768             ModeHighlight();
10769             break;
10770         }
10771     }
10772 }
10773
10774 void
10775 EditCommentEvent()
10776 {
10777     char title[MSG_SIZ];
10778
10779     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10780         strcpy(title, _("Edit comment"));
10781     } else {
10782         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10783                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10784                 parseList[currentMove - 1]);
10785     }
10786
10787     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10788 }
10789
10790
10791 void
10792 EditTagsEvent()
10793 {
10794     char *tags = PGNTags(&gameInfo);
10795     EditTagsPopUp(tags);
10796     free(tags);
10797 }
10798
10799 void
10800 AnalyzeModeEvent()
10801 {
10802     if (appData.noChessProgram || gameMode == AnalyzeMode)
10803       return;
10804
10805     if (gameMode != AnalyzeFile) {
10806         if (!appData.icsEngineAnalyze) {
10807                EditGameEvent();
10808                if (gameMode != EditGame) return;
10809         }
10810         ResurrectChessProgram();
10811         SendToProgram("analyze\n", &first);
10812         first.analyzing = TRUE;
10813         /*first.maybeThinking = TRUE;*/
10814         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10815         EngineOutputPopUp();
10816     }
10817     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10818     pausing = FALSE;
10819     ModeHighlight();
10820     SetGameInfo();
10821
10822     StartAnalysisClock();
10823     GetTimeMark(&lastNodeCountTime);
10824     lastNodeCount = 0;
10825 }
10826
10827 void
10828 AnalyzeFileEvent()
10829 {
10830     if (appData.noChessProgram || gameMode == AnalyzeFile)
10831       return;
10832
10833     if (gameMode != AnalyzeMode) {
10834         EditGameEvent();
10835         if (gameMode != EditGame) return;
10836         ResurrectChessProgram();
10837         SendToProgram("analyze\n", &first);
10838         first.analyzing = TRUE;
10839         /*first.maybeThinking = TRUE;*/
10840         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10841         EngineOutputPopUp();
10842     }
10843     gameMode = AnalyzeFile;
10844     pausing = FALSE;
10845     ModeHighlight();
10846     SetGameInfo();
10847
10848     StartAnalysisClock();
10849     GetTimeMark(&lastNodeCountTime);
10850     lastNodeCount = 0;
10851 }
10852
10853 void
10854 MachineWhiteEvent()
10855 {
10856     char buf[MSG_SIZ];
10857     char *bookHit = NULL;
10858
10859     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10860       return;
10861
10862
10863     if (gameMode == PlayFromGameFile || 
10864         gameMode == TwoMachinesPlay  || 
10865         gameMode == Training         || 
10866         gameMode == AnalyzeMode      || 
10867         gameMode == EndOfGame)
10868         EditGameEvent();
10869
10870     if (gameMode == EditPosition) 
10871         EditPositionDone(TRUE);
10872
10873     if (!WhiteOnMove(currentMove)) {
10874         DisplayError(_("It is not White's turn"), 0);
10875         return;
10876     }
10877   
10878     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10879       ExitAnalyzeMode();
10880
10881     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10882         gameMode == AnalyzeFile)
10883         TruncateGame();
10884
10885     ResurrectChessProgram();    /* in case it isn't running */
10886     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10887         gameMode = MachinePlaysWhite;
10888         ResetClocks();
10889     } else
10890     gameMode = MachinePlaysWhite;
10891     pausing = FALSE;
10892     ModeHighlight();
10893     SetGameInfo();
10894     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10895     DisplayTitle(buf);
10896     if (first.sendName) {
10897       sprintf(buf, "name %s\n", gameInfo.black);
10898       SendToProgram(buf, &first);
10899     }
10900     if (first.sendTime) {
10901       if (first.useColors) {
10902         SendToProgram("black\n", &first); /*gnu kludge*/
10903       }
10904       SendTimeRemaining(&first, TRUE);
10905     }
10906     if (first.useColors) {
10907       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10908     }
10909     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10910     SetMachineThinkingEnables();
10911     first.maybeThinking = TRUE;
10912     StartClocks();
10913     firstMove = FALSE;
10914
10915     if (appData.autoFlipView && !flipView) {
10916       flipView = !flipView;
10917       DrawPosition(FALSE, NULL);
10918       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10919     }
10920
10921     if(bookHit) { // [HGM] book: simulate book reply
10922         static char bookMove[MSG_SIZ]; // a bit generous?
10923
10924         programStats.nodes = programStats.depth = programStats.time = 
10925         programStats.score = programStats.got_only_move = 0;
10926         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10927
10928         strcpy(bookMove, "move ");
10929         strcat(bookMove, bookHit);
10930         HandleMachineMove(bookMove, &first);
10931     }
10932 }
10933
10934 void
10935 MachineBlackEvent()
10936 {
10937     char buf[MSG_SIZ];
10938    char *bookHit = NULL;
10939
10940     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10941         return;
10942
10943
10944     if (gameMode == PlayFromGameFile || 
10945         gameMode == TwoMachinesPlay  || 
10946         gameMode == Training         || 
10947         gameMode == AnalyzeMode      || 
10948         gameMode == EndOfGame)
10949         EditGameEvent();
10950
10951     if (gameMode == EditPosition) 
10952         EditPositionDone(TRUE);
10953
10954     if (WhiteOnMove(currentMove)) {
10955         DisplayError(_("It is not Black's turn"), 0);
10956         return;
10957     }
10958     
10959     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10960       ExitAnalyzeMode();
10961
10962     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10963         gameMode == AnalyzeFile)
10964         TruncateGame();
10965
10966     ResurrectChessProgram();    /* in case it isn't running */
10967     gameMode = MachinePlaysBlack;
10968     pausing = FALSE;
10969     ModeHighlight();
10970     SetGameInfo();
10971     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10972     DisplayTitle(buf);
10973     if (first.sendName) {
10974       sprintf(buf, "name %s\n", gameInfo.white);
10975       SendToProgram(buf, &first);
10976     }
10977     if (first.sendTime) {
10978       if (first.useColors) {
10979         SendToProgram("white\n", &first); /*gnu kludge*/
10980       }
10981       SendTimeRemaining(&first, FALSE);
10982     }
10983     if (first.useColors) {
10984       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10985     }
10986     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10987     SetMachineThinkingEnables();
10988     first.maybeThinking = TRUE;
10989     StartClocks();
10990
10991     if (appData.autoFlipView && flipView) {
10992       flipView = !flipView;
10993       DrawPosition(FALSE, NULL);
10994       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10995     }
10996     if(bookHit) { // [HGM] book: simulate book reply
10997         static char bookMove[MSG_SIZ]; // a bit generous?
10998
10999         programStats.nodes = programStats.depth = programStats.time = 
11000         programStats.score = programStats.got_only_move = 0;
11001         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11002
11003         strcpy(bookMove, "move ");
11004         strcat(bookMove, bookHit);
11005         HandleMachineMove(bookMove, &first);
11006     }
11007 }
11008
11009
11010 void
11011 DisplayTwoMachinesTitle()
11012 {
11013     char buf[MSG_SIZ];
11014     if (appData.matchGames > 0) {
11015         if (first.twoMachinesColor[0] == 'w') {
11016             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11017                     gameInfo.white, gameInfo.black,
11018                     first.matchWins, second.matchWins,
11019                     matchGame - 1 - (first.matchWins + second.matchWins));
11020         } else {
11021             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11022                     gameInfo.white, gameInfo.black,
11023                     second.matchWins, first.matchWins,
11024                     matchGame - 1 - (first.matchWins + second.matchWins));
11025         }
11026     } else {
11027         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11028     }
11029     DisplayTitle(buf);
11030 }
11031
11032 void
11033 TwoMachinesEvent P((void))
11034 {
11035     int i;
11036     char buf[MSG_SIZ];
11037     ChessProgramState *onmove;
11038     char *bookHit = NULL;
11039     
11040     if (appData.noChessProgram) return;
11041
11042     switch (gameMode) {
11043       case TwoMachinesPlay:
11044         return;
11045       case MachinePlaysWhite:
11046       case MachinePlaysBlack:
11047         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11048             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11049             return;
11050         }
11051         /* fall through */
11052       case BeginningOfGame:
11053       case PlayFromGameFile:
11054       case EndOfGame:
11055         EditGameEvent();
11056         if (gameMode != EditGame) return;
11057         break;
11058       case EditPosition:
11059         EditPositionDone(TRUE);
11060         break;
11061       case AnalyzeMode:
11062       case AnalyzeFile:
11063         ExitAnalyzeMode();
11064         break;
11065       case EditGame:
11066       default:
11067         break;
11068     }
11069
11070 //    forwardMostMove = currentMove;
11071     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11072     ResurrectChessProgram();    /* in case first program isn't running */
11073
11074     if (second.pr == NULL) {
11075         StartChessProgram(&second);
11076         if (second.protocolVersion == 1) {
11077           TwoMachinesEventIfReady();
11078         } else {
11079           /* kludge: allow timeout for initial "feature" command */
11080           FreezeUI();
11081           DisplayMessage("", _("Starting second chess program"));
11082           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11083         }
11084         return;
11085     }
11086     DisplayMessage("", "");
11087     InitChessProgram(&second, FALSE);
11088     SendToProgram("force\n", &second);
11089     if (startedFromSetupPosition) {
11090         SendBoard(&second, backwardMostMove);
11091     if (appData.debugMode) {
11092         fprintf(debugFP, "Two Machines\n");
11093     }
11094     }
11095     for (i = backwardMostMove; i < forwardMostMove; i++) {
11096         SendMoveToProgram(i, &second);
11097     }
11098
11099     gameMode = TwoMachinesPlay;
11100     pausing = FALSE;
11101     ModeHighlight();
11102     SetGameInfo();
11103     DisplayTwoMachinesTitle();
11104     firstMove = TRUE;
11105     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11106         onmove = &first;
11107     } else {
11108         onmove = &second;
11109     }
11110
11111     SendToProgram(first.computerString, &first);
11112     if (first.sendName) {
11113       sprintf(buf, "name %s\n", second.tidy);
11114       SendToProgram(buf, &first);
11115     }
11116     SendToProgram(second.computerString, &second);
11117     if (second.sendName) {
11118       sprintf(buf, "name %s\n", first.tidy);
11119       SendToProgram(buf, &second);
11120     }
11121
11122     ResetClocks();
11123     if (!first.sendTime || !second.sendTime) {
11124         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11125         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11126     }
11127     if (onmove->sendTime) {
11128       if (onmove->useColors) {
11129         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11130       }
11131       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11132     }
11133     if (onmove->useColors) {
11134       SendToProgram(onmove->twoMachinesColor, onmove);
11135     }
11136     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11137 //    SendToProgram("go\n", onmove);
11138     onmove->maybeThinking = TRUE;
11139     SetMachineThinkingEnables();
11140
11141     StartClocks();
11142
11143     if(bookHit) { // [HGM] book: simulate book reply
11144         static char bookMove[MSG_SIZ]; // a bit generous?
11145
11146         programStats.nodes = programStats.depth = programStats.time = 
11147         programStats.score = programStats.got_only_move = 0;
11148         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11149
11150         strcpy(bookMove, "move ");
11151         strcat(bookMove, bookHit);
11152         savedMessage = bookMove; // args for deferred call
11153         savedState = onmove;
11154         ScheduleDelayedEvent(DeferredBookMove, 1);
11155     }
11156 }
11157
11158 void
11159 TrainingEvent()
11160 {
11161     if (gameMode == Training) {
11162       SetTrainingModeOff();
11163       gameMode = PlayFromGameFile;
11164       DisplayMessage("", _("Training mode off"));
11165     } else {
11166       gameMode = Training;
11167       animateTraining = appData.animate;
11168
11169       /* make sure we are not already at the end of the game */
11170       if (currentMove < forwardMostMove) {
11171         SetTrainingModeOn();
11172         DisplayMessage("", _("Training mode on"));
11173       } else {
11174         gameMode = PlayFromGameFile;
11175         DisplayError(_("Already at end of game"), 0);
11176       }
11177     }
11178     ModeHighlight();
11179 }
11180
11181 void
11182 IcsClientEvent()
11183 {
11184     if (!appData.icsActive) return;
11185     switch (gameMode) {
11186       case IcsPlayingWhite:
11187       case IcsPlayingBlack:
11188       case IcsObserving:
11189       case IcsIdle:
11190       case BeginningOfGame:
11191       case IcsExamining:
11192         return;
11193
11194       case EditGame:
11195         break;
11196
11197       case EditPosition:
11198         EditPositionDone(TRUE);
11199         break;
11200
11201       case AnalyzeMode:
11202       case AnalyzeFile:
11203         ExitAnalyzeMode();
11204         break;
11205         
11206       default:
11207         EditGameEvent();
11208         break;
11209     }
11210
11211     gameMode = IcsIdle;
11212     ModeHighlight();
11213     return;
11214 }
11215
11216
11217 void
11218 EditGameEvent()
11219 {
11220     int i;
11221
11222     switch (gameMode) {
11223       case Training:
11224         SetTrainingModeOff();
11225         break;
11226       case MachinePlaysWhite:
11227       case MachinePlaysBlack:
11228       case BeginningOfGame:
11229         SendToProgram("force\n", &first);
11230         SetUserThinkingEnables();
11231         break;
11232       case PlayFromGameFile:
11233         (void) StopLoadGameTimer();
11234         if (gameFileFP != NULL) {
11235             gameFileFP = NULL;
11236         }
11237         break;
11238       case EditPosition:
11239         EditPositionDone(TRUE);
11240         break;
11241       case AnalyzeMode:
11242       case AnalyzeFile:
11243         ExitAnalyzeMode();
11244         SendToProgram("force\n", &first);
11245         break;
11246       case TwoMachinesPlay:
11247         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11248         ResurrectChessProgram();
11249         SetUserThinkingEnables();
11250         break;
11251       case EndOfGame:
11252         ResurrectChessProgram();
11253         break;
11254       case IcsPlayingBlack:
11255       case IcsPlayingWhite:
11256         DisplayError(_("Warning: You are still playing a game"), 0);
11257         break;
11258       case IcsObserving:
11259         DisplayError(_("Warning: You are still observing a game"), 0);
11260         break;
11261       case IcsExamining:
11262         DisplayError(_("Warning: You are still examining a game"), 0);
11263         break;
11264       case IcsIdle:
11265         break;
11266       case EditGame:
11267       default:
11268         return;
11269     }
11270     
11271     pausing = FALSE;
11272     StopClocks();
11273     first.offeredDraw = second.offeredDraw = 0;
11274
11275     if (gameMode == PlayFromGameFile) {
11276         whiteTimeRemaining = timeRemaining[0][currentMove];
11277         blackTimeRemaining = timeRemaining[1][currentMove];
11278         DisplayTitle("");
11279     }
11280
11281     if (gameMode == MachinePlaysWhite ||
11282         gameMode == MachinePlaysBlack ||
11283         gameMode == TwoMachinesPlay ||
11284         gameMode == EndOfGame) {
11285         i = forwardMostMove;
11286         while (i > currentMove) {
11287             SendToProgram("undo\n", &first);
11288             i--;
11289         }
11290         whiteTimeRemaining = timeRemaining[0][currentMove];
11291         blackTimeRemaining = timeRemaining[1][currentMove];
11292         DisplayBothClocks();
11293         if (whiteFlag || blackFlag) {
11294             whiteFlag = blackFlag = 0;
11295         }
11296         DisplayTitle("");
11297     }           
11298     
11299     gameMode = EditGame;
11300     ModeHighlight();
11301     SetGameInfo();
11302 }
11303
11304
11305 void
11306 EditPositionEvent()
11307 {
11308     if (gameMode == EditPosition) {
11309         EditGameEvent();
11310         return;
11311     }
11312     
11313     EditGameEvent();
11314     if (gameMode != EditGame) return;
11315     
11316     gameMode = EditPosition;
11317     ModeHighlight();
11318     SetGameInfo();
11319     if (currentMove > 0)
11320       CopyBoard(boards[0], boards[currentMove]);
11321     
11322     blackPlaysFirst = !WhiteOnMove(currentMove);
11323     ResetClocks();
11324     currentMove = forwardMostMove = backwardMostMove = 0;
11325     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11326     DisplayMove(-1);
11327 }
11328
11329 void
11330 ExitAnalyzeMode()
11331 {
11332     /* [DM] icsEngineAnalyze - possible call from other functions */
11333     if (appData.icsEngineAnalyze) {
11334         appData.icsEngineAnalyze = FALSE;
11335
11336         DisplayMessage("",_("Close ICS engine analyze..."));
11337     }
11338     if (first.analysisSupport && first.analyzing) {
11339       SendToProgram("exit\n", &first);
11340       first.analyzing = FALSE;
11341     }
11342     thinkOutput[0] = NULLCHAR;
11343 }
11344
11345 void
11346 EditPositionDone(Boolean fakeRights)
11347 {
11348     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11349
11350     startedFromSetupPosition = TRUE;
11351     InitChessProgram(&first, FALSE);
11352     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11353       boards[0][EP_STATUS] = EP_NONE;
11354       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11355     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11356         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11357         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11358       } else boards[0][CASTLING][2] = NoRights;
11359     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11360         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11361         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11362       } else boards[0][CASTLING][5] = NoRights;
11363     }
11364     SendToProgram("force\n", &first);
11365     if (blackPlaysFirst) {
11366         strcpy(moveList[0], "");
11367         strcpy(parseList[0], "");
11368         currentMove = forwardMostMove = backwardMostMove = 1;
11369         CopyBoard(boards[1], boards[0]);
11370     } else {
11371         currentMove = forwardMostMove = backwardMostMove = 0;
11372     }
11373     SendBoard(&first, forwardMostMove);
11374     if (appData.debugMode) {
11375         fprintf(debugFP, "EditPosDone\n");
11376     }
11377     DisplayTitle("");
11378     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11379     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11380     gameMode = EditGame;
11381     ModeHighlight();
11382     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11383     ClearHighlights(); /* [AS] */
11384 }
11385
11386 /* Pause for `ms' milliseconds */
11387 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11388 void
11389 TimeDelay(ms)
11390      long ms;
11391 {
11392     TimeMark m1, m2;
11393
11394     GetTimeMark(&m1);
11395     do {
11396         GetTimeMark(&m2);
11397     } while (SubtractTimeMarks(&m2, &m1) < ms);
11398 }
11399
11400 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11401 void
11402 SendMultiLineToICS(buf)
11403      char *buf;
11404 {
11405     char temp[MSG_SIZ+1], *p;
11406     int len;
11407
11408     len = strlen(buf);
11409     if (len > MSG_SIZ)
11410       len = MSG_SIZ;
11411   
11412     strncpy(temp, buf, len);
11413     temp[len] = 0;
11414
11415     p = temp;
11416     while (*p) {
11417         if (*p == '\n' || *p == '\r')
11418           *p = ' ';
11419         ++p;
11420     }
11421
11422     strcat(temp, "\n");
11423     SendToICS(temp);
11424     SendToPlayer(temp, strlen(temp));
11425 }
11426
11427 void
11428 SetWhiteToPlayEvent()
11429 {
11430     if (gameMode == EditPosition) {
11431         blackPlaysFirst = FALSE;
11432         DisplayBothClocks();    /* works because currentMove is 0 */
11433     } else if (gameMode == IcsExamining) {
11434         SendToICS(ics_prefix);
11435         SendToICS("tomove white\n");
11436     }
11437 }
11438
11439 void
11440 SetBlackToPlayEvent()
11441 {
11442     if (gameMode == EditPosition) {
11443         blackPlaysFirst = TRUE;
11444         currentMove = 1;        /* kludge */
11445         DisplayBothClocks();
11446         currentMove = 0;
11447     } else if (gameMode == IcsExamining) {
11448         SendToICS(ics_prefix);
11449         SendToICS("tomove black\n");
11450     }
11451 }
11452
11453 void
11454 EditPositionMenuEvent(selection, x, y)
11455      ChessSquare selection;
11456      int x, y;
11457 {
11458     char buf[MSG_SIZ];
11459     ChessSquare piece = boards[0][y][x];
11460
11461     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11462
11463     switch (selection) {
11464       case ClearBoard:
11465         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11466             SendToICS(ics_prefix);
11467             SendToICS("bsetup clear\n");
11468         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11469             SendToICS(ics_prefix);
11470             SendToICS("clearboard\n");
11471         } else {
11472             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11473                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11474                 for (y = 0; y < BOARD_HEIGHT; y++) {
11475                     if (gameMode == IcsExamining) {
11476                         if (boards[currentMove][y][x] != EmptySquare) {
11477                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11478                                     AAA + x, ONE + y);
11479                             SendToICS(buf);
11480                         }
11481                     } else {
11482                         boards[0][y][x] = p;
11483                     }
11484                 }
11485             }
11486         }
11487         if (gameMode == EditPosition) {
11488             DrawPosition(FALSE, boards[0]);
11489         }
11490         break;
11491
11492       case WhitePlay:
11493         SetWhiteToPlayEvent();
11494         break;
11495
11496       case BlackPlay:
11497         SetBlackToPlayEvent();
11498         break;
11499
11500       case EmptySquare:
11501         if (gameMode == IcsExamining) {
11502             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11503             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11504             SendToICS(buf);
11505         } else {
11506             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11507                 if(x == BOARD_LEFT-2) {
11508                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11509                     boards[0][y][1] = 0;
11510                 } else
11511                 if(x == BOARD_RGHT+1) {
11512                     if(y >= gameInfo.holdingsSize) break;
11513                     boards[0][y][BOARD_WIDTH-2] = 0;
11514                 } else break;
11515             }
11516             boards[0][y][x] = EmptySquare;
11517             DrawPosition(FALSE, boards[0]);
11518         }
11519         break;
11520
11521       case PromotePiece:
11522         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11523            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11524             selection = (ChessSquare) (PROMOTED piece);
11525         } else if(piece == EmptySquare) selection = WhiteSilver;
11526         else selection = (ChessSquare)((int)piece - 1);
11527         goto defaultlabel;
11528
11529       case DemotePiece:
11530         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11531            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11532             selection = (ChessSquare) (DEMOTED piece);
11533         } else if(piece == EmptySquare) selection = BlackSilver;
11534         else selection = (ChessSquare)((int)piece + 1);       
11535         goto defaultlabel;
11536
11537       case WhiteQueen:
11538       case BlackQueen:
11539         if(gameInfo.variant == VariantShatranj ||
11540            gameInfo.variant == VariantXiangqi  ||
11541            gameInfo.variant == VariantCourier    )
11542             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11543         goto defaultlabel;
11544
11545       case WhiteKing:
11546       case BlackKing:
11547         if(gameInfo.variant == VariantXiangqi)
11548             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11549         if(gameInfo.variant == VariantKnightmate)
11550             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11551       default:
11552         defaultlabel:
11553         if (gameMode == IcsExamining) {
11554             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11555             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11556                     PieceToChar(selection), AAA + x, ONE + y);
11557             SendToICS(buf);
11558         } else {
11559             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11560                 int n;
11561                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11562                     n = PieceToNumber(selection - BlackPawn);
11563                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11564                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11565                     boards[0][BOARD_HEIGHT-1-n][1]++;
11566                 } else
11567                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11568                     n = PieceToNumber(selection);
11569                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11570                     boards[0][n][BOARD_WIDTH-1] = selection;
11571                     boards[0][n][BOARD_WIDTH-2]++;
11572                 }
11573             } else
11574             boards[0][y][x] = selection;
11575             DrawPosition(TRUE, boards[0]);
11576         }
11577         break;
11578     }
11579 }
11580
11581
11582 void
11583 DropMenuEvent(selection, x, y)
11584      ChessSquare selection;
11585      int x, y;
11586 {
11587     ChessMove moveType;
11588
11589     switch (gameMode) {
11590       case IcsPlayingWhite:
11591       case MachinePlaysBlack:
11592         if (!WhiteOnMove(currentMove)) {
11593             DisplayMoveError(_("It is Black's turn"));
11594             return;
11595         }
11596         moveType = WhiteDrop;
11597         break;
11598       case IcsPlayingBlack:
11599       case MachinePlaysWhite:
11600         if (WhiteOnMove(currentMove)) {
11601             DisplayMoveError(_("It is White's turn"));
11602             return;
11603         }
11604         moveType = BlackDrop;
11605         break;
11606       case EditGame:
11607         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11608         break;
11609       default:
11610         return;
11611     }
11612
11613     if (moveType == BlackDrop && selection < BlackPawn) {
11614       selection = (ChessSquare) ((int) selection
11615                                  + (int) BlackPawn - (int) WhitePawn);
11616     }
11617     if (boards[currentMove][y][x] != EmptySquare) {
11618         DisplayMoveError(_("That square is occupied"));
11619         return;
11620     }
11621
11622     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11623 }
11624
11625 void
11626 AcceptEvent()
11627 {
11628     /* Accept a pending offer of any kind from opponent */
11629     
11630     if (appData.icsActive) {
11631         SendToICS(ics_prefix);
11632         SendToICS("accept\n");
11633     } else if (cmailMsgLoaded) {
11634         if (currentMove == cmailOldMove &&
11635             commentList[cmailOldMove] != NULL &&
11636             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11637                    "Black offers a draw" : "White offers a draw")) {
11638             TruncateGame();
11639             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11640             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11641         } else {
11642             DisplayError(_("There is no pending offer on this move"), 0);
11643             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11644         }
11645     } else {
11646         /* Not used for offers from chess program */
11647     }
11648 }
11649
11650 void
11651 DeclineEvent()
11652 {
11653     /* Decline a pending offer of any kind from opponent */
11654     
11655     if (appData.icsActive) {
11656         SendToICS(ics_prefix);
11657         SendToICS("decline\n");
11658     } else if (cmailMsgLoaded) {
11659         if (currentMove == cmailOldMove &&
11660             commentList[cmailOldMove] != NULL &&
11661             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11662                    "Black offers a draw" : "White offers a draw")) {
11663 #ifdef NOTDEF
11664             AppendComment(cmailOldMove, "Draw declined", TRUE);
11665             DisplayComment(cmailOldMove - 1, "Draw declined");
11666 #endif /*NOTDEF*/
11667         } else {
11668             DisplayError(_("There is no pending offer on this move"), 0);
11669         }
11670     } else {
11671         /* Not used for offers from chess program */
11672     }
11673 }
11674
11675 void
11676 RematchEvent()
11677 {
11678     /* Issue ICS rematch command */
11679     if (appData.icsActive) {
11680         SendToICS(ics_prefix);
11681         SendToICS("rematch\n");
11682     }
11683 }
11684
11685 void
11686 CallFlagEvent()
11687 {
11688     /* Call your opponent's flag (claim a win on time) */
11689     if (appData.icsActive) {
11690         SendToICS(ics_prefix);
11691         SendToICS("flag\n");
11692     } else {
11693         switch (gameMode) {
11694           default:
11695             return;
11696           case MachinePlaysWhite:
11697             if (whiteFlag) {
11698                 if (blackFlag)
11699                   GameEnds(GameIsDrawn, "Both players ran out of time",
11700                            GE_PLAYER);
11701                 else
11702                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11703             } else {
11704                 DisplayError(_("Your opponent is not out of time"), 0);
11705             }
11706             break;
11707           case MachinePlaysBlack:
11708             if (blackFlag) {
11709                 if (whiteFlag)
11710                   GameEnds(GameIsDrawn, "Both players ran out of time",
11711                            GE_PLAYER);
11712                 else
11713                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11714             } else {
11715                 DisplayError(_("Your opponent is not out of time"), 0);
11716             }
11717             break;
11718         }
11719     }
11720 }
11721
11722 void
11723 DrawEvent()
11724 {
11725     /* Offer draw or accept pending draw offer from opponent */
11726     
11727     if (appData.icsActive) {
11728         /* Note: tournament rules require draw offers to be
11729            made after you make your move but before you punch
11730            your clock.  Currently ICS doesn't let you do that;
11731            instead, you immediately punch your clock after making
11732            a move, but you can offer a draw at any time. */
11733         
11734         SendToICS(ics_prefix);
11735         SendToICS("draw\n");
11736     } else if (cmailMsgLoaded) {
11737         if (currentMove == cmailOldMove &&
11738             commentList[cmailOldMove] != NULL &&
11739             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11740                    "Black offers a draw" : "White offers a draw")) {
11741             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11742             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11743         } else if (currentMove == cmailOldMove + 1) {
11744             char *offer = WhiteOnMove(cmailOldMove) ?
11745               "White offers a draw" : "Black offers a draw";
11746             AppendComment(currentMove, offer, TRUE);
11747             DisplayComment(currentMove - 1, offer);
11748             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11749         } else {
11750             DisplayError(_("You must make your move before offering a draw"), 0);
11751             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11752         }
11753     } else if (first.offeredDraw) {
11754         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11755     } else {
11756         if (first.sendDrawOffers) {
11757             SendToProgram("draw\n", &first);
11758             userOfferedDraw = TRUE;
11759         }
11760     }
11761 }
11762
11763 void
11764 AdjournEvent()
11765 {
11766     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11767     
11768     if (appData.icsActive) {
11769         SendToICS(ics_prefix);
11770         SendToICS("adjourn\n");
11771     } else {
11772         /* Currently GNU Chess doesn't offer or accept Adjourns */
11773     }
11774 }
11775
11776
11777 void
11778 AbortEvent()
11779 {
11780     /* Offer Abort or accept pending Abort offer from opponent */
11781     
11782     if (appData.icsActive) {
11783         SendToICS(ics_prefix);
11784         SendToICS("abort\n");
11785     } else {
11786         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11787     }
11788 }
11789
11790 void
11791 ResignEvent()
11792 {
11793     /* Resign.  You can do this even if it's not your turn. */
11794     
11795     if (appData.icsActive) {
11796         SendToICS(ics_prefix);
11797         SendToICS("resign\n");
11798     } else {
11799         switch (gameMode) {
11800           case MachinePlaysWhite:
11801             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11802             break;
11803           case MachinePlaysBlack:
11804             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11805             break;
11806           case EditGame:
11807             if (cmailMsgLoaded) {
11808                 TruncateGame();
11809                 if (WhiteOnMove(cmailOldMove)) {
11810                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11811                 } else {
11812                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11813                 }
11814                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11815             }
11816             break;
11817           default:
11818             break;
11819         }
11820     }
11821 }
11822
11823
11824 void
11825 StopObservingEvent()
11826 {
11827     /* Stop observing current games */
11828     SendToICS(ics_prefix);
11829     SendToICS("unobserve\n");
11830 }
11831
11832 void
11833 StopExaminingEvent()
11834 {
11835     /* Stop observing current game */
11836     SendToICS(ics_prefix);
11837     SendToICS("unexamine\n");
11838 }
11839
11840 void
11841 ForwardInner(target)
11842      int target;
11843 {
11844     int limit;
11845
11846     if (appData.debugMode)
11847         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11848                 target, currentMove, forwardMostMove);
11849
11850     if (gameMode == EditPosition)
11851       return;
11852
11853     if (gameMode == PlayFromGameFile && !pausing)
11854       PauseEvent();
11855     
11856     if (gameMode == IcsExamining && pausing)
11857       limit = pauseExamForwardMostMove;
11858     else
11859       limit = forwardMostMove;
11860     
11861     if (target > limit) target = limit;
11862
11863     if (target > 0 && moveList[target - 1][0]) {
11864         int fromX, fromY, toX, toY;
11865         toX = moveList[target - 1][2] - AAA;
11866         toY = moveList[target - 1][3] - ONE;
11867         if (moveList[target - 1][1] == '@') {
11868             if (appData.highlightLastMove) {
11869                 SetHighlights(-1, -1, toX, toY);
11870             }
11871         } else {
11872             fromX = moveList[target - 1][0] - AAA;
11873             fromY = moveList[target - 1][1] - ONE;
11874             if (target == currentMove + 1) {
11875                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11876             }
11877             if (appData.highlightLastMove) {
11878                 SetHighlights(fromX, fromY, toX, toY);
11879             }
11880         }
11881     }
11882     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11883         gameMode == Training || gameMode == PlayFromGameFile || 
11884         gameMode == AnalyzeFile) {
11885         while (currentMove < target) {
11886             SendMoveToProgram(currentMove++, &first);
11887         }
11888     } else {
11889         currentMove = target;
11890     }
11891     
11892     if (gameMode == EditGame || gameMode == EndOfGame) {
11893         whiteTimeRemaining = timeRemaining[0][currentMove];
11894         blackTimeRemaining = timeRemaining[1][currentMove];
11895     }
11896     DisplayBothClocks();
11897     DisplayMove(currentMove - 1);
11898     DrawPosition(FALSE, boards[currentMove]);
11899     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11900     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11901         DisplayComment(currentMove - 1, commentList[currentMove]);
11902     }
11903 }
11904
11905
11906 void
11907 ForwardEvent()
11908 {
11909     if (gameMode == IcsExamining && !pausing) {
11910         SendToICS(ics_prefix);
11911         SendToICS("forward\n");
11912     } else {
11913         ForwardInner(currentMove + 1);
11914     }
11915 }
11916
11917 void
11918 ToEndEvent()
11919 {
11920     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11921         /* to optimze, we temporarily turn off analysis mode while we feed
11922          * the remaining moves to the engine. Otherwise we get analysis output
11923          * after each move.
11924          */ 
11925         if (first.analysisSupport) {
11926           SendToProgram("exit\nforce\n", &first);
11927           first.analyzing = FALSE;
11928         }
11929     }
11930         
11931     if (gameMode == IcsExamining && !pausing) {
11932         SendToICS(ics_prefix);
11933         SendToICS("forward 999999\n");
11934     } else {
11935         ForwardInner(forwardMostMove);
11936     }
11937
11938     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11939         /* we have fed all the moves, so reactivate analysis mode */
11940         SendToProgram("analyze\n", &first);
11941         first.analyzing = TRUE;
11942         /*first.maybeThinking = TRUE;*/
11943         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11944     }
11945 }
11946
11947 void
11948 BackwardInner(target)
11949      int target;
11950 {
11951     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11952
11953     if (appData.debugMode)
11954         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11955                 target, currentMove, forwardMostMove);
11956
11957     if (gameMode == EditPosition) return;
11958     if (currentMove <= backwardMostMove) {
11959         ClearHighlights();
11960         DrawPosition(full_redraw, boards[currentMove]);
11961         return;
11962     }
11963     if (gameMode == PlayFromGameFile && !pausing)
11964       PauseEvent();
11965     
11966     if (moveList[target][0]) {
11967         int fromX, fromY, toX, toY;
11968         toX = moveList[target][2] - AAA;
11969         toY = moveList[target][3] - ONE;
11970         if (moveList[target][1] == '@') {
11971             if (appData.highlightLastMove) {
11972                 SetHighlights(-1, -1, toX, toY);
11973             }
11974         } else {
11975             fromX = moveList[target][0] - AAA;
11976             fromY = moveList[target][1] - ONE;
11977             if (target == currentMove - 1) {
11978                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11979             }
11980             if (appData.highlightLastMove) {
11981                 SetHighlights(fromX, fromY, toX, toY);
11982             }
11983         }
11984     }
11985     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11986         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11987         while (currentMove > target) {
11988             SendToProgram("undo\n", &first);
11989             currentMove--;
11990         }
11991     } else {
11992         currentMove = target;
11993     }
11994     
11995     if (gameMode == EditGame || gameMode == EndOfGame) {
11996         whiteTimeRemaining = timeRemaining[0][currentMove];
11997         blackTimeRemaining = timeRemaining[1][currentMove];
11998     }
11999     DisplayBothClocks();
12000     DisplayMove(currentMove - 1);
12001     DrawPosition(full_redraw, boards[currentMove]);
12002     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12003     // [HGM] PV info: routine tests if comment empty
12004     DisplayComment(currentMove - 1, commentList[currentMove]);
12005 }
12006
12007 void
12008 BackwardEvent()
12009 {
12010     if (gameMode == IcsExamining && !pausing) {
12011         SendToICS(ics_prefix);
12012         SendToICS("backward\n");
12013     } else {
12014         BackwardInner(currentMove - 1);
12015     }
12016 }
12017
12018 void
12019 ToStartEvent()
12020 {
12021     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12022         /* to optimize, we temporarily turn off analysis mode while we undo
12023          * all the moves. Otherwise we get analysis output after each undo.
12024          */ 
12025         if (first.analysisSupport) {
12026           SendToProgram("exit\nforce\n", &first);
12027           first.analyzing = FALSE;
12028         }
12029     }
12030
12031     if (gameMode == IcsExamining && !pausing) {
12032         SendToICS(ics_prefix);
12033         SendToICS("backward 999999\n");
12034     } else {
12035         BackwardInner(backwardMostMove);
12036     }
12037
12038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12039         /* we have fed all the moves, so reactivate analysis mode */
12040         SendToProgram("analyze\n", &first);
12041         first.analyzing = TRUE;
12042         /*first.maybeThinking = TRUE;*/
12043         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12044     }
12045 }
12046
12047 void
12048 ToNrEvent(int to)
12049 {
12050   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12051   if (to >= forwardMostMove) to = forwardMostMove;
12052   if (to <= backwardMostMove) to = backwardMostMove;
12053   if (to < currentMove) {
12054     BackwardInner(to);
12055   } else {
12056     ForwardInner(to);
12057   }
12058 }
12059
12060 void
12061 RevertEvent()
12062 {
12063     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12064         return;
12065     }
12066     if (gameMode != IcsExamining) {
12067         DisplayError(_("You are not examining a game"), 0);
12068         return;
12069     }
12070     if (pausing) {
12071         DisplayError(_("You can't revert while pausing"), 0);
12072         return;
12073     }
12074     SendToICS(ics_prefix);
12075     SendToICS("revert\n");
12076 }
12077
12078 void
12079 RetractMoveEvent()
12080 {
12081     switch (gameMode) {
12082       case MachinePlaysWhite:
12083       case MachinePlaysBlack:
12084         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12085             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12086             return;
12087         }
12088         if (forwardMostMove < 2) return;
12089         currentMove = forwardMostMove = forwardMostMove - 2;
12090         whiteTimeRemaining = timeRemaining[0][currentMove];
12091         blackTimeRemaining = timeRemaining[1][currentMove];
12092         DisplayBothClocks();
12093         DisplayMove(currentMove - 1);
12094         ClearHighlights();/*!! could figure this out*/
12095         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12096         SendToProgram("remove\n", &first);
12097         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12098         break;
12099
12100       case BeginningOfGame:
12101       default:
12102         break;
12103
12104       case IcsPlayingWhite:
12105       case IcsPlayingBlack:
12106         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12107             SendToICS(ics_prefix);
12108             SendToICS("takeback 2\n");
12109         } else {
12110             SendToICS(ics_prefix);
12111             SendToICS("takeback 1\n");
12112         }
12113         break;
12114     }
12115 }
12116
12117 void
12118 MoveNowEvent()
12119 {
12120     ChessProgramState *cps;
12121
12122     switch (gameMode) {
12123       case MachinePlaysWhite:
12124         if (!WhiteOnMove(forwardMostMove)) {
12125             DisplayError(_("It is your turn"), 0);
12126             return;
12127         }
12128         cps = &first;
12129         break;
12130       case MachinePlaysBlack:
12131         if (WhiteOnMove(forwardMostMove)) {
12132             DisplayError(_("It is your turn"), 0);
12133             return;
12134         }
12135         cps = &first;
12136         break;
12137       case TwoMachinesPlay:
12138         if (WhiteOnMove(forwardMostMove) ==
12139             (first.twoMachinesColor[0] == 'w')) {
12140             cps = &first;
12141         } else {
12142             cps = &second;
12143         }
12144         break;
12145       case BeginningOfGame:
12146       default:
12147         return;
12148     }
12149     SendToProgram("?\n", cps);
12150 }
12151
12152 void
12153 TruncateGameEvent()
12154 {
12155     EditGameEvent();
12156     if (gameMode != EditGame) return;
12157     TruncateGame();
12158 }
12159
12160 void
12161 TruncateGame()
12162 {
12163     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12164     if (forwardMostMove > currentMove) {
12165         if (gameInfo.resultDetails != NULL) {
12166             free(gameInfo.resultDetails);
12167             gameInfo.resultDetails = NULL;
12168             gameInfo.result = GameUnfinished;
12169         }
12170         forwardMostMove = currentMove;
12171         HistorySet(parseList, backwardMostMove, forwardMostMove,
12172                    currentMove-1);
12173     }
12174 }
12175
12176 void
12177 HintEvent()
12178 {
12179     if (appData.noChessProgram) return;
12180     switch (gameMode) {
12181       case MachinePlaysWhite:
12182         if (WhiteOnMove(forwardMostMove)) {
12183             DisplayError(_("Wait until your turn"), 0);
12184             return;
12185         }
12186         break;
12187       case BeginningOfGame:
12188       case MachinePlaysBlack:
12189         if (!WhiteOnMove(forwardMostMove)) {
12190             DisplayError(_("Wait until your turn"), 0);
12191             return;
12192         }
12193         break;
12194       default:
12195         DisplayError(_("No hint available"), 0);
12196         return;
12197     }
12198     SendToProgram("hint\n", &first);
12199     hintRequested = TRUE;
12200 }
12201
12202 void
12203 BookEvent()
12204 {
12205     if (appData.noChessProgram) return;
12206     switch (gameMode) {
12207       case MachinePlaysWhite:
12208         if (WhiteOnMove(forwardMostMove)) {
12209             DisplayError(_("Wait until your turn"), 0);
12210             return;
12211         }
12212         break;
12213       case BeginningOfGame:
12214       case MachinePlaysBlack:
12215         if (!WhiteOnMove(forwardMostMove)) {
12216             DisplayError(_("Wait until your turn"), 0);
12217             return;
12218         }
12219         break;
12220       case EditPosition:
12221         EditPositionDone(TRUE);
12222         break;
12223       case TwoMachinesPlay:
12224         return;
12225       default:
12226         break;
12227     }
12228     SendToProgram("bk\n", &first);
12229     bookOutput[0] = NULLCHAR;
12230     bookRequested = TRUE;
12231 }
12232
12233 void
12234 AboutGameEvent()
12235 {
12236     char *tags = PGNTags(&gameInfo);
12237     TagsPopUp(tags, CmailMsg());
12238     free(tags);
12239 }
12240
12241 /* end button procedures */
12242
12243 void
12244 PrintPosition(fp, move)
12245      FILE *fp;
12246      int move;
12247 {
12248     int i, j;
12249     
12250     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12251         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12252             char c = PieceToChar(boards[move][i][j]);
12253             fputc(c == 'x' ? '.' : c, fp);
12254             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12255         }
12256     }
12257     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12258       fprintf(fp, "white to play\n");
12259     else
12260       fprintf(fp, "black to play\n");
12261 }
12262
12263 void
12264 PrintOpponents(fp)
12265      FILE *fp;
12266 {
12267     if (gameInfo.white != NULL) {
12268         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12269     } else {
12270         fprintf(fp, "\n");
12271     }
12272 }
12273
12274 /* Find last component of program's own name, using some heuristics */
12275 void
12276 TidyProgramName(prog, host, buf)
12277      char *prog, *host, buf[MSG_SIZ];
12278 {
12279     char *p, *q;
12280     int local = (strcmp(host, "localhost") == 0);
12281     while (!local && (p = strchr(prog, ';')) != NULL) {
12282         p++;
12283         while (*p == ' ') p++;
12284         prog = p;
12285     }
12286     if (*prog == '"' || *prog == '\'') {
12287         q = strchr(prog + 1, *prog);
12288     } else {
12289         q = strchr(prog, ' ');
12290     }
12291     if (q == NULL) q = prog + strlen(prog);
12292     p = q;
12293     while (p >= prog && *p != '/' && *p != '\\') p--;
12294     p++;
12295     if(p == prog && *p == '"') p++;
12296     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12297     memcpy(buf, p, q - p);
12298     buf[q - p] = NULLCHAR;
12299     if (!local) {
12300         strcat(buf, "@");
12301         strcat(buf, host);
12302     }
12303 }
12304
12305 char *
12306 TimeControlTagValue()
12307 {
12308     char buf[MSG_SIZ];
12309     if (!appData.clockMode) {
12310         strcpy(buf, "-");
12311     } else if (movesPerSession > 0) {
12312         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12313     } else if (timeIncrement == 0) {
12314         sprintf(buf, "%ld", timeControl/1000);
12315     } else {
12316         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12317     }
12318     return StrSave(buf);
12319 }
12320
12321 void
12322 SetGameInfo()
12323 {
12324     /* This routine is used only for certain modes */
12325     VariantClass v = gameInfo.variant;
12326     ChessMove r = GameUnfinished;
12327     char *p = NULL;
12328
12329     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12330         r = gameInfo.result; 
12331         p = gameInfo.resultDetails; 
12332         gameInfo.resultDetails = NULL;
12333     }
12334     ClearGameInfo(&gameInfo);
12335     gameInfo.variant = v;
12336
12337     switch (gameMode) {
12338       case MachinePlaysWhite:
12339         gameInfo.event = StrSave( appData.pgnEventHeader );
12340         gameInfo.site = StrSave(HostName());
12341         gameInfo.date = PGNDate();
12342         gameInfo.round = StrSave("-");
12343         gameInfo.white = StrSave(first.tidy);
12344         gameInfo.black = StrSave(UserName());
12345         gameInfo.timeControl = TimeControlTagValue();
12346         break;
12347
12348       case MachinePlaysBlack:
12349         gameInfo.event = StrSave( appData.pgnEventHeader );
12350         gameInfo.site = StrSave(HostName());
12351         gameInfo.date = PGNDate();
12352         gameInfo.round = StrSave("-");
12353         gameInfo.white = StrSave(UserName());
12354         gameInfo.black = StrSave(first.tidy);
12355         gameInfo.timeControl = TimeControlTagValue();
12356         break;
12357
12358       case TwoMachinesPlay:
12359         gameInfo.event = StrSave( appData.pgnEventHeader );
12360         gameInfo.site = StrSave(HostName());
12361         gameInfo.date = PGNDate();
12362         if (matchGame > 0) {
12363             char buf[MSG_SIZ];
12364             sprintf(buf, "%d", matchGame);
12365             gameInfo.round = StrSave(buf);
12366         } else {
12367             gameInfo.round = StrSave("-");
12368         }
12369         if (first.twoMachinesColor[0] == 'w') {
12370             gameInfo.white = StrSave(first.tidy);
12371             gameInfo.black = StrSave(second.tidy);
12372         } else {
12373             gameInfo.white = StrSave(second.tidy);
12374             gameInfo.black = StrSave(first.tidy);
12375         }
12376         gameInfo.timeControl = TimeControlTagValue();
12377         break;
12378
12379       case EditGame:
12380         gameInfo.event = StrSave("Edited game");
12381         gameInfo.site = StrSave(HostName());
12382         gameInfo.date = PGNDate();
12383         gameInfo.round = StrSave("-");
12384         gameInfo.white = StrSave("-");
12385         gameInfo.black = StrSave("-");
12386         gameInfo.result = r;
12387         gameInfo.resultDetails = p;
12388         break;
12389
12390       case EditPosition:
12391         gameInfo.event = StrSave("Edited position");
12392         gameInfo.site = StrSave(HostName());
12393         gameInfo.date = PGNDate();
12394         gameInfo.round = StrSave("-");
12395         gameInfo.white = StrSave("-");
12396         gameInfo.black = StrSave("-");
12397         break;
12398
12399       case IcsPlayingWhite:
12400       case IcsPlayingBlack:
12401       case IcsObserving:
12402       case IcsExamining:
12403         break;
12404
12405       case PlayFromGameFile:
12406         gameInfo.event = StrSave("Game from non-PGN file");
12407         gameInfo.site = StrSave(HostName());
12408         gameInfo.date = PGNDate();
12409         gameInfo.round = StrSave("-");
12410         gameInfo.white = StrSave("?");
12411         gameInfo.black = StrSave("?");
12412         break;
12413
12414       default:
12415         break;
12416     }
12417 }
12418
12419 void
12420 ReplaceComment(index, text)
12421      int index;
12422      char *text;
12423 {
12424     int len;
12425
12426     while (*text == '\n') text++;
12427     len = strlen(text);
12428     while (len > 0 && text[len - 1] == '\n') len--;
12429
12430     if (commentList[index] != NULL)
12431       free(commentList[index]);
12432
12433     if (len == 0) {
12434         commentList[index] = NULL;
12435         return;
12436     }
12437   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12438       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12439       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12440     commentList[index] = (char *) malloc(len + 2);
12441     strncpy(commentList[index], text, len);
12442     commentList[index][len] = '\n';
12443     commentList[index][len + 1] = NULLCHAR;
12444   } else { 
12445     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12446     char *p;
12447     commentList[index] = (char *) malloc(len + 6);
12448     strcpy(commentList[index], "{\n");
12449     strncpy(commentList[index]+2, text, len);
12450     commentList[index][len+2] = NULLCHAR;
12451     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12452     strcat(commentList[index], "\n}\n");
12453   }
12454 }
12455
12456 void
12457 CrushCRs(text)
12458      char *text;
12459 {
12460   char *p = text;
12461   char *q = text;
12462   char ch;
12463
12464   do {
12465     ch = *p++;
12466     if (ch == '\r') continue;
12467     *q++ = ch;
12468   } while (ch != '\0');
12469 }
12470
12471 void
12472 AppendComment(index, text, addBraces)
12473      int index;
12474      char *text;
12475      Boolean addBraces; // [HGM] braces: tells if we should add {}
12476 {
12477     int oldlen, len;
12478     char *old;
12479
12480 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12481     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12482
12483     CrushCRs(text);
12484     while (*text == '\n') text++;
12485     len = strlen(text);
12486     while (len > 0 && text[len - 1] == '\n') len--;
12487
12488     if (len == 0) return;
12489
12490     if (commentList[index] != NULL) {
12491         old = commentList[index];
12492         oldlen = strlen(old);
12493         while(commentList[index][oldlen-1] ==  '\n')
12494           commentList[index][--oldlen] = NULLCHAR;
12495         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12496         strcpy(commentList[index], old);
12497         free(old);
12498         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12499         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12500           if(addBraces) addBraces = FALSE; else { text++; len--; }
12501           while (*text == '\n') { text++; len--; }
12502           commentList[index][--oldlen] = NULLCHAR;
12503       }
12504         if(addBraces) strcat(commentList[index], "\n{\n");
12505         else          strcat(commentList[index], "\n");
12506         strcat(commentList[index], text);
12507         if(addBraces) strcat(commentList[index], "\n}\n");
12508         else          strcat(commentList[index], "\n");
12509     } else {
12510         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12511         if(addBraces)
12512              strcpy(commentList[index], "{\n");
12513         else commentList[index][0] = NULLCHAR;
12514         strcat(commentList[index], text);
12515         strcat(commentList[index], "\n");
12516         if(addBraces) strcat(commentList[index], "}\n");
12517     }
12518 }
12519
12520 static char * FindStr( char * text, char * sub_text )
12521 {
12522     char * result = strstr( text, sub_text );
12523
12524     if( result != NULL ) {
12525         result += strlen( sub_text );
12526     }
12527
12528     return result;
12529 }
12530
12531 /* [AS] Try to extract PV info from PGN comment */
12532 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12533 char *GetInfoFromComment( int index, char * text )
12534 {
12535     char * sep = text;
12536
12537     if( text != NULL && index > 0 ) {
12538         int score = 0;
12539         int depth = 0;
12540         int time = -1, sec = 0, deci;
12541         char * s_eval = FindStr( text, "[%eval " );
12542         char * s_emt = FindStr( text, "[%emt " );
12543
12544         if( s_eval != NULL || s_emt != NULL ) {
12545             /* New style */
12546             char delim;
12547
12548             if( s_eval != NULL ) {
12549                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12550                     return text;
12551                 }
12552
12553                 if( delim != ']' ) {
12554                     return text;
12555                 }
12556             }
12557
12558             if( s_emt != NULL ) {
12559             }
12560                 return text;
12561         }
12562         else {
12563             /* We expect something like: [+|-]nnn.nn/dd */
12564             int score_lo = 0;
12565
12566             if(*text != '{') return text; // [HGM] braces: must be normal comment
12567
12568             sep = strchr( text, '/' );
12569             if( sep == NULL || sep < (text+4) ) {
12570                 return text;
12571             }
12572
12573             time = -1; sec = -1; deci = -1;
12574             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12575                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12576                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12577                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12578                 return text;
12579             }
12580
12581             if( score_lo < 0 || score_lo >= 100 ) {
12582                 return text;
12583             }
12584
12585             if(sec >= 0) time = 600*time + 10*sec; else
12586             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12587
12588             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12589
12590             /* [HGM] PV time: now locate end of PV info */
12591             while( *++sep >= '0' && *sep <= '9'); // strip depth
12592             if(time >= 0)
12593             while( *++sep >= '0' && *sep <= '9'); // strip time
12594             if(sec >= 0)
12595             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12596             if(deci >= 0)
12597             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12598             while(*sep == ' ') sep++;
12599         }
12600
12601         if( depth <= 0 ) {
12602             return text;
12603         }
12604
12605         if( time < 0 ) {
12606             time = -1;
12607         }
12608
12609         pvInfoList[index-1].depth = depth;
12610         pvInfoList[index-1].score = score;
12611         pvInfoList[index-1].time  = 10*time; // centi-sec
12612         if(*sep == '}') *sep = 0; else *--sep = '{';
12613     }
12614     return sep;
12615 }
12616
12617 void
12618 SendToProgram(message, cps)
12619      char *message;
12620      ChessProgramState *cps;
12621 {
12622     int count, outCount, error;
12623     char buf[MSG_SIZ];
12624
12625     if (cps->pr == NULL) return;
12626     Attention(cps);
12627     
12628     if (appData.debugMode) {
12629         TimeMark now;
12630         GetTimeMark(&now);
12631         fprintf(debugFP, "%ld >%-6s: %s", 
12632                 SubtractTimeMarks(&now, &programStartTime),
12633                 cps->which, message);
12634     }
12635     
12636     count = strlen(message);
12637     outCount = OutputToProcess(cps->pr, message, count, &error);
12638     if (outCount < count && !exiting 
12639                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12640         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12641         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12642             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12643                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12644                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12645             } else {
12646                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12647             }
12648             gameInfo.resultDetails = StrSave(buf);
12649         }
12650         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12651     }
12652 }
12653
12654 void
12655 ReceiveFromProgram(isr, closure, message, count, error)
12656      InputSourceRef isr;
12657      VOIDSTAR closure;
12658      char *message;
12659      int count;
12660      int error;
12661 {
12662     char *end_str;
12663     char buf[MSG_SIZ];
12664     ChessProgramState *cps = (ChessProgramState *)closure;
12665
12666     if (isr != cps->isr) return; /* Killed intentionally */
12667     if (count <= 0) {
12668         if (count == 0) {
12669             sprintf(buf,
12670                     _("Error: %s chess program (%s) exited unexpectedly"),
12671                     cps->which, cps->program);
12672         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12673                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12674                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12675                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12676                 } else {
12677                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12678                 }
12679                 gameInfo.resultDetails = StrSave(buf);
12680             }
12681             RemoveInputSource(cps->isr);
12682             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12683         } else {
12684             sprintf(buf,
12685                     _("Error reading from %s chess program (%s)"),
12686                     cps->which, cps->program);
12687             RemoveInputSource(cps->isr);
12688
12689             /* [AS] Program is misbehaving badly... kill it */
12690             if( count == -2 ) {
12691                 DestroyChildProcess( cps->pr, 9 );
12692                 cps->pr = NoProc;
12693             }
12694
12695             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12696         }
12697         return;
12698     }
12699     
12700     if ((end_str = strchr(message, '\r')) != NULL)
12701       *end_str = NULLCHAR;
12702     if ((end_str = strchr(message, '\n')) != NULL)
12703       *end_str = NULLCHAR;
12704     
12705     if (appData.debugMode) {
12706         TimeMark now; int print = 1;
12707         char *quote = ""; char c; int i;
12708
12709         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12710                 char start = message[0];
12711                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12712                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12713                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12714                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12715                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12716                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12717                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12718                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12719                         { quote = "# "; print = (appData.engineComments == 2); }
12720                 message[0] = start; // restore original message
12721         }
12722         if(print) {
12723                 GetTimeMark(&now);
12724                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12725                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12726                         quote,
12727                         message);
12728         }
12729     }
12730
12731     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12732     if (appData.icsEngineAnalyze) {
12733         if (strstr(message, "whisper") != NULL ||
12734              strstr(message, "kibitz") != NULL || 
12735             strstr(message, "tellics") != NULL) return;
12736     }
12737
12738     HandleMachineMove(message, cps);
12739 }
12740
12741
12742 void
12743 SendTimeControl(cps, mps, tc, inc, sd, st)
12744      ChessProgramState *cps;
12745      int mps, inc, sd, st;
12746      long tc;
12747 {
12748     char buf[MSG_SIZ];
12749     int seconds;
12750
12751     if( timeControl_2 > 0 ) {
12752         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12753             tc = timeControl_2;
12754         }
12755     }
12756     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12757     inc /= cps->timeOdds;
12758     st  /= cps->timeOdds;
12759
12760     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12761
12762     if (st > 0) {
12763       /* Set exact time per move, normally using st command */
12764       if (cps->stKludge) {
12765         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12766         seconds = st % 60;
12767         if (seconds == 0) {
12768           sprintf(buf, "level 1 %d\n", st/60);
12769         } else {
12770           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12771         }
12772       } else {
12773         sprintf(buf, "st %d\n", st);
12774       }
12775     } else {
12776       /* Set conventional or incremental time control, using level command */
12777       if (seconds == 0) {
12778         /* Note old gnuchess bug -- minutes:seconds used to not work.
12779            Fixed in later versions, but still avoid :seconds
12780            when seconds is 0. */
12781         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12782       } else {
12783         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12784                 seconds, inc/1000);
12785       }
12786     }
12787     SendToProgram(buf, cps);
12788
12789     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12790     /* Orthogonally, limit search to given depth */
12791     if (sd > 0) {
12792       if (cps->sdKludge) {
12793         sprintf(buf, "depth\n%d\n", sd);
12794       } else {
12795         sprintf(buf, "sd %d\n", sd);
12796       }
12797       SendToProgram(buf, cps);
12798     }
12799
12800     if(cps->nps > 0) { /* [HGM] nps */
12801         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12802         else {
12803                 sprintf(buf, "nps %d\n", cps->nps);
12804               SendToProgram(buf, cps);
12805         }
12806     }
12807 }
12808
12809 ChessProgramState *WhitePlayer()
12810 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12811 {
12812     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12813        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12814         return &second;
12815     return &first;
12816 }
12817
12818 void
12819 SendTimeRemaining(cps, machineWhite)
12820      ChessProgramState *cps;
12821      int /*boolean*/ machineWhite;
12822 {
12823     char message[MSG_SIZ];
12824     long time, otime;
12825
12826     /* Note: this routine must be called when the clocks are stopped
12827        or when they have *just* been set or switched; otherwise
12828        it will be off by the time since the current tick started.
12829     */
12830     if (machineWhite) {
12831         time = whiteTimeRemaining / 10;
12832         otime = blackTimeRemaining / 10;
12833     } else {
12834         time = blackTimeRemaining / 10;
12835         otime = whiteTimeRemaining / 10;
12836     }
12837     /* [HGM] translate opponent's time by time-odds factor */
12838     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12839     if (appData.debugMode) {
12840         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12841     }
12842
12843     if (time <= 0) time = 1;
12844     if (otime <= 0) otime = 1;
12845     
12846     sprintf(message, "time %ld\n", time);
12847     SendToProgram(message, cps);
12848
12849     sprintf(message, "otim %ld\n", otime);
12850     SendToProgram(message, cps);
12851 }
12852
12853 int
12854 BoolFeature(p, name, loc, cps)
12855      char **p;
12856      char *name;
12857      int *loc;
12858      ChessProgramState *cps;
12859 {
12860   char buf[MSG_SIZ];
12861   int len = strlen(name);
12862   int val;
12863   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12864     (*p) += len + 1;
12865     sscanf(*p, "%d", &val);
12866     *loc = (val != 0);
12867     while (**p && **p != ' ') (*p)++;
12868     sprintf(buf, "accepted %s\n", name);
12869     SendToProgram(buf, cps);
12870     return TRUE;
12871   }
12872   return FALSE;
12873 }
12874
12875 int
12876 IntFeature(p, name, loc, cps)
12877      char **p;
12878      char *name;
12879      int *loc;
12880      ChessProgramState *cps;
12881 {
12882   char buf[MSG_SIZ];
12883   int len = strlen(name);
12884   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12885     (*p) += len + 1;
12886     sscanf(*p, "%d", loc);
12887     while (**p && **p != ' ') (*p)++;
12888     sprintf(buf, "accepted %s\n", name);
12889     SendToProgram(buf, cps);
12890     return TRUE;
12891   }
12892   return FALSE;
12893 }
12894
12895 int
12896 StringFeature(p, name, loc, cps)
12897      char **p;
12898      char *name;
12899      char loc[];
12900      ChessProgramState *cps;
12901 {
12902   char buf[MSG_SIZ];
12903   int len = strlen(name);
12904   if (strncmp((*p), name, len) == 0
12905       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12906     (*p) += len + 2;
12907     sscanf(*p, "%[^\"]", loc);
12908     while (**p && **p != '\"') (*p)++;
12909     if (**p == '\"') (*p)++;
12910     sprintf(buf, "accepted %s\n", name);
12911     SendToProgram(buf, cps);
12912     return TRUE;
12913   }
12914   return FALSE;
12915 }
12916
12917 int 
12918 ParseOption(Option *opt, ChessProgramState *cps)
12919 // [HGM] options: process the string that defines an engine option, and determine
12920 // name, type, default value, and allowed value range
12921 {
12922         char *p, *q, buf[MSG_SIZ];
12923         int n, min = (-1)<<31, max = 1<<31, def;
12924
12925         if(p = strstr(opt->name, " -spin ")) {
12926             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12927             if(max < min) max = min; // enforce consistency
12928             if(def < min) def = min;
12929             if(def > max) def = max;
12930             opt->value = def;
12931             opt->min = min;
12932             opt->max = max;
12933             opt->type = Spin;
12934         } else if((p = strstr(opt->name, " -slider "))) {
12935             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12936             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12937             if(max < min) max = min; // enforce consistency
12938             if(def < min) def = min;
12939             if(def > max) def = max;
12940             opt->value = def;
12941             opt->min = min;
12942             opt->max = max;
12943             opt->type = Spin; // Slider;
12944         } else if((p = strstr(opt->name, " -string "))) {
12945             opt->textValue = p+9;
12946             opt->type = TextBox;
12947         } else if((p = strstr(opt->name, " -file "))) {
12948             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12949             opt->textValue = p+7;
12950             opt->type = TextBox; // FileName;
12951         } else if((p = strstr(opt->name, " -path "))) {
12952             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12953             opt->textValue = p+7;
12954             opt->type = TextBox; // PathName;
12955         } else if(p = strstr(opt->name, " -check ")) {
12956             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12957             opt->value = (def != 0);
12958             opt->type = CheckBox;
12959         } else if(p = strstr(opt->name, " -combo ")) {
12960             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12961             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12962             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12963             opt->value = n = 0;
12964             while(q = StrStr(q, " /// ")) {
12965                 n++; *q = 0;    // count choices, and null-terminate each of them
12966                 q += 5;
12967                 if(*q == '*') { // remember default, which is marked with * prefix
12968                     q++;
12969                     opt->value = n;
12970                 }
12971                 cps->comboList[cps->comboCnt++] = q;
12972             }
12973             cps->comboList[cps->comboCnt++] = NULL;
12974             opt->max = n + 1;
12975             opt->type = ComboBox;
12976         } else if(p = strstr(opt->name, " -button")) {
12977             opt->type = Button;
12978         } else if(p = strstr(opt->name, " -save")) {
12979             opt->type = SaveButton;
12980         } else return FALSE;
12981         *p = 0; // terminate option name
12982         // now look if the command-line options define a setting for this engine option.
12983         if(cps->optionSettings && cps->optionSettings[0])
12984             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12985         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12986                 sprintf(buf, "option %s", p);
12987                 if(p = strstr(buf, ",")) *p = 0;
12988                 strcat(buf, "\n");
12989                 SendToProgram(buf, cps);
12990         }
12991         return TRUE;
12992 }
12993
12994 void
12995 FeatureDone(cps, val)
12996      ChessProgramState* cps;
12997      int val;
12998 {
12999   DelayedEventCallback cb = GetDelayedEvent();
13000   if ((cb == InitBackEnd3 && cps == &first) ||
13001       (cb == TwoMachinesEventIfReady && cps == &second)) {
13002     CancelDelayedEvent();
13003     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13004   }
13005   cps->initDone = val;
13006 }
13007
13008 /* Parse feature command from engine */
13009 void
13010 ParseFeatures(args, cps)
13011      char* args;
13012      ChessProgramState *cps;  
13013 {
13014   char *p = args;
13015   char *q;
13016   int val;
13017   char buf[MSG_SIZ];
13018
13019   for (;;) {
13020     while (*p == ' ') p++;
13021     if (*p == NULLCHAR) return;
13022
13023     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13024     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13025     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13026     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13027     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13028     if (BoolFeature(&p, "reuse", &val, cps)) {
13029       /* Engine can disable reuse, but can't enable it if user said no */
13030       if (!val) cps->reuse = FALSE;
13031       continue;
13032     }
13033     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13034     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13035       if (gameMode == TwoMachinesPlay) {
13036         DisplayTwoMachinesTitle();
13037       } else {
13038         DisplayTitle("");
13039       }
13040       continue;
13041     }
13042     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13043     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13044     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13045     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13046     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13047     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13048     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13049     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13050     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13051     if (IntFeature(&p, "done", &val, cps)) {
13052       FeatureDone(cps, val);
13053       continue;
13054     }
13055     /* Added by Tord: */
13056     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13057     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13058     /* End of additions by Tord */
13059
13060     /* [HGM] added features: */
13061     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13062     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13063     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13064     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13065     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13066     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13067     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13068         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13069             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13070             SendToProgram(buf, cps);
13071             continue;
13072         }
13073         if(cps->nrOptions >= MAX_OPTIONS) {
13074             cps->nrOptions--;
13075             sprintf(buf, "%s engine has too many options\n", cps->which);
13076             DisplayError(buf, 0);
13077         }
13078         continue;
13079     }
13080     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13081     /* End of additions by HGM */
13082
13083     /* unknown feature: complain and skip */
13084     q = p;
13085     while (*q && *q != '=') q++;
13086     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13087     SendToProgram(buf, cps);
13088     p = q;
13089     if (*p == '=') {
13090       p++;
13091       if (*p == '\"') {
13092         p++;
13093         while (*p && *p != '\"') p++;
13094         if (*p == '\"') p++;
13095       } else {
13096         while (*p && *p != ' ') p++;
13097       }
13098     }
13099   }
13100
13101 }
13102
13103 void
13104 PeriodicUpdatesEvent(newState)
13105      int newState;
13106 {
13107     if (newState == appData.periodicUpdates)
13108       return;
13109
13110     appData.periodicUpdates=newState;
13111
13112     /* Display type changes, so update it now */
13113 //    DisplayAnalysis();
13114
13115     /* Get the ball rolling again... */
13116     if (newState) {
13117         AnalysisPeriodicEvent(1);
13118         StartAnalysisClock();
13119     }
13120 }
13121
13122 void
13123 PonderNextMoveEvent(newState)
13124      int newState;
13125 {
13126     if (newState == appData.ponderNextMove) return;
13127     if (gameMode == EditPosition) EditPositionDone(TRUE);
13128     if (newState) {
13129         SendToProgram("hard\n", &first);
13130         if (gameMode == TwoMachinesPlay) {
13131             SendToProgram("hard\n", &second);
13132         }
13133     } else {
13134         SendToProgram("easy\n", &first);
13135         thinkOutput[0] = NULLCHAR;
13136         if (gameMode == TwoMachinesPlay) {
13137             SendToProgram("easy\n", &second);
13138         }
13139     }
13140     appData.ponderNextMove = newState;
13141 }
13142
13143 void
13144 NewSettingEvent(option, command, value)
13145      char *command;
13146      int option, value;
13147 {
13148     char buf[MSG_SIZ];
13149
13150     if (gameMode == EditPosition) EditPositionDone(TRUE);
13151     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13152     SendToProgram(buf, &first);
13153     if (gameMode == TwoMachinesPlay) {
13154         SendToProgram(buf, &second);
13155     }
13156 }
13157
13158 void
13159 ShowThinkingEvent()
13160 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13161 {
13162     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13163     int newState = appData.showThinking
13164         // [HGM] thinking: other features now need thinking output as well
13165         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13166     
13167     if (oldState == newState) return;
13168     oldState = newState;
13169     if (gameMode == EditPosition) EditPositionDone(TRUE);
13170     if (oldState) {
13171         SendToProgram("post\n", &first);
13172         if (gameMode == TwoMachinesPlay) {
13173             SendToProgram("post\n", &second);
13174         }
13175     } else {
13176         SendToProgram("nopost\n", &first);
13177         thinkOutput[0] = NULLCHAR;
13178         if (gameMode == TwoMachinesPlay) {
13179             SendToProgram("nopost\n", &second);
13180         }
13181     }
13182 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13183 }
13184
13185 void
13186 AskQuestionEvent(title, question, replyPrefix, which)
13187      char *title; char *question; char *replyPrefix; char *which;
13188 {
13189   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13190   if (pr == NoProc) return;
13191   AskQuestion(title, question, replyPrefix, pr);
13192 }
13193
13194 void
13195 DisplayMove(moveNumber)
13196      int moveNumber;
13197 {
13198     char message[MSG_SIZ];
13199     char res[MSG_SIZ];
13200     char cpThinkOutput[MSG_SIZ];
13201
13202     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13203     
13204     if (moveNumber == forwardMostMove - 1 || 
13205         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13206
13207         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13208
13209         if (strchr(cpThinkOutput, '\n')) {
13210             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13211         }
13212     } else {
13213         *cpThinkOutput = NULLCHAR;
13214     }
13215
13216     /* [AS] Hide thinking from human user */
13217     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13218         *cpThinkOutput = NULLCHAR;
13219         if( thinkOutput[0] != NULLCHAR ) {
13220             int i;
13221
13222             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13223                 cpThinkOutput[i] = '.';
13224             }
13225             cpThinkOutput[i] = NULLCHAR;
13226             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13227         }
13228     }
13229
13230     if (moveNumber == forwardMostMove - 1 &&
13231         gameInfo.resultDetails != NULL) {
13232         if (gameInfo.resultDetails[0] == NULLCHAR) {
13233             sprintf(res, " %s", PGNResult(gameInfo.result));
13234         } else {
13235             sprintf(res, " {%s} %s",
13236                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13237         }
13238     } else {
13239         res[0] = NULLCHAR;
13240     }
13241
13242     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13243         DisplayMessage(res, cpThinkOutput);
13244     } else {
13245         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13246                 WhiteOnMove(moveNumber) ? " " : ".. ",
13247                 parseList[moveNumber], res);
13248         DisplayMessage(message, cpThinkOutput);
13249     }
13250 }
13251
13252 void
13253 DisplayComment(moveNumber, text)
13254      int moveNumber;
13255      char *text;
13256 {
13257     char title[MSG_SIZ];
13258     char buf[8000]; // comment can be long!
13259     int score, depth;
13260     
13261     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13262       strcpy(title, "Comment");
13263     } else {
13264       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13265               WhiteOnMove(moveNumber) ? " " : ".. ",
13266               parseList[moveNumber]);
13267     }
13268     // [HGM] PV info: display PV info together with (or as) comment
13269     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13270       if(text == NULL) text = "";                                           
13271       score = pvInfoList[moveNumber].score;
13272       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13273               depth, (pvInfoList[moveNumber].time+50)/100, text);
13274       text = buf;
13275     }
13276     if (text != NULL && (appData.autoDisplayComment || commentUp))
13277         CommentPopUp(title, text);
13278 }
13279
13280 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13281  * might be busy thinking or pondering.  It can be omitted if your
13282  * gnuchess is configured to stop thinking immediately on any user
13283  * input.  However, that gnuchess feature depends on the FIONREAD
13284  * ioctl, which does not work properly on some flavors of Unix.
13285  */
13286 void
13287 Attention(cps)
13288      ChessProgramState *cps;
13289 {
13290 #if ATTENTION
13291     if (!cps->useSigint) return;
13292     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13293     switch (gameMode) {
13294       case MachinePlaysWhite:
13295       case MachinePlaysBlack:
13296       case TwoMachinesPlay:
13297       case IcsPlayingWhite:
13298       case IcsPlayingBlack:
13299       case AnalyzeMode:
13300       case AnalyzeFile:
13301         /* Skip if we know it isn't thinking */
13302         if (!cps->maybeThinking) return;
13303         if (appData.debugMode)
13304           fprintf(debugFP, "Interrupting %s\n", cps->which);
13305         InterruptChildProcess(cps->pr);
13306         cps->maybeThinking = FALSE;
13307         break;
13308       default:
13309         break;
13310     }
13311 #endif /*ATTENTION*/
13312 }
13313
13314 int
13315 CheckFlags()
13316 {
13317     if (whiteTimeRemaining <= 0) {
13318         if (!whiteFlag) {
13319             whiteFlag = TRUE;
13320             if (appData.icsActive) {
13321                 if (appData.autoCallFlag &&
13322                     gameMode == IcsPlayingBlack && !blackFlag) {
13323                   SendToICS(ics_prefix);
13324                   SendToICS("flag\n");
13325                 }
13326             } else {
13327                 if (blackFlag) {
13328                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13329                 } else {
13330                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13331                     if (appData.autoCallFlag) {
13332                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13333                         return TRUE;
13334                     }
13335                 }
13336             }
13337         }
13338     }
13339     if (blackTimeRemaining <= 0) {
13340         if (!blackFlag) {
13341             blackFlag = TRUE;
13342             if (appData.icsActive) {
13343                 if (appData.autoCallFlag &&
13344                     gameMode == IcsPlayingWhite && !whiteFlag) {
13345                   SendToICS(ics_prefix);
13346                   SendToICS("flag\n");
13347                 }
13348             } else {
13349                 if (whiteFlag) {
13350                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13351                 } else {
13352                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13353                     if (appData.autoCallFlag) {
13354                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13355                         return TRUE;
13356                     }
13357                 }
13358             }
13359         }
13360     }
13361     return FALSE;
13362 }
13363
13364 void
13365 CheckTimeControl()
13366 {
13367     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13368         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13369
13370     /*
13371      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13372      */
13373     if ( !WhiteOnMove(forwardMostMove) )
13374         /* White made time control */
13375         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13376         /* [HGM] time odds: correct new time quota for time odds! */
13377                                             / WhitePlayer()->timeOdds;
13378       else
13379         /* Black made time control */
13380         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13381                                             / WhitePlayer()->other->timeOdds;
13382 }
13383
13384 void
13385 DisplayBothClocks()
13386 {
13387     int wom = gameMode == EditPosition ?
13388       !blackPlaysFirst : WhiteOnMove(currentMove);
13389     DisplayWhiteClock(whiteTimeRemaining, wom);
13390     DisplayBlackClock(blackTimeRemaining, !wom);
13391 }
13392
13393
13394 /* Timekeeping seems to be a portability nightmare.  I think everyone
13395    has ftime(), but I'm really not sure, so I'm including some ifdefs
13396    to use other calls if you don't.  Clocks will be less accurate if
13397    you have neither ftime nor gettimeofday.
13398 */
13399
13400 /* VS 2008 requires the #include outside of the function */
13401 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13402 #include <sys/timeb.h>
13403 #endif
13404
13405 /* Get the current time as a TimeMark */
13406 void
13407 GetTimeMark(tm)
13408      TimeMark *tm;
13409 {
13410 #if HAVE_GETTIMEOFDAY
13411
13412     struct timeval timeVal;
13413     struct timezone timeZone;
13414
13415     gettimeofday(&timeVal, &timeZone);
13416     tm->sec = (long) timeVal.tv_sec; 
13417     tm->ms = (int) (timeVal.tv_usec / 1000L);
13418
13419 #else /*!HAVE_GETTIMEOFDAY*/
13420 #if HAVE_FTIME
13421
13422 // include <sys/timeb.h> / moved to just above start of function
13423     struct timeb timeB;
13424
13425     ftime(&timeB);
13426     tm->sec = (long) timeB.time;
13427     tm->ms = (int) timeB.millitm;
13428
13429 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13430     tm->sec = (long) time(NULL);
13431     tm->ms = 0;
13432 #endif
13433 #endif
13434 }
13435
13436 /* Return the difference in milliseconds between two
13437    time marks.  We assume the difference will fit in a long!
13438 */
13439 long
13440 SubtractTimeMarks(tm2, tm1)
13441      TimeMark *tm2, *tm1;
13442 {
13443     return 1000L*(tm2->sec - tm1->sec) +
13444            (long) (tm2->ms - tm1->ms);
13445 }
13446
13447
13448 /*
13449  * Code to manage the game clocks.
13450  *
13451  * In tournament play, black starts the clock and then white makes a move.
13452  * We give the human user a slight advantage if he is playing white---the
13453  * clocks don't run until he makes his first move, so it takes zero time.
13454  * Also, we don't account for network lag, so we could get out of sync
13455  * with GNU Chess's clock -- but then, referees are always right.  
13456  */
13457
13458 static TimeMark tickStartTM;
13459 static long intendedTickLength;
13460
13461 long
13462 NextTickLength(timeRemaining)
13463      long timeRemaining;
13464 {
13465     long nominalTickLength, nextTickLength;
13466
13467     if (timeRemaining > 0L && timeRemaining <= 10000L)
13468       nominalTickLength = 100L;
13469     else
13470       nominalTickLength = 1000L;
13471     nextTickLength = timeRemaining % nominalTickLength;
13472     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13473
13474     return nextTickLength;
13475 }
13476
13477 /* Adjust clock one minute up or down */
13478 void
13479 AdjustClock(Boolean which, int dir)
13480 {
13481     if(which) blackTimeRemaining += 60000*dir;
13482     else      whiteTimeRemaining += 60000*dir;
13483     DisplayBothClocks();
13484 }
13485
13486 /* Stop clocks and reset to a fresh time control */
13487 void
13488 ResetClocks() 
13489 {
13490     (void) StopClockTimer();
13491     if (appData.icsActive) {
13492         whiteTimeRemaining = blackTimeRemaining = 0;
13493     } else if (searchTime) {
13494         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13495         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13496     } else { /* [HGM] correct new time quote for time odds */
13497         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13498         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13499     }
13500     if (whiteFlag || blackFlag) {
13501         DisplayTitle("");
13502         whiteFlag = blackFlag = FALSE;
13503     }
13504     DisplayBothClocks();
13505 }
13506
13507 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13508
13509 /* Decrement running clock by amount of time that has passed */
13510 void
13511 DecrementClocks()
13512 {
13513     long timeRemaining;
13514     long lastTickLength, fudge;
13515     TimeMark now;
13516
13517     if (!appData.clockMode) return;
13518     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13519         
13520     GetTimeMark(&now);
13521
13522     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13523
13524     /* Fudge if we woke up a little too soon */
13525     fudge = intendedTickLength - lastTickLength;
13526     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13527
13528     if (WhiteOnMove(forwardMostMove)) {
13529         if(whiteNPS >= 0) lastTickLength = 0;
13530         timeRemaining = whiteTimeRemaining -= lastTickLength;
13531         DisplayWhiteClock(whiteTimeRemaining - fudge,
13532                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13533     } else {
13534         if(blackNPS >= 0) lastTickLength = 0;
13535         timeRemaining = blackTimeRemaining -= lastTickLength;
13536         DisplayBlackClock(blackTimeRemaining - fudge,
13537                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13538     }
13539
13540     if (CheckFlags()) return;
13541         
13542     tickStartTM = now;
13543     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13544     StartClockTimer(intendedTickLength);
13545
13546     /* if the time remaining has fallen below the alarm threshold, sound the
13547      * alarm. if the alarm has sounded and (due to a takeback or time control
13548      * with increment) the time remaining has increased to a level above the
13549      * threshold, reset the alarm so it can sound again. 
13550      */
13551     
13552     if (appData.icsActive && appData.icsAlarm) {
13553
13554         /* make sure we are dealing with the user's clock */
13555         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13556                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13557            )) return;
13558
13559         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13560             alarmSounded = FALSE;
13561         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13562             PlayAlarmSound();
13563             alarmSounded = TRUE;
13564         }
13565     }
13566 }
13567
13568
13569 /* A player has just moved, so stop the previously running
13570    clock and (if in clock mode) start the other one.
13571    We redisplay both clocks in case we're in ICS mode, because
13572    ICS gives us an update to both clocks after every move.
13573    Note that this routine is called *after* forwardMostMove
13574    is updated, so the last fractional tick must be subtracted
13575    from the color that is *not* on move now.
13576 */
13577 void
13578 SwitchClocks()
13579 {
13580     long lastTickLength;
13581     TimeMark now;
13582     int flagged = FALSE;
13583
13584     GetTimeMark(&now);
13585
13586     if (StopClockTimer() && appData.clockMode) {
13587         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13588         if (WhiteOnMove(forwardMostMove)) {
13589             if(blackNPS >= 0) lastTickLength = 0;
13590             blackTimeRemaining -= lastTickLength;
13591            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13592 //         if(pvInfoList[forwardMostMove-1].time == -1)
13593                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13594                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13595         } else {
13596            if(whiteNPS >= 0) lastTickLength = 0;
13597            whiteTimeRemaining -= lastTickLength;
13598            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13599 //         if(pvInfoList[forwardMostMove-1].time == -1)
13600                  pvInfoList[forwardMostMove-1].time = 
13601                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13602         }
13603         flagged = CheckFlags();
13604     }
13605     CheckTimeControl();
13606
13607     if (flagged || !appData.clockMode) return;
13608
13609     switch (gameMode) {
13610       case MachinePlaysBlack:
13611       case MachinePlaysWhite:
13612       case BeginningOfGame:
13613         if (pausing) return;
13614         break;
13615
13616       case EditGame:
13617       case PlayFromGameFile:
13618       case IcsExamining:
13619         return;
13620
13621       default:
13622         break;
13623     }
13624
13625     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13626         if(WhiteOnMove(forwardMostMove))
13627              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13628         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13629     }
13630
13631     tickStartTM = now;
13632     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13633       whiteTimeRemaining : blackTimeRemaining);
13634     StartClockTimer(intendedTickLength);
13635 }
13636         
13637
13638 /* Stop both clocks */
13639 void
13640 StopClocks()
13641 {       
13642     long lastTickLength;
13643     TimeMark now;
13644
13645     if (!StopClockTimer()) return;
13646     if (!appData.clockMode) return;
13647
13648     GetTimeMark(&now);
13649
13650     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13651     if (WhiteOnMove(forwardMostMove)) {
13652         if(whiteNPS >= 0) lastTickLength = 0;
13653         whiteTimeRemaining -= lastTickLength;
13654         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13655     } else {
13656         if(blackNPS >= 0) lastTickLength = 0;
13657         blackTimeRemaining -= lastTickLength;
13658         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13659     }
13660     CheckFlags();
13661 }
13662         
13663 /* Start clock of player on move.  Time may have been reset, so
13664    if clock is already running, stop and restart it. */
13665 void
13666 StartClocks()
13667 {
13668     (void) StopClockTimer(); /* in case it was running already */
13669     DisplayBothClocks();
13670     if (CheckFlags()) return;
13671
13672     if (!appData.clockMode) return;
13673     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13674
13675     GetTimeMark(&tickStartTM);
13676     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13677       whiteTimeRemaining : blackTimeRemaining);
13678
13679    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13680     whiteNPS = blackNPS = -1; 
13681     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13682        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13683         whiteNPS = first.nps;
13684     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13685        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13686         blackNPS = first.nps;
13687     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13688         whiteNPS = second.nps;
13689     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13690         blackNPS = second.nps;
13691     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13692
13693     StartClockTimer(intendedTickLength);
13694 }
13695
13696 char *
13697 TimeString(ms)
13698      long ms;
13699 {
13700     long second, minute, hour, day;
13701     char *sign = "";
13702     static char buf[32];
13703     
13704     if (ms > 0 && ms <= 9900) {
13705       /* convert milliseconds to tenths, rounding up */
13706       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13707
13708       sprintf(buf, " %03.1f ", tenths/10.0);
13709       return buf;
13710     }
13711
13712     /* convert milliseconds to seconds, rounding up */
13713     /* use floating point to avoid strangeness of integer division
13714        with negative dividends on many machines */
13715     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13716
13717     if (second < 0) {
13718         sign = "-";
13719         second = -second;
13720     }
13721     
13722     day = second / (60 * 60 * 24);
13723     second = second % (60 * 60 * 24);
13724     hour = second / (60 * 60);
13725     second = second % (60 * 60);
13726     minute = second / 60;
13727     second = second % 60;
13728     
13729     if (day > 0)
13730       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13731               sign, day, hour, minute, second);
13732     else if (hour > 0)
13733       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13734     else
13735       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13736     
13737     return buf;
13738 }
13739
13740
13741 /*
13742  * This is necessary because some C libraries aren't ANSI C compliant yet.
13743  */
13744 char *
13745 StrStr(string, match)
13746      char *string, *match;
13747 {
13748     int i, length;
13749     
13750     length = strlen(match);
13751     
13752     for (i = strlen(string) - length; i >= 0; i--, string++)
13753       if (!strncmp(match, string, length))
13754         return string;
13755     
13756     return NULL;
13757 }
13758
13759 char *
13760 StrCaseStr(string, match)
13761      char *string, *match;
13762 {
13763     int i, j, length;
13764     
13765     length = strlen(match);
13766     
13767     for (i = strlen(string) - length; i >= 0; i--, string++) {
13768         for (j = 0; j < length; j++) {
13769             if (ToLower(match[j]) != ToLower(string[j]))
13770               break;
13771         }
13772         if (j == length) return string;
13773     }
13774
13775     return NULL;
13776 }
13777
13778 #ifndef _amigados
13779 int
13780 StrCaseCmp(s1, s2)
13781      char *s1, *s2;
13782 {
13783     char c1, c2;
13784     
13785     for (;;) {
13786         c1 = ToLower(*s1++);
13787         c2 = ToLower(*s2++);
13788         if (c1 > c2) return 1;
13789         if (c1 < c2) return -1;
13790         if (c1 == NULLCHAR) return 0;
13791     }
13792 }
13793
13794
13795 int
13796 ToLower(c)
13797      int c;
13798 {
13799     return isupper(c) ? tolower(c) : c;
13800 }
13801
13802
13803 int
13804 ToUpper(c)
13805      int c;
13806 {
13807     return islower(c) ? toupper(c) : c;
13808 }
13809 #endif /* !_amigados    */
13810
13811 char *
13812 StrSave(s)
13813      char *s;
13814 {
13815     char *ret;
13816
13817     if ((ret = (char *) malloc(strlen(s) + 1))) {
13818         strcpy(ret, s);
13819     }
13820     return ret;
13821 }
13822
13823 char *
13824 StrSavePtr(s, savePtr)
13825      char *s, **savePtr;
13826 {
13827     if (*savePtr) {
13828         free(*savePtr);
13829     }
13830     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13831         strcpy(*savePtr, s);
13832     }
13833     return(*savePtr);
13834 }
13835
13836 char *
13837 PGNDate()
13838 {
13839     time_t clock;
13840     struct tm *tm;
13841     char buf[MSG_SIZ];
13842
13843     clock = time((time_t *)NULL);
13844     tm = localtime(&clock);
13845     sprintf(buf, "%04d.%02d.%02d",
13846             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13847     return StrSave(buf);
13848 }
13849
13850
13851 char *
13852 PositionToFEN(move, overrideCastling)
13853      int move;
13854      char *overrideCastling;
13855 {
13856     int i, j, fromX, fromY, toX, toY;
13857     int whiteToPlay;
13858     char buf[128];
13859     char *p, *q;
13860     int emptycount;
13861     ChessSquare piece;
13862
13863     whiteToPlay = (gameMode == EditPosition) ?
13864       !blackPlaysFirst : (move % 2 == 0);
13865     p = buf;
13866
13867     /* Piece placement data */
13868     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13869         emptycount = 0;
13870         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13871             if (boards[move][i][j] == EmptySquare) {
13872                 emptycount++;
13873             } else { ChessSquare piece = boards[move][i][j];
13874                 if (emptycount > 0) {
13875                     if(emptycount<10) /* [HGM] can be >= 10 */
13876                         *p++ = '0' + emptycount;
13877                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13878                     emptycount = 0;
13879                 }
13880                 if(PieceToChar(piece) == '+') {
13881                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13882                     *p++ = '+';
13883                     piece = (ChessSquare)(DEMOTED piece);
13884                 } 
13885                 *p++ = PieceToChar(piece);
13886                 if(p[-1] == '~') {
13887                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13888                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13889                     *p++ = '~';
13890                 }
13891             }
13892         }
13893         if (emptycount > 0) {
13894             if(emptycount<10) /* [HGM] can be >= 10 */
13895                 *p++ = '0' + emptycount;
13896             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13897             emptycount = 0;
13898         }
13899         *p++ = '/';
13900     }
13901     *(p - 1) = ' ';
13902
13903     /* [HGM] print Crazyhouse or Shogi holdings */
13904     if( gameInfo.holdingsWidth ) {
13905         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13906         q = p;
13907         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13908             piece = boards[move][i][BOARD_WIDTH-1];
13909             if( piece != EmptySquare )
13910               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13911                   *p++ = PieceToChar(piece);
13912         }
13913         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13914             piece = boards[move][BOARD_HEIGHT-i-1][0];
13915             if( piece != EmptySquare )
13916               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13917                   *p++ = PieceToChar(piece);
13918         }
13919
13920         if( q == p ) *p++ = '-';
13921         *p++ = ']';
13922         *p++ = ' ';
13923     }
13924
13925     /* Active color */
13926     *p++ = whiteToPlay ? 'w' : 'b';
13927     *p++ = ' ';
13928
13929   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13930     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13931   } else {
13932   if(nrCastlingRights) {
13933      q = p;
13934      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13935        /* [HGM] write directly from rights */
13936            if(boards[move][CASTLING][2] != NoRights &&
13937               boards[move][CASTLING][0] != NoRights   )
13938                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13939            if(boards[move][CASTLING][2] != NoRights &&
13940               boards[move][CASTLING][1] != NoRights   )
13941                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13942            if(boards[move][CASTLING][5] != NoRights &&
13943               boards[move][CASTLING][3] != NoRights   )
13944                 *p++ = boards[move][CASTLING][3] + AAA;
13945            if(boards[move][CASTLING][5] != NoRights &&
13946               boards[move][CASTLING][4] != NoRights   )
13947                 *p++ = boards[move][CASTLING][4] + AAA;
13948      } else {
13949
13950         /* [HGM] write true castling rights */
13951         if( nrCastlingRights == 6 ) {
13952             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13953                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13954             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13955                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13956             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13957                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13958             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13959                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13960         }
13961      }
13962      if (q == p) *p++ = '-'; /* No castling rights */
13963      *p++ = ' ';
13964   }
13965
13966   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13967      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13968     /* En passant target square */
13969     if (move > backwardMostMove) {
13970         fromX = moveList[move - 1][0] - AAA;
13971         fromY = moveList[move - 1][1] - ONE;
13972         toX = moveList[move - 1][2] - AAA;
13973         toY = moveList[move - 1][3] - ONE;
13974         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13975             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13976             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13977             fromX == toX) {
13978             /* 2-square pawn move just happened */
13979             *p++ = toX + AAA;
13980             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13981         } else {
13982             *p++ = '-';
13983         }
13984     } else if(move == backwardMostMove) {
13985         // [HGM] perhaps we should always do it like this, and forget the above?
13986         if((signed char)boards[move][EP_STATUS] >= 0) {
13987             *p++ = boards[move][EP_STATUS] + AAA;
13988             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13989         } else {
13990             *p++ = '-';
13991         }
13992     } else {
13993         *p++ = '-';
13994     }
13995     *p++ = ' ';
13996   }
13997   }
13998
13999     /* [HGM] find reversible plies */
14000     {   int i = 0, j=move;
14001
14002         if (appData.debugMode) { int k;
14003             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14004             for(k=backwardMostMove; k<=forwardMostMove; k++)
14005                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14006
14007         }
14008
14009         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14010         if( j == backwardMostMove ) i += initialRulePlies;
14011         sprintf(p, "%d ", i);
14012         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14013     }
14014     /* Fullmove number */
14015     sprintf(p, "%d", (move / 2) + 1);
14016     
14017     return StrSave(buf);
14018 }
14019
14020 Boolean
14021 ParseFEN(board, blackPlaysFirst, fen)
14022     Board board;
14023      int *blackPlaysFirst;
14024      char *fen;
14025 {
14026     int i, j;
14027     char *p;
14028     int emptycount;
14029     ChessSquare piece;
14030
14031     p = fen;
14032
14033     /* [HGM] by default clear Crazyhouse holdings, if present */
14034     if(gameInfo.holdingsWidth) {
14035        for(i=0; i<BOARD_HEIGHT; i++) {
14036            board[i][0]             = EmptySquare; /* black holdings */
14037            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14038            board[i][1]             = (ChessSquare) 0; /* black counts */
14039            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14040        }
14041     }
14042
14043     /* Piece placement data */
14044     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14045         j = 0;
14046         for (;;) {
14047             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14048                 if (*p == '/') p++;
14049                 emptycount = gameInfo.boardWidth - j;
14050                 while (emptycount--)
14051                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14052                 break;
14053 #if(BOARD_FILES >= 10)
14054             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14055                 p++; emptycount=10;
14056                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14057                 while (emptycount--)
14058                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14059 #endif
14060             } else if (isdigit(*p)) {
14061                 emptycount = *p++ - '0';
14062                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14063                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14064                 while (emptycount--)
14065                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14066             } else if (*p == '+' || isalpha(*p)) {
14067                 if (j >= gameInfo.boardWidth) return FALSE;
14068                 if(*p=='+') {
14069                     piece = CharToPiece(*++p);
14070                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14071                     piece = (ChessSquare) (PROMOTED piece ); p++;
14072                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14073                 } else piece = CharToPiece(*p++);
14074
14075                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14076                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14077                     piece = (ChessSquare) (PROMOTED piece);
14078                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14079                     p++;
14080                 }
14081                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14082             } else {
14083                 return FALSE;
14084             }
14085         }
14086     }
14087     while (*p == '/' || *p == ' ') p++;
14088
14089     /* [HGM] look for Crazyhouse holdings here */
14090     while(*p==' ') p++;
14091     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14092         if(*p == '[') p++;
14093         if(*p == '-' ) *p++; /* empty holdings */ else {
14094             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14095             /* if we would allow FEN reading to set board size, we would   */
14096             /* have to add holdings and shift the board read so far here   */
14097             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14098                 *p++;
14099                 if((int) piece >= (int) BlackPawn ) {
14100                     i = (int)piece - (int)BlackPawn;
14101                     i = PieceToNumber((ChessSquare)i);
14102                     if( i >= gameInfo.holdingsSize ) return FALSE;
14103                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14104                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14105                 } else {
14106                     i = (int)piece - (int)WhitePawn;
14107                     i = PieceToNumber((ChessSquare)i);
14108                     if( i >= gameInfo.holdingsSize ) return FALSE;
14109                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14110                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14111                 }
14112             }
14113         }
14114         if(*p == ']') *p++;
14115     }
14116
14117     while(*p == ' ') p++;
14118
14119     /* Active color */
14120     switch (*p++) {
14121       case 'w':
14122         *blackPlaysFirst = FALSE;
14123         break;
14124       case 'b': 
14125         *blackPlaysFirst = TRUE;
14126         break;
14127       default:
14128         return FALSE;
14129     }
14130
14131     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14132     /* return the extra info in global variiables             */
14133
14134     /* set defaults in case FEN is incomplete */
14135     board[EP_STATUS] = EP_UNKNOWN;
14136     for(i=0; i<nrCastlingRights; i++ ) {
14137         board[CASTLING][i] =
14138             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14139     }   /* assume possible unless obviously impossible */
14140     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14141     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14142     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14143                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14144     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14145     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14146     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14147                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14148     FENrulePlies = 0;
14149
14150     while(*p==' ') p++;
14151     if(nrCastlingRights) {
14152       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14153           /* castling indicator present, so default becomes no castlings */
14154           for(i=0; i<nrCastlingRights; i++ ) {
14155                  board[CASTLING][i] = NoRights;
14156           }
14157       }
14158       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14159              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14160              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14161              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14162         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14163
14164         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14165             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14166             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14167         }
14168         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14169             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14170         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14171                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14172         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14173                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14174         switch(c) {
14175           case'K':
14176               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14177               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14178               board[CASTLING][2] = whiteKingFile;
14179               break;
14180           case'Q':
14181               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14182               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14183               board[CASTLING][2] = whiteKingFile;
14184               break;
14185           case'k':
14186               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14187               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14188               board[CASTLING][5] = blackKingFile;
14189               break;
14190           case'q':
14191               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14192               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14193               board[CASTLING][5] = blackKingFile;
14194           case '-':
14195               break;
14196           default: /* FRC castlings */
14197               if(c >= 'a') { /* black rights */
14198                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14199                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14200                   if(i == BOARD_RGHT) break;
14201                   board[CASTLING][5] = i;
14202                   c -= AAA;
14203                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14204                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14205                   if(c > i)
14206                       board[CASTLING][3] = c;
14207                   else
14208                       board[CASTLING][4] = c;
14209               } else { /* white rights */
14210                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14211                     if(board[0][i] == WhiteKing) break;
14212                   if(i == BOARD_RGHT) break;
14213                   board[CASTLING][2] = i;
14214                   c -= AAA - 'a' + 'A';
14215                   if(board[0][c] >= WhiteKing) break;
14216                   if(c > i)
14217                       board[CASTLING][0] = c;
14218                   else
14219                       board[CASTLING][1] = c;
14220               }
14221         }
14222       }
14223     if (appData.debugMode) {
14224         fprintf(debugFP, "FEN castling rights:");
14225         for(i=0; i<nrCastlingRights; i++)
14226         fprintf(debugFP, " %d", board[CASTLING][i]);
14227         fprintf(debugFP, "\n");
14228     }
14229
14230       while(*p==' ') p++;
14231     }
14232
14233     /* read e.p. field in games that know e.p. capture */
14234     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14235        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14236       if(*p=='-') {
14237         p++; board[EP_STATUS] = EP_NONE;
14238       } else {
14239          char c = *p++ - AAA;
14240
14241          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14242          if(*p >= '0' && *p <='9') *p++;
14243          board[EP_STATUS] = c;
14244       }
14245     }
14246
14247
14248     if(sscanf(p, "%d", &i) == 1) {
14249         FENrulePlies = i; /* 50-move ply counter */
14250         /* (The move number is still ignored)    */
14251     }
14252
14253     return TRUE;
14254 }
14255       
14256 void
14257 EditPositionPasteFEN(char *fen)
14258 {
14259   if (fen != NULL) {
14260     Board initial_position;
14261
14262     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14263       DisplayError(_("Bad FEN position in clipboard"), 0);
14264       return ;
14265     } else {
14266       int savedBlackPlaysFirst = blackPlaysFirst;
14267       EditPositionEvent();
14268       blackPlaysFirst = savedBlackPlaysFirst;
14269       CopyBoard(boards[0], initial_position);
14270       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14271       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14272       DisplayBothClocks();
14273       DrawPosition(FALSE, boards[currentMove]);
14274     }
14275   }
14276 }
14277
14278 static char cseq[12] = "\\   ";
14279
14280 Boolean set_cont_sequence(char *new_seq)
14281 {
14282     int len;
14283     Boolean ret;
14284
14285     // handle bad attempts to set the sequence
14286         if (!new_seq)
14287                 return 0; // acceptable error - no debug
14288
14289     len = strlen(new_seq);
14290     ret = (len > 0) && (len < sizeof(cseq));
14291     if (ret)
14292         strcpy(cseq, new_seq);
14293     else if (appData.debugMode)
14294         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14295     return ret;
14296 }
14297
14298 /*
14299     reformat a source message so words don't cross the width boundary.  internal
14300     newlines are not removed.  returns the wrapped size (no null character unless
14301     included in source message).  If dest is NULL, only calculate the size required
14302     for the dest buffer.  lp argument indicats line position upon entry, and it's
14303     passed back upon exit.
14304 */
14305 int wrap(char *dest, char *src, int count, int width, int *lp)
14306 {
14307     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14308
14309     cseq_len = strlen(cseq);
14310     old_line = line = *lp;
14311     ansi = len = clen = 0;
14312
14313     for (i=0; i < count; i++)
14314     {
14315         if (src[i] == '\033')
14316             ansi = 1;
14317
14318         // if we hit the width, back up
14319         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14320         {
14321             // store i & len in case the word is too long
14322             old_i = i, old_len = len;
14323
14324             // find the end of the last word
14325             while (i && src[i] != ' ' && src[i] != '\n')
14326             {
14327                 i--;
14328                 len--;
14329             }
14330
14331             // word too long?  restore i & len before splitting it
14332             if ((old_i-i+clen) >= width)
14333             {
14334                 i = old_i;
14335                 len = old_len;
14336             }
14337
14338             // extra space?
14339             if (i && src[i-1] == ' ')
14340                 len--;
14341
14342             if (src[i] != ' ' && src[i] != '\n')
14343             {
14344                 i--;
14345                 if (len)
14346                     len--;
14347             }
14348
14349             // now append the newline and continuation sequence
14350             if (dest)
14351                 dest[len] = '\n';
14352             len++;
14353             if (dest)
14354                 strncpy(dest+len, cseq, cseq_len);
14355             len += cseq_len;
14356             line = cseq_len;
14357             clen = cseq_len;
14358             continue;
14359         }
14360
14361         if (dest)
14362             dest[len] = src[i];
14363         len++;
14364         if (!ansi)
14365             line++;
14366         if (src[i] == '\n')
14367             line = 0;
14368         if (src[i] == 'm')
14369             ansi = 0;
14370     }
14371     if (dest && appData.debugMode)
14372     {
14373         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14374             count, width, line, len, *lp);
14375         show_bytes(debugFP, src, count);
14376         fprintf(debugFP, "\ndest: ");
14377         show_bytes(debugFP, dest, len);
14378         fprintf(debugFP, "\n");
14379     }
14380     *lp = dest ? line : old_line;
14381
14382     return len;
14383 }
14384
14385 // [HGM] vari: routines for shelving variations
14386
14387 void 
14388 PushTail(int firstMove, int lastMove)
14389 {
14390         int i, j, nrMoves = lastMove - firstMove;
14391
14392         if(appData.icsActive) { // only in local mode
14393                 forwardMostMove = currentMove; // mimic old ICS behavior
14394                 return;
14395         }
14396         if(storedGames >= MAX_VARIATIONS-1) return;
14397
14398         // push current tail of game on stack
14399         savedResult[storedGames] = gameInfo.result;
14400         savedDetails[storedGames] = gameInfo.resultDetails;
14401         gameInfo.resultDetails = NULL;
14402         savedFirst[storedGames] = firstMove;
14403         savedLast [storedGames] = lastMove;
14404         savedFramePtr[storedGames] = framePtr;
14405         framePtr -= nrMoves; // reserve space for the boards
14406         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14407             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14408             for(j=0; j<MOVE_LEN; j++)
14409                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14410             for(j=0; j<2*MOVE_LEN; j++)
14411                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14412             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14413             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14414             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14415             pvInfoList[firstMove+i-1].depth = 0;
14416             commentList[framePtr+i] = commentList[firstMove+i];
14417             commentList[firstMove+i] = NULL;
14418         }
14419
14420         storedGames++;
14421         forwardMostMove = currentMove; // truncte game so we can start variation
14422         if(storedGames == 1) GreyRevert(FALSE);
14423 }
14424
14425 Boolean
14426 PopTail(Boolean annotate)
14427 {
14428         int i, j, nrMoves;
14429         char buf[8000], moveBuf[20];
14430
14431         if(appData.icsActive) return FALSE; // only in local mode
14432         if(!storedGames) return FALSE; // sanity
14433
14434         storedGames--;
14435         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14436         nrMoves = savedLast[storedGames] - currentMove;
14437         if(annotate) {
14438                 int cnt = 10;
14439                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14440                 else strcpy(buf, "(");
14441                 for(i=currentMove; i<forwardMostMove; i++) {
14442                         if(WhiteOnMove(i))
14443                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14444                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14445                         strcat(buf, moveBuf);
14446                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14447                 }
14448                 strcat(buf, ")");
14449         }
14450         for(i=1; i<nrMoves; i++) { // copy last variation back
14451             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14452             for(j=0; j<MOVE_LEN; j++)
14453                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14454             for(j=0; j<2*MOVE_LEN; j++)
14455                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14456             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14457             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14458             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14459             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14460             commentList[currentMove+i] = commentList[framePtr+i];
14461             commentList[framePtr+i] = NULL;
14462         }
14463         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14464         framePtr = savedFramePtr[storedGames];
14465         gameInfo.result = savedResult[storedGames];
14466         if(gameInfo.resultDetails != NULL) {
14467             free(gameInfo.resultDetails);
14468       }
14469         gameInfo.resultDetails = savedDetails[storedGames];
14470         forwardMostMove = currentMove + nrMoves;
14471         if(storedGames == 0) GreyRevert(TRUE);
14472         return TRUE;
14473 }
14474
14475 void 
14476 CleanupTail()
14477 {       // remove all shelved variations
14478         int i;
14479         for(i=0; i<storedGames; i++) {
14480             if(savedDetails[i])
14481                 free(savedDetails[i]);
14482             savedDetails[i] = NULL;
14483         }
14484         for(i=framePtr; i<MAX_MOVES; i++) {
14485                 if(commentList[i]) free(commentList[i]);
14486                 commentList[i] = NULL;
14487         }
14488         framePtr = MAX_MOVES-1;
14489         storedGames = 0;
14490 }