Make WinBoard makefiles use parser.c in XBoard directory
[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;
2172         leftover_start = 0;
2173         
2174         i = 0;
2175         while (i < buf_len) {
2176             /* Deal with part of the TELNET option negotiation
2177                protocol.  We refuse to do anything beyond the
2178                defaults, except that we allow the WILL ECHO option,
2179                which ICS uses to turn off password echoing when we are
2180                directly connected to it.  We reject this option
2181                if localLineEditing mode is on (always on in xboard)
2182                and we are talking to port 23, which might be a real
2183                telnet server that will try to keep WILL ECHO on permanently.
2184              */
2185             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2186                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2187                 unsigned char option;
2188                 oldi = i;
2189                 switch ((unsigned char) buf[++i]) {
2190                   case TN_WILL:
2191                     if (appData.debugMode)
2192                       fprintf(debugFP, "\n<WILL ");
2193                     switch (option = (unsigned char) buf[++i]) {
2194                       case TN_ECHO:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "ECHO ");
2197                         /* Reply only if this is a change, according
2198                            to the protocol rules. */
2199                         if (remoteEchoOption) break;
2200                         if (appData.localLineEditing &&
2201                             atoi(appData.icsPort) == TN_PORT) {
2202                             TelnetRequest(TN_DONT, TN_ECHO);
2203                         } else {
2204                             EchoOff();
2205                             TelnetRequest(TN_DO, TN_ECHO);
2206                             remoteEchoOption = TRUE;
2207                         }
2208                         break;
2209                       default:
2210                         if (appData.debugMode)
2211                           fprintf(debugFP, "%d ", option);
2212                         /* Whatever this is, we don't want it. */
2213                         TelnetRequest(TN_DONT, option);
2214                         break;
2215                     }
2216                     break;
2217                   case TN_WONT:
2218                     if (appData.debugMode)
2219                       fprintf(debugFP, "\n<WONT ");
2220                     switch (option = (unsigned char) buf[++i]) {
2221                       case TN_ECHO:
2222                         if (appData.debugMode)
2223                           fprintf(debugFP, "ECHO ");
2224                         /* Reply only if this is a change, according
2225                            to the protocol rules. */
2226                         if (!remoteEchoOption) break;
2227                         EchoOn();
2228                         TelnetRequest(TN_DONT, TN_ECHO);
2229                         remoteEchoOption = FALSE;
2230                         break;
2231                       default:
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", (unsigned char) option);
2234                         /* Whatever this is, it must already be turned
2235                            off, because we never agree to turn on
2236                            anything non-default, so according to the
2237                            protocol rules, we don't reply. */
2238                         break;
2239                     }
2240                     break;
2241                   case TN_DO:
2242                     if (appData.debugMode)
2243                       fprintf(debugFP, "\n<DO ");
2244                     switch (option = (unsigned char) buf[++i]) {
2245                       default:
2246                         /* Whatever this is, we refuse to do it. */
2247                         if (appData.debugMode)
2248                           fprintf(debugFP, "%d ", option);
2249                         TelnetRequest(TN_WONT, option);
2250                         break;
2251                     }
2252                     break;
2253                   case TN_DONT:
2254                     if (appData.debugMode)
2255                       fprintf(debugFP, "\n<DONT ");
2256                     switch (option = (unsigned char) buf[++i]) {
2257                       default:
2258                         if (appData.debugMode)
2259                           fprintf(debugFP, "%d ", option);
2260                         /* Whatever this is, we are already not doing
2261                            it, because we never agree to do anything
2262                            non-default, so according to the protocol
2263                            rules, we don't reply. */
2264                         break;
2265                     }
2266                     break;
2267                   case TN_IAC:
2268                     if (appData.debugMode)
2269                       fprintf(debugFP, "\n<IAC ");
2270                     /* Doubled IAC; pass it through */
2271                     i--;
2272                     break;
2273                   default:
2274                     if (appData.debugMode)
2275                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2276                     /* Drop all other telnet commands on the floor */
2277                     break;
2278                 }
2279                 if (oldi > next_out)
2280                   SendToPlayer(&buf[next_out], oldi - next_out);
2281                 if (++i > next_out)
2282                   next_out = i;
2283                 continue;
2284             }
2285                 
2286             /* OK, this at least will *usually* work */
2287             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2288                 loggedOn = TRUE;
2289             }
2290             
2291             if (loggedOn && !intfSet) {
2292                 if (ics_type == ICS_ICC) {
2293                   sprintf(str,
2294                           "/set-quietly interface %s\n/set-quietly style 12\n",
2295                           programVersion);
2296                 } else if (ics_type == ICS_CHESSNET) {
2297                   sprintf(str, "/style 12\n");
2298                 } else {
2299                   strcpy(str, "alias $ @\n$set interface ");
2300                   strcat(str, programVersion);
2301                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2302 #ifdef WIN32
2303                   strcat(str, "$iset nohighlight 1\n");
2304 #endif
2305                   strcat(str, "$iset lock 1\n$style 12\n");
2306                 }
2307                 SendToICS(str);
2308                 NotifyFrontendLogin();
2309                 intfSet = TRUE;
2310             }
2311
2312             if (started == STARTED_COMMENT) {
2313                 /* Accumulate characters in comment */
2314                 parse[parse_pos++] = buf[i];
2315                 if (buf[i] == '\n') {
2316                     parse[parse_pos] = NULLCHAR;
2317                     if(chattingPartner>=0) {
2318                         char mess[MSG_SIZ];
2319                         sprintf(mess, "%s%s", talker, parse);
2320                         OutputChatMessage(chattingPartner, mess);
2321                         chattingPartner = -1;
2322                     } else
2323                     if(!suppressKibitz) // [HGM] kibitz
2324                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2325                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2326                         int nrDigit = 0, nrAlph = 0, i;
2327                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2328                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2329                         parse[parse_pos] = NULLCHAR;
2330                         // try to be smart: if it does not look like search info, it should go to
2331                         // ICS interaction window after all, not to engine-output window.
2332                         for(i=0; i<parse_pos; i++) { // count letters and digits
2333                             nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2334                             nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');
2335                             nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');
2336                         }
2337                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2338                             int depth=0; float score;
2339                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2340                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2341                                 pvInfoList[forwardMostMove-1].depth = depth;
2342                                 pvInfoList[forwardMostMove-1].score = 100*score;
2343                             }
2344                             OutputKibitz(suppressKibitz, parse);
2345                         } else {
2346                             char tmp[MSG_SIZ];
2347                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2348                             SendToPlayer(tmp, strlen(tmp));
2349                         }
2350                     }
2351                     started = STARTED_NONE;
2352                 } else {
2353                     /* Don't match patterns against characters in chatter */
2354                     i++;
2355                     continue;
2356                 }
2357             }
2358             if (started == STARTED_CHATTER) {
2359                 if (buf[i] != '\n') {
2360                     /* Don't match patterns against characters in chatter */
2361                     i++;
2362                     continue;
2363                 }
2364                 started = STARTED_NONE;
2365             }
2366
2367             /* Kludge to deal with rcmd protocol */
2368             if (firstTime && looking_at(buf, &i, "\001*")) {
2369                 DisplayFatalError(&buf[1], 0, 1);
2370                 continue;
2371             } else {
2372                 firstTime = FALSE;
2373             }
2374
2375             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2376                 ics_type = ICS_ICC;
2377                 ics_prefix = "/";
2378                 if (appData.debugMode)
2379                   fprintf(debugFP, "ics_type %d\n", ics_type);
2380                 continue;
2381             }
2382             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2383                 ics_type = ICS_FICS;
2384                 ics_prefix = "$";
2385                 if (appData.debugMode)
2386                   fprintf(debugFP, "ics_type %d\n", ics_type);
2387                 continue;
2388             }
2389             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2390                 ics_type = ICS_CHESSNET;
2391                 ics_prefix = "/";
2392                 if (appData.debugMode)
2393                   fprintf(debugFP, "ics_type %d\n", ics_type);
2394                 continue;
2395             }
2396
2397             if (!loggedOn &&
2398                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2399                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2400                  looking_at(buf, &i, "will be \"*\""))) {
2401               strcpy(ics_handle, star_match[0]);
2402               continue;
2403             }
2404
2405             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2406               char buf[MSG_SIZ];
2407               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2408               DisplayIcsInteractionTitle(buf);
2409               have_set_title = TRUE;
2410             }
2411
2412             /* skip finger notes */
2413             if (started == STARTED_NONE &&
2414                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2415                  (buf[i] == '1' && buf[i+1] == '0')) &&
2416                 buf[i+2] == ':' && buf[i+3] == ' ') {
2417               started = STARTED_CHATTER;
2418               i += 3;
2419               continue;
2420             }
2421
2422             /* skip formula vars */
2423             if (started == STARTED_NONE &&
2424                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2425               started = STARTED_CHATTER;
2426               i += 3;
2427               continue;
2428             }
2429
2430             oldi = i;
2431             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2432             if (appData.autoKibitz && started == STARTED_NONE && 
2433                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2434                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2435                 if(looking_at(buf, &i, "* kibitzes: ") &&
2436                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2437                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2438                         suppressKibitz = TRUE;
2439                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2440                                 && (gameMode == IcsPlayingWhite)) ||
2441                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2442                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2443                             started = STARTED_CHATTER; // own kibitz we simply discard
2444                         else {
2445                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2446                             parse_pos = 0; parse[0] = NULLCHAR;
2447                             savingComment = TRUE;
2448                             suppressKibitz = gameMode != IcsObserving ? 2 :
2449                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2450                         } 
2451                         continue;
2452                 } else
2453                 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2454                     started = STARTED_CHATTER;
2455                     suppressKibitz = TRUE;
2456                 }
2457             } // [HGM] kibitz: end of patch
2458
2459 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2460
2461             // [HGM] chat: intercept tells by users for which we have an open chat window
2462             channel = -1;
2463             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2464                                            looking_at(buf, &i, "* whispers:") ||
2465                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2466                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2467                 int p;
2468                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2469                 chattingPartner = -1;
2470
2471                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2472                 for(p=0; p<MAX_CHAT; p++) {
2473                     if(channel == atoi(chatPartner[p])) {
2474                     talker[0] = '['; strcat(talker, "]");
2475                     chattingPartner = p; break;
2476                     }
2477                 } else
2478                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2479                 for(p=0; p<MAX_CHAT; p++) {
2480                     if(!strcmp("WHISPER", chatPartner[p])) {
2481                         talker[0] = '['; strcat(talker, "]");
2482                         chattingPartner = p; break;
2483                     }
2484                 }
2485                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2486                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2487                     talker[0] = 0;
2488                     chattingPartner = p; break;
2489                 }
2490                 if(chattingPartner<0) i = oldi; else {
2491                     started = STARTED_COMMENT;
2492                     parse_pos = 0; parse[0] = NULLCHAR;
2493                     savingComment = TRUE;
2494                     suppressKibitz = TRUE;
2495                 }
2496             } // [HGM] chat: end of patch
2497
2498             if (appData.zippyTalk || appData.zippyPlay) {
2499                 /* [DM] Backup address for color zippy lines */
2500                 backup = i;
2501 #if ZIPPY
2502        #ifdef WIN32
2503                if (loggedOn == TRUE)
2504                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2505                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2506        #else
2507                 if (ZippyControl(buf, &i) ||
2508                     ZippyConverse(buf, &i) ||
2509                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2510                       loggedOn = TRUE;
2511                       if (!appData.colorize) continue;
2512                 }
2513        #endif
2514 #endif
2515             } // [DM] 'else { ' deleted
2516                 if (
2517                     /* Regular tells and says */
2518                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2519                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2520                     looking_at(buf, &i, "* says: ") ||
2521                     /* Don't color "message" or "messages" output */
2522                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2523                     looking_at(buf, &i, "*. * at *:*: ") ||
2524                     looking_at(buf, &i, "--* (*:*): ") ||
2525                     /* Message notifications (same color as tells) */
2526                     looking_at(buf, &i, "* has left a message ") ||
2527                     looking_at(buf, &i, "* just sent you a message:\n") ||
2528                     /* Whispers and kibitzes */
2529                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2530                     looking_at(buf, &i, "* kibitzes: ") ||
2531                     /* Channel tells */
2532                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2533
2534                   if (tkind == 1 && strchr(star_match[0], ':')) {
2535                       /* Avoid "tells you:" spoofs in channels */
2536                      tkind = 3;
2537                   }
2538                   if (star_match[0][0] == NULLCHAR ||
2539                       strchr(star_match[0], ' ') ||
2540                       (tkind == 3 && strchr(star_match[1], ' '))) {
2541                     /* Reject bogus matches */
2542                     i = oldi;
2543                   } else {
2544                     if (appData.colorize) {
2545                       if (oldi > next_out) {
2546                         SendToPlayer(&buf[next_out], oldi - next_out);
2547                         next_out = oldi;
2548                       }
2549                       switch (tkind) {
2550                       case 1:
2551                         Colorize(ColorTell, FALSE);
2552                         curColor = ColorTell;
2553                         break;
2554                       case 2:
2555                         Colorize(ColorKibitz, FALSE);
2556                         curColor = ColorKibitz;
2557                         break;
2558                       case 3:
2559                         p = strrchr(star_match[1], '(');
2560                         if (p == NULL) {
2561                           p = star_match[1];
2562                         } else {
2563                           p++;
2564                         }
2565                         if (atoi(p) == 1) {
2566                           Colorize(ColorChannel1, FALSE);
2567                           curColor = ColorChannel1;
2568                         } else {
2569                           Colorize(ColorChannel, FALSE);
2570                           curColor = ColorChannel;
2571                         }
2572                         break;
2573                       case 5:
2574                         curColor = ColorNormal;
2575                         break;
2576                       }
2577                     }
2578                     if (started == STARTED_NONE && appData.autoComment &&
2579                         (gameMode == IcsObserving ||
2580                          gameMode == IcsPlayingWhite ||
2581                          gameMode == IcsPlayingBlack)) {
2582                       parse_pos = i - oldi;
2583                       memcpy(parse, &buf[oldi], parse_pos);
2584                       parse[parse_pos] = NULLCHAR;
2585                       started = STARTED_COMMENT;
2586                       savingComment = TRUE;
2587                     } else {
2588                       started = STARTED_CHATTER;
2589                       savingComment = FALSE;
2590                     }
2591                     loggedOn = TRUE;
2592                     continue;
2593                   }
2594                 }
2595
2596                 if (looking_at(buf, &i, "* s-shouts: ") ||
2597                     looking_at(buf, &i, "* c-shouts: ")) {
2598                     if (appData.colorize) {
2599                         if (oldi > next_out) {
2600                             SendToPlayer(&buf[next_out], oldi - next_out);
2601                             next_out = oldi;
2602                         }
2603                         Colorize(ColorSShout, FALSE);
2604                         curColor = ColorSShout;
2605                     }
2606                     loggedOn = TRUE;
2607                     started = STARTED_CHATTER;
2608                     continue;
2609                 }
2610
2611                 if (looking_at(buf, &i, "--->")) {
2612                     loggedOn = TRUE;
2613                     continue;
2614                 }
2615
2616                 if (looking_at(buf, &i, "* shouts: ") ||
2617                     looking_at(buf, &i, "--> ")) {
2618                     if (appData.colorize) {
2619                         if (oldi > next_out) {
2620                             SendToPlayer(&buf[next_out], oldi - next_out);
2621                             next_out = oldi;
2622                         }
2623                         Colorize(ColorShout, FALSE);
2624                         curColor = ColorShout;
2625                     }
2626                     loggedOn = TRUE;
2627                     started = STARTED_CHATTER;
2628                     continue;
2629                 }
2630
2631                 if (looking_at( buf, &i, "Challenge:")) {
2632                     if (appData.colorize) {
2633                         if (oldi > next_out) {
2634                             SendToPlayer(&buf[next_out], oldi - next_out);
2635                             next_out = oldi;
2636                         }
2637                         Colorize(ColorChallenge, FALSE);
2638                         curColor = ColorChallenge;
2639                     }
2640                     loggedOn = TRUE;
2641                     continue;
2642                 }
2643
2644                 if (looking_at(buf, &i, "* offers you") ||
2645                     looking_at(buf, &i, "* offers to be") ||
2646                     looking_at(buf, &i, "* would like to") ||
2647                     looking_at(buf, &i, "* requests to") ||
2648                     looking_at(buf, &i, "Your opponent offers") ||
2649                     looking_at(buf, &i, "Your opponent requests")) {
2650
2651                     if (appData.colorize) {
2652                         if (oldi > next_out) {
2653                             SendToPlayer(&buf[next_out], oldi - next_out);
2654                             next_out = oldi;
2655                         }
2656                         Colorize(ColorRequest, FALSE);
2657                         curColor = ColorRequest;
2658                     }
2659                     continue;
2660                 }
2661
2662                 if (looking_at(buf, &i, "* (*) seeking")) {
2663                     if (appData.colorize) {
2664                         if (oldi > next_out) {
2665                             SendToPlayer(&buf[next_out], oldi - next_out);
2666                             next_out = oldi;
2667                         }
2668                         Colorize(ColorSeek, FALSE);
2669                         curColor = ColorSeek;
2670                     }
2671                     continue;
2672             }
2673
2674             if (looking_at(buf, &i, "\\   ")) {
2675                 if (prevColor != ColorNormal) {
2676                     if (oldi > next_out) {
2677                         SendToPlayer(&buf[next_out], oldi - next_out);
2678                         next_out = oldi;
2679                     }
2680                     Colorize(prevColor, TRUE);
2681                     curColor = prevColor;
2682                 }
2683                 if (savingComment) {
2684                     parse_pos = i - oldi;
2685                     memcpy(parse, &buf[oldi], parse_pos);
2686                     parse[parse_pos] = NULLCHAR;
2687                     started = STARTED_COMMENT;
2688                 } else {
2689                     started = STARTED_CHATTER;
2690                 }
2691                 continue;
2692             }
2693
2694             if (looking_at(buf, &i, "Black Strength :") ||
2695                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2696                 looking_at(buf, &i, "<10>") ||
2697                 looking_at(buf, &i, "#@#")) {
2698                 /* Wrong board style */
2699                 loggedOn = TRUE;
2700                 SendToICS(ics_prefix);
2701                 SendToICS("set style 12\n");
2702                 SendToICS(ics_prefix);
2703                 SendToICS("refresh\n");
2704                 continue;
2705             }
2706             
2707             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2708                 ICSInitScript();
2709                 have_sent_ICS_logon = 1;
2710                 continue;
2711             }
2712               
2713             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2714                 (looking_at(buf, &i, "\n<12> ") ||
2715                  looking_at(buf, &i, "<12> "))) {
2716                 loggedOn = TRUE;
2717                 if (oldi > next_out) {
2718                     SendToPlayer(&buf[next_out], oldi - next_out);
2719                 }
2720                 next_out = i;
2721                 started = STARTED_BOARD;
2722                 parse_pos = 0;
2723                 continue;
2724             }
2725
2726             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2727                 looking_at(buf, &i, "<b1> ")) {
2728                 if (oldi > next_out) {
2729                     SendToPlayer(&buf[next_out], oldi - next_out);
2730                 }
2731                 next_out = i;
2732                 started = STARTED_HOLDINGS;
2733                 parse_pos = 0;
2734                 continue;
2735             }
2736
2737             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2738                 loggedOn = TRUE;
2739                 /* Header for a move list -- first line */
2740
2741                 switch (ics_getting_history) {
2742                   case H_FALSE:
2743                     switch (gameMode) {
2744                       case IcsIdle:
2745                       case BeginningOfGame:
2746                         /* User typed "moves" or "oldmoves" while we
2747                            were idle.  Pretend we asked for these
2748                            moves and soak them up so user can step
2749                            through them and/or save them.
2750                            */
2751                         Reset(FALSE, TRUE);
2752                         gameMode = IcsObserving;
2753                         ModeHighlight();
2754                         ics_gamenum = -1;
2755                         ics_getting_history = H_GOT_UNREQ_HEADER;
2756                         break;
2757                       case EditGame: /*?*/
2758                       case EditPosition: /*?*/
2759                         /* Should above feature work in these modes too? */
2760                         /* For now it doesn't */
2761                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2762                         break;
2763                       default:
2764                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2765                         break;
2766                     }
2767                     break;
2768                   case H_REQUESTED:
2769                     /* Is this the right one? */
2770                     if (gameInfo.white && gameInfo.black &&
2771                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2772                         strcmp(gameInfo.black, star_match[2]) == 0) {
2773                         /* All is well */
2774                         ics_getting_history = H_GOT_REQ_HEADER;
2775                     }
2776                     break;
2777                   case H_GOT_REQ_HEADER:
2778                   case H_GOT_UNREQ_HEADER:
2779                   case H_GOT_UNWANTED_HEADER:
2780                   case H_GETTING_MOVES:
2781                     /* Should not happen */
2782                     DisplayError(_("Error gathering move list: two headers"), 0);
2783                     ics_getting_history = H_FALSE;
2784                     break;
2785                 }
2786
2787                 /* Save player ratings into gameInfo if needed */
2788                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2789                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2790                     (gameInfo.whiteRating == -1 ||
2791                      gameInfo.blackRating == -1)) {
2792
2793                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2794                     gameInfo.blackRating = string_to_rating(star_match[3]);
2795                     if (appData.debugMode)
2796                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2797                               gameInfo.whiteRating, gameInfo.blackRating);
2798                 }
2799                 continue;
2800             }
2801
2802             if (looking_at(buf, &i,
2803               "* * match, initial time: * minute*, increment: * second")) {
2804                 /* Header for a move list -- second line */
2805                 /* Initial board will follow if this is a wild game */
2806                 if (gameInfo.event != NULL) free(gameInfo.event);
2807                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2808                 gameInfo.event = StrSave(str);
2809                 /* [HGM] we switched variant. Translate boards if needed. */
2810                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2811                 continue;
2812             }
2813
2814             if (looking_at(buf, &i, "Move  ")) {
2815                 /* Beginning of a move list */
2816                 switch (ics_getting_history) {
2817                   case H_FALSE:
2818                     /* Normally should not happen */
2819                     /* Maybe user hit reset while we were parsing */
2820                     break;
2821                   case H_REQUESTED:
2822                     /* Happens if we are ignoring a move list that is not
2823                      * the one we just requested.  Common if the user
2824                      * tries to observe two games without turning off
2825                      * getMoveList */
2826                     break;
2827                   case H_GETTING_MOVES:
2828                     /* Should not happen */
2829                     DisplayError(_("Error gathering move list: nested"), 0);
2830                     ics_getting_history = H_FALSE;
2831                     break;
2832                   case H_GOT_REQ_HEADER:
2833                     ics_getting_history = H_GETTING_MOVES;
2834                     started = STARTED_MOVES;
2835                     parse_pos = 0;
2836                     if (oldi > next_out) {
2837                         SendToPlayer(&buf[next_out], oldi - next_out);
2838                     }
2839                     break;
2840                   case H_GOT_UNREQ_HEADER:
2841                     ics_getting_history = H_GETTING_MOVES;
2842                     started = STARTED_MOVES_NOHIDE;
2843                     parse_pos = 0;
2844                     break;
2845                   case H_GOT_UNWANTED_HEADER:
2846                     ics_getting_history = H_FALSE;
2847                     break;
2848                 }
2849                 continue;
2850             }                           
2851             
2852             if (looking_at(buf, &i, "% ") ||
2853                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2854                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2855                 savingComment = FALSE;
2856                 switch (started) {
2857                   case STARTED_MOVES:
2858                   case STARTED_MOVES_NOHIDE:
2859                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2860                     parse[parse_pos + i - oldi] = NULLCHAR;
2861                     ParseGameHistory(parse);
2862 #if ZIPPY
2863                     if (appData.zippyPlay && first.initDone) {
2864                         FeedMovesToProgram(&first, forwardMostMove);
2865                         if (gameMode == IcsPlayingWhite) {
2866                             if (WhiteOnMove(forwardMostMove)) {
2867                                 if (first.sendTime) {
2868                                   if (first.useColors) {
2869                                     SendToProgram("black\n", &first); 
2870                                   }
2871                                   SendTimeRemaining(&first, TRUE);
2872                                 }
2873                                 if (first.useColors) {
2874                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2875                                 }
2876                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2877                                 first.maybeThinking = TRUE;
2878                             } else {
2879                                 if (first.usePlayother) {
2880                                   if (first.sendTime) {
2881                                     SendTimeRemaining(&first, TRUE);
2882                                   }
2883                                   SendToProgram("playother\n", &first);
2884                                   firstMove = FALSE;
2885                                 } else {
2886                                   firstMove = TRUE;
2887                                 }
2888                             }
2889                         } else if (gameMode == IcsPlayingBlack) {
2890                             if (!WhiteOnMove(forwardMostMove)) {
2891                                 if (first.sendTime) {
2892                                   if (first.useColors) {
2893                                     SendToProgram("white\n", &first);
2894                                   }
2895                                   SendTimeRemaining(&first, FALSE);
2896                                 }
2897                                 if (first.useColors) {
2898                                   SendToProgram("black\n", &first);
2899                                 }
2900                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2901                                 first.maybeThinking = TRUE;
2902                             } else {
2903                                 if (first.usePlayother) {
2904                                   if (first.sendTime) {
2905                                     SendTimeRemaining(&first, FALSE);
2906                                   }
2907                                   SendToProgram("playother\n", &first);
2908                                   firstMove = FALSE;
2909                                 } else {
2910                                   firstMove = TRUE;
2911                                 }
2912                             }
2913                         }                       
2914                     }
2915 #endif
2916                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2917                         /* Moves came from oldmoves or moves command
2918                            while we weren't doing anything else.
2919                            */
2920                         currentMove = forwardMostMove;
2921                         ClearHighlights();/*!!could figure this out*/
2922                         flipView = appData.flipView;
2923                         DrawPosition(TRUE, boards[currentMove]);
2924                         DisplayBothClocks();
2925                         sprintf(str, "%s vs. %s",
2926                                 gameInfo.white, gameInfo.black);
2927                         DisplayTitle(str);
2928                         gameMode = IcsIdle;
2929                     } else {
2930                         /* Moves were history of an active game */
2931                         if (gameInfo.resultDetails != NULL) {
2932                             free(gameInfo.resultDetails);
2933                             gameInfo.resultDetails = NULL;
2934                         }
2935                     }
2936                     HistorySet(parseList, backwardMostMove,
2937                                forwardMostMove, currentMove-1);
2938                     DisplayMove(currentMove - 1);
2939                     if (started == STARTED_MOVES) next_out = i;
2940                     started = STARTED_NONE;
2941                     ics_getting_history = H_FALSE;
2942                     break;
2943
2944                   case STARTED_OBSERVE:
2945                     started = STARTED_NONE;
2946                     SendToICS(ics_prefix);
2947                     SendToICS("refresh\n");
2948                     break;
2949
2950                   default:
2951                     break;
2952                 }
2953                 if(bookHit) { // [HGM] book: simulate book reply
2954                     static char bookMove[MSG_SIZ]; // a bit generous?
2955
2956                     programStats.nodes = programStats.depth = programStats.time = 
2957                     programStats.score = programStats.got_only_move = 0;
2958                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2959
2960                     strcpy(bookMove, "move ");
2961                     strcat(bookMove, bookHit);
2962                     HandleMachineMove(bookMove, &first);
2963                 }
2964                 continue;
2965             }
2966             
2967             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2968                  started == STARTED_HOLDINGS ||
2969                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2970                 /* Accumulate characters in move list or board */
2971                 parse[parse_pos++] = buf[i];
2972             }
2973             
2974             /* Start of game messages.  Mostly we detect start of game
2975                when the first board image arrives.  On some versions
2976                of the ICS, though, we need to do a "refresh" after starting
2977                to observe in order to get the current board right away. */
2978             if (looking_at(buf, &i, "Adding game * to observation list")) {
2979                 started = STARTED_OBSERVE;
2980                 continue;
2981             }
2982
2983             /* Handle auto-observe */
2984             if (appData.autoObserve &&
2985                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2986                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2987                 char *player;
2988                 /* Choose the player that was highlighted, if any. */
2989                 if (star_match[0][0] == '\033' ||
2990                     star_match[1][0] != '\033') {
2991                     player = star_match[0];
2992                 } else {
2993                     player = star_match[2];
2994                 }
2995                 sprintf(str, "%sobserve %s\n",
2996                         ics_prefix, StripHighlightAndTitle(player));
2997                 SendToICS(str);
2998
2999                 /* Save ratings from notify string */
3000                 strcpy(player1Name, star_match[0]);
3001                 player1Rating = string_to_rating(star_match[1]);
3002                 strcpy(player2Name, star_match[2]);
3003                 player2Rating = string_to_rating(star_match[3]);
3004
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, 
3007                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3008                           player1Name, player1Rating,
3009                           player2Name, player2Rating);
3010
3011                 continue;
3012             }
3013
3014             /* Deal with automatic examine mode after a game,
3015                and with IcsObserving -> IcsExamining transition */
3016             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3017                 looking_at(buf, &i, "has made you an examiner of game *")) {
3018
3019                 int gamenum = atoi(star_match[0]);
3020                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3021                     gamenum == ics_gamenum) {
3022                     /* We were already playing or observing this game;
3023                        no need to refetch history */
3024                     gameMode = IcsExamining;
3025                     if (pausing) {
3026                         pauseExamForwardMostMove = forwardMostMove;
3027                     } else if (currentMove < forwardMostMove) {
3028                         ForwardInner(forwardMostMove);
3029                     }
3030                 } else {
3031                     /* I don't think this case really can happen */
3032                     SendToICS(ics_prefix);
3033                     SendToICS("refresh\n");
3034                 }
3035                 continue;
3036             }    
3037             
3038             /* Error messages */
3039 //          if (ics_user_moved) {
3040             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3041                 if (looking_at(buf, &i, "Illegal move") ||
3042                     looking_at(buf, &i, "Not a legal move") ||
3043                     looking_at(buf, &i, "Your king is in check") ||
3044                     looking_at(buf, &i, "It isn't your turn") ||
3045                     looking_at(buf, &i, "It is not your move")) {
3046                     /* Illegal move */
3047                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3048                         currentMove = --forwardMostMove;
3049                         DisplayMove(currentMove - 1); /* before DMError */
3050                         DrawPosition(FALSE, boards[currentMove]);
3051                         SwitchClocks();
3052                         DisplayBothClocks();
3053                     }
3054                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3055                     ics_user_moved = 0;
3056                     continue;
3057                 }
3058             }
3059
3060             if (looking_at(buf, &i, "still have time") ||
3061                 looking_at(buf, &i, "not out of time") ||
3062                 looking_at(buf, &i, "either player is out of time") ||
3063                 looking_at(buf, &i, "has timeseal; checking")) {
3064                 /* We must have called his flag a little too soon */
3065                 whiteFlag = blackFlag = FALSE;
3066                 continue;
3067             }
3068
3069             if (looking_at(buf, &i, "added * seconds to") ||
3070                 looking_at(buf, &i, "seconds were added to")) {
3071                 /* Update the clocks */
3072                 SendToICS(ics_prefix);
3073                 SendToICS("refresh\n");
3074                 continue;
3075             }
3076
3077             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3078                 ics_clock_paused = TRUE;
3079                 StopClocks();
3080                 continue;
3081             }
3082
3083             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3084                 ics_clock_paused = FALSE;
3085                 StartClocks();
3086                 continue;
3087             }
3088
3089             /* Grab player ratings from the Creating: message.
3090                Note we have to check for the special case when
3091                the ICS inserts things like [white] or [black]. */
3092             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3093                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3094                 /* star_matches:
3095                    0    player 1 name (not necessarily white)
3096                    1    player 1 rating
3097                    2    empty, white, or black (IGNORED)
3098                    3    player 2 name (not necessarily black)
3099                    4    player 2 rating
3100                    
3101                    The names/ratings are sorted out when the game
3102                    actually starts (below).
3103                 */
3104                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3105                 player1Rating = string_to_rating(star_match[1]);
3106                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3107                 player2Rating = string_to_rating(star_match[4]);
3108
3109                 if (appData.debugMode)
3110                   fprintf(debugFP, 
3111                           "Ratings from 'Creating:' %s %d, %s %d\n",
3112                           player1Name, player1Rating,
3113                           player2Name, player2Rating);
3114
3115                 continue;
3116             }
3117             
3118             /* Improved generic start/end-of-game messages */
3119             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3120                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3121                 /* If tkind == 0: */
3122                 /* star_match[0] is the game number */
3123                 /*           [1] is the white player's name */
3124                 /*           [2] is the black player's name */
3125                 /* For end-of-game: */
3126                 /*           [3] is the reason for the game end */
3127                 /*           [4] is a PGN end game-token, preceded by " " */
3128                 /* For start-of-game: */
3129                 /*           [3] begins with "Creating" or "Continuing" */
3130                 /*           [4] is " *" or empty (don't care). */
3131                 int gamenum = atoi(star_match[0]);
3132                 char *whitename, *blackname, *why, *endtoken;
3133                 ChessMove endtype = (ChessMove) 0;
3134
3135                 if (tkind == 0) {
3136                   whitename = star_match[1];
3137                   blackname = star_match[2];
3138                   why = star_match[3];
3139                   endtoken = star_match[4];
3140                 } else {
3141                   whitename = star_match[1];
3142                   blackname = star_match[3];
3143                   why = star_match[5];
3144                   endtoken = star_match[6];
3145                 }
3146
3147                 /* Game start messages */
3148                 if (strncmp(why, "Creating ", 9) == 0 ||
3149                     strncmp(why, "Continuing ", 11) == 0) {
3150                     gs_gamenum = gamenum;
3151                     strcpy(gs_kind, strchr(why, ' ') + 1);
3152 #if ZIPPY
3153                     if (appData.zippyPlay) {
3154                         ZippyGameStart(whitename, blackname);
3155                     }
3156 #endif /*ZIPPY*/
3157                     continue;
3158                 }
3159
3160                 /* Game end messages */
3161                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3162                     ics_gamenum != gamenum) {
3163                     continue;
3164                 }
3165                 while (endtoken[0] == ' ') endtoken++;
3166                 switch (endtoken[0]) {
3167                   case '*':
3168                   default:
3169                     endtype = GameUnfinished;
3170                     break;
3171                   case '0':
3172                     endtype = BlackWins;
3173                     break;
3174                   case '1':
3175                     if (endtoken[1] == '/')
3176                       endtype = GameIsDrawn;
3177                     else
3178                       endtype = WhiteWins;
3179                     break;
3180                 }
3181                 GameEnds(endtype, why, GE_ICS);
3182 #if ZIPPY
3183                 if (appData.zippyPlay && first.initDone) {
3184                     ZippyGameEnd(endtype, why);
3185                     if (first.pr == NULL) {
3186                       /* Start the next process early so that we'll
3187                          be ready for the next challenge */
3188                       StartChessProgram(&first);
3189                     }
3190                     /* Send "new" early, in case this command takes
3191                        a long time to finish, so that we'll be ready
3192                        for the next challenge. */
3193                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3194                     Reset(TRUE, TRUE);
3195                 }
3196 #endif /*ZIPPY*/
3197                 continue;
3198             }
3199
3200             if (looking_at(buf, &i, "Removing game * from observation") ||
3201                 looking_at(buf, &i, "no longer observing game *") ||
3202                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3203                 if (gameMode == IcsObserving &&
3204                     atoi(star_match[0]) == ics_gamenum)
3205                   {
3206                       /* icsEngineAnalyze */
3207                       if (appData.icsEngineAnalyze) {
3208                             ExitAnalyzeMode();
3209                             ModeHighlight();
3210                       }
3211                       StopClocks();
3212                       gameMode = IcsIdle;
3213                       ics_gamenum = -1;
3214                       ics_user_moved = FALSE;
3215                   }
3216                 continue;
3217             }
3218
3219             if (looking_at(buf, &i, "no longer examining game *")) {
3220                 if (gameMode == IcsExamining &&
3221                     atoi(star_match[0]) == ics_gamenum)
3222                   {
3223                       gameMode = IcsIdle;
3224                       ics_gamenum = -1;
3225                       ics_user_moved = FALSE;
3226                   }
3227                 continue;
3228             }
3229
3230             /* Advance leftover_start past any newlines we find,
3231                so only partial lines can get reparsed */
3232             if (looking_at(buf, &i, "\n")) {
3233                 prevColor = curColor;
3234                 if (curColor != ColorNormal) {
3235                     if (oldi > next_out) {
3236                         SendToPlayer(&buf[next_out], oldi - next_out);
3237                         next_out = oldi;
3238                     }
3239                     Colorize(ColorNormal, FALSE);
3240                     curColor = ColorNormal;
3241                 }
3242                 if (started == STARTED_BOARD) {
3243                     started = STARTED_NONE;
3244                     parse[parse_pos] = NULLCHAR;
3245                     ParseBoard12(parse);
3246                     ics_user_moved = 0;
3247
3248                     /* Send premove here */
3249                     if (appData.premove) {
3250                       char str[MSG_SIZ];
3251                       if (currentMove == 0 &&
3252                           gameMode == IcsPlayingWhite &&
3253                           appData.premoveWhite) {
3254                         sprintf(str, "%s\n", appData.premoveWhiteText);
3255                         if (appData.debugMode)
3256                           fprintf(debugFP, "Sending premove:\n");
3257                         SendToICS(str);
3258                       } else if (currentMove == 1 &&
3259                                  gameMode == IcsPlayingBlack &&
3260                                  appData.premoveBlack) {
3261                         sprintf(str, "%s\n", appData.premoveBlackText);
3262                         if (appData.debugMode)
3263                           fprintf(debugFP, "Sending premove:\n");
3264                         SendToICS(str);
3265                       } else if (gotPremove) {
3266                         gotPremove = 0;
3267                         ClearPremoveHighlights();
3268                         if (appData.debugMode)
3269                           fprintf(debugFP, "Sending premove:\n");
3270                           UserMoveEvent(premoveFromX, premoveFromY, 
3271                                         premoveToX, premoveToY, 
3272                                         premovePromoChar);
3273                       }
3274                     }
3275
3276                     /* Usually suppress following prompt */
3277                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3278                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3279                         if (looking_at(buf, &i, "*% ")) {
3280                             savingComment = FALSE;
3281                         }
3282                     }
3283                     next_out = i;
3284                 } else if (started == STARTED_HOLDINGS) {
3285                     int gamenum;
3286                     char new_piece[MSG_SIZ];
3287                     started = STARTED_NONE;
3288                     parse[parse_pos] = NULLCHAR;
3289                     if (appData.debugMode)
3290                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3291                                                         parse, currentMove);
3292                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3293                         gamenum == ics_gamenum) {
3294                         if (gameInfo.variant == VariantNormal) {
3295                           /* [HGM] We seem to switch variant during a game!
3296                            * Presumably no holdings were displayed, so we have
3297                            * to move the position two files to the right to
3298                            * create room for them!
3299                            */
3300                           VariantClass newVariant;
3301                           switch(gameInfo.boardWidth) { // base guess on board width
3302                                 case 9:  newVariant = VariantShogi; break;
3303                                 case 10: newVariant = VariantGreat; break;
3304                                 default: newVariant = VariantCrazyhouse; break;
3305                           }
3306                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3307                           /* Get a move list just to see the header, which
3308                              will tell us whether this is really bug or zh */
3309                           if (ics_getting_history == H_FALSE) {
3310                             ics_getting_history = H_REQUESTED;
3311                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3312                             SendToICS(str);
3313                           }
3314                         }
3315                         new_piece[0] = NULLCHAR;
3316                         sscanf(parse, "game %d white [%s black [%s <- %s",
3317                                &gamenum, white_holding, black_holding,
3318                                new_piece);
3319                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3320                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3321                         /* [HGM] copy holdings to board holdings area */
3322                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3323                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3324                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3325 #if ZIPPY
3326                         if (appData.zippyPlay && first.initDone) {
3327                             ZippyHoldings(white_holding, black_holding,
3328                                           new_piece);
3329                         }
3330 #endif /*ZIPPY*/
3331                         if (tinyLayout || smallLayout) {
3332                             char wh[16], bh[16];
3333                             PackHolding(wh, white_holding);
3334                             PackHolding(bh, black_holding);
3335                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3336                                     gameInfo.white, gameInfo.black);
3337                         } else {
3338                             sprintf(str, "%s [%s] vs. %s [%s]",
3339                                     gameInfo.white, white_holding,
3340                                     gameInfo.black, black_holding);
3341                         }
3342
3343                         DrawPosition(FALSE, boards[currentMove]);
3344                         DisplayTitle(str);
3345                     }
3346                     /* Suppress following prompt */
3347                     if (looking_at(buf, &i, "*% ")) {
3348                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3349                         savingComment = FALSE;
3350                     }
3351                     next_out = i;
3352                 }
3353                 continue;
3354             }
3355
3356             i++;                /* skip unparsed character and loop back */
3357         }
3358         
3359         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3360             started != STARTED_HOLDINGS && i > next_out) {
3361             SendToPlayer(&buf[next_out], i - next_out);
3362             next_out = i;
3363         }
3364         suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3365         
3366         leftover_len = buf_len - leftover_start;
3367         /* if buffer ends with something we couldn't parse,
3368            reparse it after appending the next read */
3369         
3370     } else if (count == 0) {
3371         RemoveInputSource(isr);
3372         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3373     } else {
3374         DisplayFatalError(_("Error reading from ICS"), error, 1);
3375     }
3376 }
3377
3378
3379 /* Board style 12 looks like this:
3380    
3381    <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
3382    
3383  * The "<12> " is stripped before it gets to this routine.  The two
3384  * trailing 0's (flip state and clock ticking) are later addition, and
3385  * some chess servers may not have them, or may have only the first.
3386  * Additional trailing fields may be added in the future.  
3387  */
3388
3389 #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"
3390
3391 #define RELATION_OBSERVING_PLAYED    0
3392 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3393 #define RELATION_PLAYING_MYMOVE      1
3394 #define RELATION_PLAYING_NOTMYMOVE  -1
3395 #define RELATION_EXAMINING           2
3396 #define RELATION_ISOLATED_BOARD     -3
3397 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3398
3399 void
3400 ParseBoard12(string)
3401      char *string;
3402
3403     GameMode newGameMode;
3404     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3405     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3406     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3407     char to_play, board_chars[200];
3408     char move_str[500], str[500], elapsed_time[500];
3409     char black[32], white[32];
3410     Board board;
3411     int prevMove = currentMove;
3412     int ticking = 2;
3413     ChessMove moveType;
3414     int fromX, fromY, toX, toY;
3415     char promoChar;
3416     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3417     char *bookHit = NULL; // [HGM] book
3418     Boolean weird = FALSE, reqFlag = FALSE;
3419
3420     fromX = fromY = toX = toY = -1;
3421     
3422     newGame = FALSE;
3423
3424     if (appData.debugMode)
3425       fprintf(debugFP, _("Parsing board: %s\n"), string);
3426
3427     move_str[0] = NULLCHAR;
3428     elapsed_time[0] = NULLCHAR;
3429     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3430         int  i = 0, j;
3431         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3432             if(string[i] == ' ') { ranks++; files = 0; }
3433             else files++;
3434             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3435             i++;
3436         }
3437         for(j = 0; j <i; j++) board_chars[j] = string[j];
3438         board_chars[i] = '\0';
3439         string += i + 1;
3440     }
3441     n = sscanf(string, PATTERN, &to_play, &double_push,
3442                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3443                &gamenum, white, black, &relation, &basetime, &increment,
3444                &white_stren, &black_stren, &white_time, &black_time,
3445                &moveNum, str, elapsed_time, move_str, &ics_flip,
3446                &ticking);
3447
3448     if (n < 21) {
3449         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3450         DisplayError(str, 0);
3451         return;
3452     }
3453
3454     /* Convert the move number to internal form */
3455     moveNum = (moveNum - 1) * 2;
3456     if (to_play == 'B') moveNum++;
3457     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3458       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3459                         0, 1);
3460       return;
3461     }
3462     
3463     switch (relation) {
3464       case RELATION_OBSERVING_PLAYED:
3465       case RELATION_OBSERVING_STATIC:
3466         if (gamenum == -1) {
3467             /* Old ICC buglet */
3468             relation = RELATION_OBSERVING_STATIC;
3469         }
3470         newGameMode = IcsObserving;
3471         break;
3472       case RELATION_PLAYING_MYMOVE:
3473       case RELATION_PLAYING_NOTMYMOVE:
3474         newGameMode =
3475           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3476             IcsPlayingWhite : IcsPlayingBlack;
3477         break;
3478       case RELATION_EXAMINING:
3479         newGameMode = IcsExamining;
3480         break;
3481       case RELATION_ISOLATED_BOARD:
3482       default:
3483         /* Just display this board.  If user was doing something else,
3484            we will forget about it until the next board comes. */ 
3485         newGameMode = IcsIdle;
3486         break;
3487       case RELATION_STARTING_POSITION:
3488         newGameMode = gameMode;
3489         break;
3490     }
3491     
3492     /* Modify behavior for initial board display on move listing
3493        of wild games.
3494        */
3495     switch (ics_getting_history) {
3496       case H_FALSE:
3497       case H_REQUESTED:
3498         break;
3499       case H_GOT_REQ_HEADER:
3500       case H_GOT_UNREQ_HEADER:
3501         /* This is the initial position of the current game */
3502         gamenum = ics_gamenum;
3503         moveNum = 0;            /* old ICS bug workaround */
3504         if (to_play == 'B') {
3505           startedFromSetupPosition = TRUE;
3506           blackPlaysFirst = TRUE;
3507           moveNum = 1;
3508           if (forwardMostMove == 0) forwardMostMove = 1;
3509           if (backwardMostMove == 0) backwardMostMove = 1;
3510           if (currentMove == 0) currentMove = 1;
3511         }
3512         newGameMode = gameMode;
3513         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3514         break;
3515       case H_GOT_UNWANTED_HEADER:
3516         /* This is an initial board that we don't want */
3517         return;
3518       case H_GETTING_MOVES:
3519         /* Should not happen */
3520         DisplayError(_("Error gathering move list: extra board"), 0);
3521         ics_getting_history = H_FALSE;
3522         return;
3523     }
3524
3525    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3526                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3527      /* [HGM] We seem to have switched variant unexpectedly
3528       * Try to guess new variant from board size
3529       */
3530           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3531           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3532           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3533           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3534           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3535           if(!weird) newVariant = VariantNormal;
3536           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3537           /* Get a move list just to see the header, which
3538              will tell us whether this is really bug or zh */
3539           if (ics_getting_history == H_FALSE) {
3540             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3541             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3542             SendToICS(str);
3543           }
3544     }
3545     
3546     /* Take action if this is the first board of a new game, or of a
3547        different game than is currently being displayed.  */
3548     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3549         relation == RELATION_ISOLATED_BOARD) {
3550         
3551         /* Forget the old game and get the history (if any) of the new one */
3552         if (gameMode != BeginningOfGame) {
3553           Reset(TRUE, TRUE);
3554         }
3555         newGame = TRUE;
3556         if (appData.autoRaiseBoard) BoardToTop();
3557         prevMove = -3;
3558         if (gamenum == -1) {
3559             newGameMode = IcsIdle;
3560         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3561                    appData.getMoveList && !reqFlag) {
3562             /* Need to get game history */
3563             ics_getting_history = H_REQUESTED;
3564             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3565             SendToICS(str);
3566         }
3567         
3568         /* Initially flip the board to have black on the bottom if playing
3569            black or if the ICS flip flag is set, but let the user change
3570            it with the Flip View button. */
3571         flipView = appData.autoFlipView ? 
3572           (newGameMode == IcsPlayingBlack) || ics_flip :
3573           appData.flipView;
3574         
3575         /* Done with values from previous mode; copy in new ones */
3576         gameMode = newGameMode;
3577         ModeHighlight();
3578         ics_gamenum = gamenum;
3579         if (gamenum == gs_gamenum) {
3580             int klen = strlen(gs_kind);
3581             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3582             sprintf(str, "ICS %s", gs_kind);
3583             gameInfo.event = StrSave(str);
3584         } else {
3585             gameInfo.event = StrSave("ICS game");
3586         }
3587         gameInfo.site = StrSave(appData.icsHost);
3588         gameInfo.date = PGNDate();
3589         gameInfo.round = StrSave("-");
3590         gameInfo.white = StrSave(white);
3591         gameInfo.black = StrSave(black);
3592         timeControl = basetime * 60 * 1000;
3593         timeControl_2 = 0;
3594         timeIncrement = increment * 1000;
3595         movesPerSession = 0;
3596         gameInfo.timeControl = TimeControlTagValue();
3597         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3598   if (appData.debugMode) {
3599     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3600     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3601     setbuf(debugFP, NULL);
3602   }
3603
3604         gameInfo.outOfBook = NULL;
3605         
3606         /* Do we have the ratings? */
3607         if (strcmp(player1Name, white) == 0 &&
3608             strcmp(player2Name, black) == 0) {
3609             if (appData.debugMode)
3610               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3611                       player1Rating, player2Rating);
3612             gameInfo.whiteRating = player1Rating;
3613             gameInfo.blackRating = player2Rating;
3614         } else if (strcmp(player2Name, white) == 0 &&
3615                    strcmp(player1Name, black) == 0) {
3616             if (appData.debugMode)
3617               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3618                       player2Rating, player1Rating);
3619             gameInfo.whiteRating = player2Rating;
3620             gameInfo.blackRating = player1Rating;
3621         }
3622         player1Name[0] = player2Name[0] = NULLCHAR;
3623
3624         /* Silence shouts if requested */
3625         if (appData.quietPlay &&
3626             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3627             SendToICS(ics_prefix);
3628             SendToICS("set shout 0\n");
3629         }
3630     }
3631     
3632     /* Deal with midgame name changes */
3633     if (!newGame) {
3634         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3635             if (gameInfo.white) free(gameInfo.white);
3636             gameInfo.white = StrSave(white);
3637         }
3638         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3639             if (gameInfo.black) free(gameInfo.black);
3640             gameInfo.black = StrSave(black);
3641         }
3642     }
3643     
3644     /* Throw away game result if anything actually changes in examine mode */
3645     if (gameMode == IcsExamining && !newGame) {
3646         gameInfo.result = GameUnfinished;
3647         if (gameInfo.resultDetails != NULL) {
3648             free(gameInfo.resultDetails);
3649             gameInfo.resultDetails = NULL;
3650         }
3651     }
3652     
3653     /* In pausing && IcsExamining mode, we ignore boards coming
3654        in if they are in a different variation than we are. */
3655     if (pauseExamInvalid) return;
3656     if (pausing && gameMode == IcsExamining) {
3657         if (moveNum <= pauseExamForwardMostMove) {
3658             pauseExamInvalid = TRUE;
3659             forwardMostMove = pauseExamForwardMostMove;
3660             return;
3661         }
3662     }
3663     
3664   if (appData.debugMode) {
3665     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3666   }
3667     /* Parse the board */
3668     for (k = 0; k < ranks; k++) {
3669       for (j = 0; j < files; j++)
3670         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3671       if(gameInfo.holdingsWidth > 1) {
3672            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3673            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3674       }
3675     }
3676     CopyBoard(boards[moveNum], board);
3677     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3678     if (moveNum == 0) {
3679         startedFromSetupPosition =
3680           !CompareBoards(board, initialPosition);
3681         if(startedFromSetupPosition)
3682             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3683     }
3684
3685     /* [HGM] Set castling rights. Take the outermost Rooks,
3686        to make it also work for FRC opening positions. Note that board12
3687        is really defective for later FRC positions, as it has no way to
3688        indicate which Rook can castle if they are on the same side of King.
3689        For the initial position we grant rights to the outermost Rooks,
3690        and remember thos rights, and we then copy them on positions
3691        later in an FRC game. This means WB might not recognize castlings with
3692        Rooks that have moved back to their original position as illegal,
3693        but in ICS mode that is not its job anyway.
3694     */
3695     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3696     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3697
3698         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3699             if(board[0][i] == WhiteRook) j = i;
3700         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3702             if(board[0][i] == WhiteRook) j = i;
3703         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3705             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3708             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3709         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3710
3711         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3712         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3713             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3714         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3715             if(board[BOARD_HEIGHT-1][k] == bKing)
3716                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3717     } else { int r;
3718         r = boards[moveNum][CASTLING][0] = initialRights[0];
3719         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3720         r = boards[moveNum][CASTLING][1] = initialRights[1];
3721         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3722         r = boards[moveNum][CASTLING][3] = initialRights[3];
3723         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3724         r = boards[moveNum][CASTLING][4] = initialRights[4];
3725         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3726         /* wildcastle kludge: always assume King has rights */
3727         r = boards[moveNum][CASTLING][2] = initialRights[2];
3728         r = boards[moveNum][CASTLING][5] = initialRights[5];
3729     }
3730     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3731     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3732
3733     
3734     if (ics_getting_history == H_GOT_REQ_HEADER ||
3735         ics_getting_history == H_GOT_UNREQ_HEADER) {
3736         /* This was an initial position from a move list, not
3737            the current position */
3738         return;
3739     }
3740     
3741     /* Update currentMove and known move number limits */
3742     newMove = newGame || moveNum > forwardMostMove;
3743
3744     if (newGame) {
3745         forwardMostMove = backwardMostMove = currentMove = moveNum;
3746         if (gameMode == IcsExamining && moveNum == 0) {
3747           /* Workaround for ICS limitation: we are not told the wild
3748              type when starting to examine a game.  But if we ask for
3749              the move list, the move list header will tell us */
3750             ics_getting_history = H_REQUESTED;
3751             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3752             SendToICS(str);
3753         }
3754     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3755                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3756 #if ZIPPY
3757         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3758         /* [HGM] applied this also to an engine that is silently watching        */
3759         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3760             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3761             gameInfo.variant == currentlyInitializedVariant) {
3762           takeback = forwardMostMove - moveNum;
3763           for (i = 0; i < takeback; i++) {
3764             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3765             SendToProgram("undo\n", &first);
3766           }
3767         }
3768 #endif
3769
3770         forwardMostMove = moveNum;
3771         if (!pausing || currentMove > forwardMostMove)
3772           currentMove = forwardMostMove;
3773     } else {
3774         /* New part of history that is not contiguous with old part */ 
3775         if (pausing && gameMode == IcsExamining) {
3776             pauseExamInvalid = TRUE;
3777             forwardMostMove = pauseExamForwardMostMove;
3778             return;
3779         }
3780         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3781 #if ZIPPY
3782             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3783                 // [HGM] when we will receive the move list we now request, it will be
3784                 // fed to the engine from the first move on. So if the engine is not
3785                 // in the initial position now, bring it there.
3786                 InitChessProgram(&first, 0);
3787             }
3788 #endif
3789             ics_getting_history = H_REQUESTED;
3790             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3791             SendToICS(str);
3792         }
3793         forwardMostMove = backwardMostMove = currentMove = moveNum;
3794     }
3795     
3796     /* Update the clocks */
3797     if (strchr(elapsed_time, '.')) {
3798       /* Time is in ms */
3799       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3800       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3801     } else {
3802       /* Time is in seconds */
3803       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3804       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3805     }
3806       
3807
3808 #if ZIPPY
3809     if (appData.zippyPlay && newGame &&
3810         gameMode != IcsObserving && gameMode != IcsIdle &&
3811         gameMode != IcsExamining)
3812       ZippyFirstBoard(moveNum, basetime, increment);
3813 #endif
3814     
3815     /* Put the move on the move list, first converting
3816        to canonical algebraic form. */
3817     if (moveNum > 0) {
3818   if (appData.debugMode) {
3819     if (appData.debugMode) { int f = forwardMostMove;
3820         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3821                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3822                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3823     }
3824     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3825     fprintf(debugFP, "moveNum = %d\n", moveNum);
3826     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3827     setbuf(debugFP, NULL);
3828   }
3829         if (moveNum <= backwardMostMove) {
3830             /* We don't know what the board looked like before
3831                this move.  Punt. */
3832             strcpy(parseList[moveNum - 1], move_str);
3833             strcat(parseList[moveNum - 1], " ");
3834             strcat(parseList[moveNum - 1], elapsed_time);
3835             moveList[moveNum - 1][0] = NULLCHAR;
3836         } else if (strcmp(move_str, "none") == 0) {
3837             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3838             /* Again, we don't know what the board looked like;
3839                this is really the start of the game. */
3840             parseList[moveNum - 1][0] = NULLCHAR;
3841             moveList[moveNum - 1][0] = NULLCHAR;
3842             backwardMostMove = moveNum;
3843             startedFromSetupPosition = TRUE;
3844             fromX = fromY = toX = toY = -1;
3845         } else {
3846           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3847           //                 So we parse the long-algebraic move string in stead of the SAN move
3848           int valid; char buf[MSG_SIZ], *prom;
3849
3850           // str looks something like "Q/a1-a2"; kill the slash
3851           if(str[1] == '/') 
3852                 sprintf(buf, "%c%s", str[0], str+2);
3853           else  strcpy(buf, str); // might be castling
3854           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3855                 strcat(buf, prom); // long move lacks promo specification!
3856           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3857                 if(appData.debugMode) 
3858                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3859                 strcpy(move_str, buf);
3860           }
3861           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3862                                 &fromX, &fromY, &toX, &toY, &promoChar)
3863                || ParseOneMove(buf, moveNum - 1, &moveType,
3864                                 &fromX, &fromY, &toX, &toY, &promoChar);
3865           // end of long SAN patch
3866           if (valid) {
3867             (void) CoordsToAlgebraic(boards[moveNum - 1],
3868                                      PosFlags(moveNum - 1),
3869                                      fromY, fromX, toY, toX, promoChar,
3870                                      parseList[moveNum-1]);
3871             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3872               case MT_NONE:
3873               case MT_STALEMATE:
3874               default:
3875                 break;
3876               case MT_CHECK:
3877                 if(gameInfo.variant != VariantShogi)
3878                     strcat(parseList[moveNum - 1], "+");
3879                 break;
3880               case MT_CHECKMATE:
3881               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3882                 strcat(parseList[moveNum - 1], "#");
3883                 break;
3884             }
3885             strcat(parseList[moveNum - 1], " ");
3886             strcat(parseList[moveNum - 1], elapsed_time);
3887             /* currentMoveString is set as a side-effect of ParseOneMove */
3888             strcpy(moveList[moveNum - 1], currentMoveString);
3889             strcat(moveList[moveNum - 1], "\n");
3890           } else {
3891             /* Move from ICS was illegal!?  Punt. */
3892   if (appData.debugMode) {
3893     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3895   }
3896             strcpy(parseList[moveNum - 1], move_str);
3897             strcat(parseList[moveNum - 1], " ");
3898             strcat(parseList[moveNum - 1], elapsed_time);
3899             moveList[moveNum - 1][0] = NULLCHAR;
3900             fromX = fromY = toX = toY = -1;
3901           }
3902         }
3903   if (appData.debugMode) {
3904     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3905     setbuf(debugFP, NULL);
3906   }
3907
3908 #if ZIPPY
3909         /* Send move to chess program (BEFORE animating it). */
3910         if (appData.zippyPlay && !newGame && newMove && 
3911            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3912
3913             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3914                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3915                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3916                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3917                             move_str);
3918                     DisplayError(str, 0);
3919                 } else {
3920                     if (first.sendTime) {
3921                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3922                     }
3923                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3924                     if (firstMove && !bookHit) {
3925                         firstMove = FALSE;
3926                         if (first.useColors) {
3927                           SendToProgram(gameMode == IcsPlayingWhite ?
3928                                         "white\ngo\n" :
3929                                         "black\ngo\n", &first);
3930                         } else {
3931                           SendToProgram("go\n", &first);
3932                         }
3933                         first.maybeThinking = TRUE;
3934                     }
3935                 }
3936             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3937               if (moveList[moveNum - 1][0] == NULLCHAR) {
3938                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3939                 DisplayError(str, 0);
3940               } else {
3941                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3942                 SendMoveToProgram(moveNum - 1, &first);
3943               }
3944             }
3945         }
3946 #endif
3947     }
3948
3949     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3950         /* If move comes from a remote source, animate it.  If it
3951            isn't remote, it will have already been animated. */
3952         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3953             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3954         }
3955         if (!pausing && appData.highlightLastMove) {
3956             SetHighlights(fromX, fromY, toX, toY);
3957         }
3958     }
3959     
3960     /* Start the clocks */
3961     whiteFlag = blackFlag = FALSE;
3962     appData.clockMode = !(basetime == 0 && increment == 0);
3963     if (ticking == 0) {
3964       ics_clock_paused = TRUE;
3965       StopClocks();
3966     } else if (ticking == 1) {
3967       ics_clock_paused = FALSE;
3968     }
3969     if (gameMode == IcsIdle ||
3970         relation == RELATION_OBSERVING_STATIC ||
3971         relation == RELATION_EXAMINING ||
3972         ics_clock_paused)
3973       DisplayBothClocks();
3974     else
3975       StartClocks();
3976     
3977     /* Display opponents and material strengths */
3978     if (gameInfo.variant != VariantBughouse &&
3979         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3980         if (tinyLayout || smallLayout) {
3981             if(gameInfo.variant == VariantNormal)
3982                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3984                     basetime, increment);
3985             else
3986                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3987                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3988                     basetime, increment, (int) gameInfo.variant);
3989         } else {
3990             if(gameInfo.variant == VariantNormal)
3991                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
3992                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3993                     basetime, increment);
3994             else
3995                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
3996                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3997                     basetime, increment, VariantName(gameInfo.variant));
3998         }
3999         DisplayTitle(str);
4000   if (appData.debugMode) {
4001     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4002   }
4003     }
4004
4005    
4006     /* Display the board */
4007     if (!pausing && !appData.noGUI) {
4008       
4009       if (appData.premove)
4010           if (!gotPremove || 
4011              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4012              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4013               ClearPremoveHighlights();
4014
4015       DrawPosition(FALSE, boards[currentMove]);
4016       DisplayMove(moveNum - 1);
4017       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4018             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4019               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4020         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4021       }
4022     }
4023
4024     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4025 #if ZIPPY
4026     if(bookHit) { // [HGM] book: simulate book reply
4027         static char bookMove[MSG_SIZ]; // a bit generous?
4028
4029         programStats.nodes = programStats.depth = programStats.time = 
4030         programStats.score = programStats.got_only_move = 0;
4031         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4032
4033         strcpy(bookMove, "move ");
4034         strcat(bookMove, bookHit);
4035         HandleMachineMove(bookMove, &first);
4036     }
4037 #endif
4038 }
4039
4040 void
4041 GetMoveListEvent()
4042 {
4043     char buf[MSG_SIZ];
4044     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4045         ics_getting_history = H_REQUESTED;
4046         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4047         SendToICS(buf);
4048     }
4049 }
4050
4051 void
4052 AnalysisPeriodicEvent(force)
4053      int force;
4054 {
4055     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4056          && !force) || !appData.periodicUpdates)
4057       return;
4058
4059     /* Send . command to Crafty to collect stats */
4060     SendToProgram(".\n", &first);
4061
4062     /* Don't send another until we get a response (this makes
4063        us stop sending to old Crafty's which don't understand
4064        the "." command (sending illegal cmds resets node count & time,
4065        which looks bad)) */
4066     programStats.ok_to_send = 0;
4067 }
4068
4069 void ics_update_width(new_width)
4070         int new_width;
4071 {
4072         ics_printf("set width %d\n", new_width);
4073 }
4074
4075 void
4076 SendMoveToProgram(moveNum, cps)
4077      int moveNum;
4078      ChessProgramState *cps;
4079 {
4080     char buf[MSG_SIZ];
4081
4082     if (cps->useUsermove) {
4083       SendToProgram("usermove ", cps);
4084     }
4085     if (cps->useSAN) {
4086       char *space;
4087       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4088         int len = space - parseList[moveNum];
4089         memcpy(buf, parseList[moveNum], len);
4090         buf[len++] = '\n';
4091         buf[len] = NULLCHAR;
4092       } else {
4093         sprintf(buf, "%s\n", parseList[moveNum]);
4094       }
4095       SendToProgram(buf, cps);
4096     } else {
4097       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4098         AlphaRank(moveList[moveNum], 4);
4099         SendToProgram(moveList[moveNum], cps);
4100         AlphaRank(moveList[moveNum], 4); // and back
4101       } else
4102       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4103        * the engine. It would be nice to have a better way to identify castle 
4104        * moves here. */
4105       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4106                                                                          && cps->useOOCastle) {
4107         int fromX = moveList[moveNum][0] - AAA; 
4108         int fromY = moveList[moveNum][1] - ONE;
4109         int toX = moveList[moveNum][2] - AAA; 
4110         int toY = moveList[moveNum][3] - ONE;
4111         if((boards[moveNum][fromY][fromX] == WhiteKing 
4112             && boards[moveNum][toY][toX] == WhiteRook)
4113            || (boards[moveNum][fromY][fromX] == BlackKing 
4114                && boards[moveNum][toY][toX] == BlackRook)) {
4115           if(toX > fromX) SendToProgram("O-O\n", cps);
4116           else SendToProgram("O-O-O\n", cps);
4117         }
4118         else SendToProgram(moveList[moveNum], cps);
4119       }
4120       else SendToProgram(moveList[moveNum], cps);
4121       /* End of additions by Tord */
4122     }
4123
4124     /* [HGM] setting up the opening has brought engine in force mode! */
4125     /*       Send 'go' if we are in a mode where machine should play. */
4126     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4127         (gameMode == TwoMachinesPlay   ||
4128 #ifdef ZIPPY
4129          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4130 #endif
4131          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4132         SendToProgram("go\n", cps);
4133   if (appData.debugMode) {
4134     fprintf(debugFP, "(extra)\n");
4135   }
4136     }
4137     setboardSpoiledMachineBlack = 0;
4138 }
4139
4140 void
4141 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4142      ChessMove moveType;
4143      int fromX, fromY, toX, toY;
4144 {
4145     char user_move[MSG_SIZ];
4146
4147     switch (moveType) {
4148       default:
4149         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4150                 (int)moveType, fromX, fromY, toX, toY);
4151         DisplayError(user_move + strlen("say "), 0);
4152         break;
4153       case WhiteKingSideCastle:
4154       case BlackKingSideCastle:
4155       case WhiteQueenSideCastleWild:
4156       case BlackQueenSideCastleWild:
4157       /* PUSH Fabien */
4158       case WhiteHSideCastleFR:
4159       case BlackHSideCastleFR:
4160       /* POP Fabien */
4161         sprintf(user_move, "o-o\n");
4162         break;
4163       case WhiteQueenSideCastle:
4164       case BlackQueenSideCastle:
4165       case WhiteKingSideCastleWild:
4166       case BlackKingSideCastleWild:
4167       /* PUSH Fabien */
4168       case WhiteASideCastleFR:
4169       case BlackASideCastleFR:
4170       /* POP Fabien */
4171         sprintf(user_move, "o-o-o\n");
4172         break;
4173       case WhitePromotionQueen:
4174       case BlackPromotionQueen:
4175       case WhitePromotionRook:
4176       case BlackPromotionRook:
4177       case WhitePromotionBishop:
4178       case BlackPromotionBishop:
4179       case WhitePromotionKnight:
4180       case BlackPromotionKnight:
4181       case WhitePromotionKing:
4182       case BlackPromotionKing:
4183       case WhitePromotionChancellor:
4184       case BlackPromotionChancellor:
4185       case WhitePromotionArchbishop:
4186       case BlackPromotionArchbishop:
4187         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4188             sprintf(user_move, "%c%c%c%c=%c\n",
4189                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4190                 PieceToChar(WhiteFerz));
4191         else if(gameInfo.variant == VariantGreat)
4192             sprintf(user_move, "%c%c%c%c=%c\n",
4193                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4194                 PieceToChar(WhiteMan));
4195         else
4196             sprintf(user_move, "%c%c%c%c=%c\n",
4197                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4198                 PieceToChar(PromoPiece(moveType)));
4199         break;
4200       case WhiteDrop:
4201       case BlackDrop:
4202         sprintf(user_move, "%c@%c%c\n",
4203                 ToUpper(PieceToChar((ChessSquare) fromX)),
4204                 AAA + toX, ONE + toY);
4205         break;
4206       case NormalMove:
4207       case WhiteCapturesEnPassant:
4208       case BlackCapturesEnPassant:
4209       case IllegalMove:  /* could be a variant we don't quite understand */
4210         sprintf(user_move, "%c%c%c%c\n",
4211                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4212         break;
4213     }
4214     SendToICS(user_move);
4215     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4216         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4217 }
4218
4219 void
4220 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4221      int rf, ff, rt, ft;
4222      char promoChar;
4223      char move[7];
4224 {
4225     if (rf == DROP_RANK) {
4226         sprintf(move, "%c@%c%c\n",
4227                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4228     } else {
4229         if (promoChar == 'x' || promoChar == NULLCHAR) {
4230             sprintf(move, "%c%c%c%c\n",
4231                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4232         } else {
4233             sprintf(move, "%c%c%c%c%c\n",
4234                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4235         }
4236     }
4237 }
4238
4239 void
4240 ProcessICSInitScript(f)
4241      FILE *f;
4242 {
4243     char buf[MSG_SIZ];
4244
4245     while (fgets(buf, MSG_SIZ, f)) {
4246         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4247     }
4248
4249     fclose(f);
4250 }
4251
4252
4253 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4254 void
4255 AlphaRank(char *move, int n)
4256 {
4257 //    char *p = move, c; int x, y;
4258
4259     if (appData.debugMode) {
4260         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4261     }
4262
4263     if(move[1]=='*' && 
4264        move[2]>='0' && move[2]<='9' &&
4265        move[3]>='a' && move[3]<='x'    ) {
4266         move[1] = '@';
4267         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4268         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4269     } else
4270     if(move[0]>='0' && move[0]<='9' &&
4271        move[1]>='a' && move[1]<='x' &&
4272        move[2]>='0' && move[2]<='9' &&
4273        move[3]>='a' && move[3]<='x'    ) {
4274         /* input move, Shogi -> normal */
4275         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4276         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4277         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4278         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4279     } else
4280     if(move[1]=='@' &&
4281        move[3]>='0' && move[3]<='9' &&
4282        move[2]>='a' && move[2]<='x'    ) {
4283         move[1] = '*';
4284         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4285         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4286     } else
4287     if(
4288        move[0]>='a' && move[0]<='x' &&
4289        move[3]>='0' && move[3]<='9' &&
4290        move[2]>='a' && move[2]<='x'    ) {
4291          /* output move, normal -> Shogi */
4292         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4293         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4294         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4295         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4296         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4297     }
4298     if (appData.debugMode) {
4299         fprintf(debugFP, "   out = '%s'\n", move);
4300     }
4301 }
4302
4303 /* Parser for moves from gnuchess, ICS, or user typein box */
4304 Boolean
4305 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4306      char *move;
4307      int moveNum;
4308      ChessMove *moveType;
4309      int *fromX, *fromY, *toX, *toY;
4310      char *promoChar;
4311 {       
4312     if (appData.debugMode) {
4313         fprintf(debugFP, "move to parse: %s\n", move);
4314     }
4315     *moveType = yylexstr(moveNum, move);
4316
4317     switch (*moveType) {
4318       case WhitePromotionChancellor:
4319       case BlackPromotionChancellor:
4320       case WhitePromotionArchbishop:
4321       case BlackPromotionArchbishop:
4322       case WhitePromotionQueen:
4323       case BlackPromotionQueen:
4324       case WhitePromotionRook:
4325       case BlackPromotionRook:
4326       case WhitePromotionBishop:
4327       case BlackPromotionBishop:
4328       case WhitePromotionKnight:
4329       case BlackPromotionKnight:
4330       case WhitePromotionKing:
4331       case BlackPromotionKing:
4332       case NormalMove:
4333       case WhiteCapturesEnPassant:
4334       case BlackCapturesEnPassant:
4335       case WhiteKingSideCastle:
4336       case WhiteQueenSideCastle:
4337       case BlackKingSideCastle:
4338       case BlackQueenSideCastle:
4339       case WhiteKingSideCastleWild:
4340       case WhiteQueenSideCastleWild:
4341       case BlackKingSideCastleWild:
4342       case BlackQueenSideCastleWild:
4343       /* Code added by Tord: */
4344       case WhiteHSideCastleFR:
4345       case WhiteASideCastleFR:
4346       case BlackHSideCastleFR:
4347       case BlackASideCastleFR:
4348       /* End of code added by Tord */
4349       case IllegalMove:         /* bug or odd chess variant */
4350         *fromX = currentMoveString[0] - AAA;
4351         *fromY = currentMoveString[1] - ONE;
4352         *toX = currentMoveString[2] - AAA;
4353         *toY = currentMoveString[3] - ONE;
4354         *promoChar = currentMoveString[4];
4355         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4356             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4357     if (appData.debugMode) {
4358         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4359     }
4360             *fromX = *fromY = *toX = *toY = 0;
4361             return FALSE;
4362         }
4363         if (appData.testLegality) {
4364           return (*moveType != IllegalMove);
4365         } else {
4366           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4367                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4368         }
4369
4370       case WhiteDrop:
4371       case BlackDrop:
4372         *fromX = *moveType == WhiteDrop ?
4373           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4374           (int) CharToPiece(ToLower(currentMoveString[0]));
4375         *fromY = DROP_RANK;
4376         *toX = currentMoveString[2] - AAA;
4377         *toY = currentMoveString[3] - ONE;
4378         *promoChar = NULLCHAR;
4379         return TRUE;
4380
4381       case AmbiguousMove:
4382       case ImpossibleMove:
4383       case (ChessMove) 0:       /* end of file */
4384       case ElapsedTime:
4385       case Comment:
4386       case PGNTag:
4387       case NAG:
4388       case WhiteWins:
4389       case BlackWins:
4390       case GameIsDrawn:
4391       default:
4392     if (appData.debugMode) {
4393         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4394     }
4395         /* bug? */
4396         *fromX = *fromY = *toX = *toY = 0;
4397         *promoChar = NULLCHAR;
4398         return FALSE;
4399     }
4400 }
4401
4402
4403 void
4404 ParsePV(char *pv)
4405 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4406   int fromX, fromY, toX, toY; char promoChar;
4407   ChessMove moveType;
4408   Boolean valid;
4409   int nr = 0;
4410
4411   endPV = forwardMostMove;
4412   do {
4413     while(*pv == ' ') pv++;
4414     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4415     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4416 if(appData.debugMode){
4417 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4418 }
4419     if(!valid && nr == 0 &&
4420        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4421         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4422     }
4423     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4424     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4425     nr++;
4426     if(endPV+1 > framePtr) break; // no space, truncate
4427     if(!valid) break;
4428     endPV++;
4429     CopyBoard(boards[endPV], boards[endPV-1]);
4430     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4431     moveList[endPV-1][0] = fromX + AAA;
4432     moveList[endPV-1][1] = fromY + ONE;
4433     moveList[endPV-1][2] = toX + AAA;
4434     moveList[endPV-1][3] = toY + ONE;
4435     parseList[endPV-1][0] = NULLCHAR;
4436   } while(valid);
4437   currentMove = endPV;
4438   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4439   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4440                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4441   DrawPosition(TRUE, boards[currentMove]);
4442 }
4443
4444 static int lastX, lastY;
4445
4446 Boolean
4447 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4448 {
4449         int startPV;
4450
4451         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4452         lastX = x; lastY = y;
4453         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4454         startPV = index;
4455       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4456       index = startPV;
4457         while(buf[index] && buf[index] != '\n') index++;
4458         buf[index] = 0;
4459         ParsePV(buf+startPV);
4460         *start = startPV; *end = index-1;
4461         return TRUE;
4462 }
4463
4464 Boolean
4465 LoadPV(int x, int y)
4466 { // called on right mouse click to load PV
4467   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4468   lastX = x; lastY = y;
4469   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4470   return TRUE;
4471 }
4472
4473 void
4474 UnLoadPV()
4475 {
4476   if(endPV < 0) return;
4477   endPV = -1;
4478   currentMove = forwardMostMove;
4479   ClearPremoveHighlights();
4480   DrawPosition(TRUE, boards[currentMove]);
4481 }
4482
4483 void
4484 MovePV(int x, int y, int h)
4485 { // step through PV based on mouse coordinates (called on mouse move)
4486   int margin = h>>3, step = 0;
4487
4488   if(endPV < 0) return;
4489   // we must somehow check if right button is still down (might be released off board!)
4490   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4491   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4492   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4493   if(!step) return;
4494   lastX = x; lastY = y;
4495   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4496   currentMove += step;
4497   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4498   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4499                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4500   DrawPosition(FALSE, boards[currentMove]);
4501 }
4502
4503
4504 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4505 // All positions will have equal probability, but the current method will not provide a unique
4506 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4507 #define DARK 1
4508 #define LITE 2
4509 #define ANY 3
4510
4511 int squaresLeft[4];
4512 int piecesLeft[(int)BlackPawn];
4513 int seed, nrOfShuffles;
4514
4515 void GetPositionNumber()
4516 {       // sets global variable seed
4517         int i;
4518
4519         seed = appData.defaultFrcPosition;
4520         if(seed < 0) { // randomize based on time for negative FRC position numbers
4521                 for(i=0; i<50; i++) seed += random();
4522                 seed = random() ^ random() >> 8 ^ random() << 8;
4523                 if(seed<0) seed = -seed;
4524         }
4525 }
4526
4527 int put(Board board, int pieceType, int rank, int n, int shade)
4528 // put the piece on the (n-1)-th empty squares of the given shade
4529 {
4530         int i;
4531
4532         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4533                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4534                         board[rank][i] = (ChessSquare) pieceType;
4535                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4536                         squaresLeft[ANY]--;
4537                         piecesLeft[pieceType]--; 
4538                         return i;
4539                 }
4540         }
4541         return -1;
4542 }
4543
4544
4545 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4546 // calculate where the next piece goes, (any empty square), and put it there
4547 {
4548         int i;
4549
4550         i = seed % squaresLeft[shade];
4551         nrOfShuffles *= squaresLeft[shade];
4552         seed /= squaresLeft[shade];
4553         put(board, pieceType, rank, i, shade);
4554 }
4555
4556 void AddTwoPieces(Board board, int pieceType, int rank)
4557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4558 {
4559         int i, n=squaresLeft[ANY], j=n-1, k;
4560
4561         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4562         i = seed % k;  // pick one
4563         nrOfShuffles *= k;
4564         seed /= k;
4565         while(i >= j) i -= j--;
4566         j = n - 1 - j; i += j;
4567         put(board, pieceType, rank, j, ANY);
4568         put(board, pieceType, rank, i, ANY);
4569 }
4570
4571 void SetUpShuffle(Board board, int number)
4572 {
4573         int i, p, first=1;
4574
4575         GetPositionNumber(); nrOfShuffles = 1;
4576
4577         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4578         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4579         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4580
4581         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4582
4583         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4584             p = (int) board[0][i];
4585             if(p < (int) BlackPawn) piecesLeft[p] ++;
4586             board[0][i] = EmptySquare;
4587         }
4588
4589         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4590             // shuffles restricted to allow normal castling put KRR first
4591             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4592                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4593             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4594                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4595             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4596                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4597             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4598                 put(board, WhiteRook, 0, 0, ANY);
4599             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4600         }
4601
4602         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4603             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4604             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4605                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4606                 while(piecesLeft[p] >= 2) {
4607                     AddOnePiece(board, p, 0, LITE);
4608                     AddOnePiece(board, p, 0, DARK);
4609                 }
4610                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4611             }
4612
4613         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4614             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4615             // but we leave King and Rooks for last, to possibly obey FRC restriction
4616             if(p == (int)WhiteRook) continue;
4617             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4618             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4619         }
4620
4621         // now everything is placed, except perhaps King (Unicorn) and Rooks
4622
4623         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4624             // Last King gets castling rights
4625             while(piecesLeft[(int)WhiteUnicorn]) {
4626                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4627                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4628             }
4629
4630             while(piecesLeft[(int)WhiteKing]) {
4631                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4632                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4633             }
4634
4635
4636         } else {
4637             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4638             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4639         }
4640
4641         // Only Rooks can be left; simply place them all
4642         while(piecesLeft[(int)WhiteRook]) {
4643                 i = put(board, WhiteRook, 0, 0, ANY);
4644                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4645                         if(first) {
4646                                 first=0;
4647                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4648                         }
4649                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4650                 }
4651         }
4652         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4653             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4654         }
4655
4656         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4657 }
4658
4659 int SetCharTable( char *table, const char * map )
4660 /* [HGM] moved here from winboard.c because of its general usefulness */
4661 /*       Basically a safe strcpy that uses the last character as King */
4662 {
4663     int result = FALSE; int NrPieces;
4664
4665     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4666                     && NrPieces >= 12 && !(NrPieces&1)) {
4667         int i; /* [HGM] Accept even length from 12 to 34 */
4668
4669         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4670         for( i=0; i<NrPieces/2-1; i++ ) {
4671             table[i] = map[i];
4672             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4673         }
4674         table[(int) WhiteKing]  = map[NrPieces/2-1];
4675         table[(int) BlackKing]  = map[NrPieces-1];
4676
4677         result = TRUE;
4678     }
4679
4680     return result;
4681 }
4682
4683 void Prelude(Board board)
4684 {       // [HGM] superchess: random selection of exo-pieces
4685         int i, j, k; ChessSquare p; 
4686         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4687
4688         GetPositionNumber(); // use FRC position number
4689
4690         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4691             SetCharTable(pieceToChar, appData.pieceToCharTable);
4692             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4693                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4694         }
4695
4696         j = seed%4;                 seed /= 4; 
4697         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4698         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4699         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4700         j = seed%3 + (seed%3 >= j); seed /= 3; 
4701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4704         j = seed%3;                 seed /= 3; 
4705         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4708         j = seed%2 + (seed%2 >= j); seed /= 2; 
4709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4712         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4713         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4714         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4715         put(board, exoPieces[0],    0, 0, ANY);
4716         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4717 }
4718
4719 void
4720 InitPosition(redraw)
4721      int redraw;
4722 {
4723     ChessSquare (* pieces)[BOARD_FILES];
4724     int i, j, pawnRow, overrule,
4725     oldx = gameInfo.boardWidth,
4726     oldy = gameInfo.boardHeight,
4727     oldh = gameInfo.holdingsWidth,
4728     oldv = gameInfo.variant;
4729
4730     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4731
4732     /* [AS] Initialize pv info list [HGM] and game status */
4733     {
4734         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4735             pvInfoList[i].depth = 0;
4736             boards[i][EP_STATUS] = EP_NONE;
4737             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4738         }
4739
4740         initialRulePlies = 0; /* 50-move counter start */
4741
4742         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4743         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4744     }
4745
4746     
4747     /* [HGM] logic here is completely changed. In stead of full positions */
4748     /* the initialized data only consist of the two backranks. The switch */
4749     /* selects which one we will use, which is than copied to the Board   */
4750     /* initialPosition, which for the rest is initialized by Pawns and    */
4751     /* empty squares. This initial position is then copied to boards[0],  */
4752     /* possibly after shuffling, so that it remains available.            */
4753
4754     gameInfo.holdingsWidth = 0; /* default board sizes */
4755     gameInfo.boardWidth    = 8;
4756     gameInfo.boardHeight   = 8;
4757     gameInfo.holdingsSize  = 0;
4758     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4759     for(i=0; i<BOARD_FILES-2; i++)
4760       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4761     initialPosition[EP_STATUS] = EP_NONE;
4762     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4763
4764     switch (gameInfo.variant) {
4765     case VariantFischeRandom:
4766       shuffleOpenings = TRUE;
4767     default:
4768       pieces = FIDEArray;
4769       break;
4770     case VariantShatranj:
4771       pieces = ShatranjArray;
4772       nrCastlingRights = 0;
4773       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4774       break;
4775     case VariantTwoKings:
4776       pieces = twoKingsArray;
4777       break;
4778     case VariantCapaRandom:
4779       shuffleOpenings = TRUE;
4780     case VariantCapablanca:
4781       pieces = CapablancaArray;
4782       gameInfo.boardWidth = 10;
4783       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4784       break;
4785     case VariantGothic:
4786       pieces = GothicArray;
4787       gameInfo.boardWidth = 10;
4788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4789       break;
4790     case VariantJanus:
4791       pieces = JanusArray;
4792       gameInfo.boardWidth = 10;
4793       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4794       nrCastlingRights = 6;
4795         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4796         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4797         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4798         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4799         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4800         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4801       break;
4802     case VariantFalcon:
4803       pieces = FalconArray;
4804       gameInfo.boardWidth = 10;
4805       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4806       break;
4807     case VariantXiangqi:
4808       pieces = XiangqiArray;
4809       gameInfo.boardWidth  = 9;
4810       gameInfo.boardHeight = 10;
4811       nrCastlingRights = 0;
4812       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4813       break;
4814     case VariantShogi:
4815       pieces = ShogiArray;
4816       gameInfo.boardWidth  = 9;
4817       gameInfo.boardHeight = 9;
4818       gameInfo.holdingsSize = 7;
4819       nrCastlingRights = 0;
4820       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4821       break;
4822     case VariantCourier:
4823       pieces = CourierArray;
4824       gameInfo.boardWidth  = 12;
4825       nrCastlingRights = 0;
4826       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4827       break;
4828     case VariantKnightmate:
4829       pieces = KnightmateArray;
4830       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4831       break;
4832     case VariantFairy:
4833       pieces = fairyArray;
4834       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); 
4835       break;
4836     case VariantGreat:
4837       pieces = GreatArray;
4838       gameInfo.boardWidth = 10;
4839       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4840       gameInfo.holdingsSize = 8;
4841       break;
4842     case VariantSuper:
4843       pieces = FIDEArray;
4844       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4845       gameInfo.holdingsSize = 8;
4846       startedFromSetupPosition = TRUE;
4847       break;
4848     case VariantCrazyhouse:
4849     case VariantBughouse:
4850       pieces = FIDEArray;
4851       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4852       gameInfo.holdingsSize = 5;
4853       break;
4854     case VariantWildCastle:
4855       pieces = FIDEArray;
4856       /* !!?shuffle with kings guaranteed to be on d or e file */
4857       shuffleOpenings = 1;
4858       break;
4859     case VariantNoCastle:
4860       pieces = FIDEArray;
4861       nrCastlingRights = 0;
4862       /* !!?unconstrained back-rank shuffle */
4863       shuffleOpenings = 1;
4864       break;
4865     }
4866
4867     overrule = 0;
4868     if(appData.NrFiles >= 0) {
4869         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4870         gameInfo.boardWidth = appData.NrFiles;
4871     }
4872     if(appData.NrRanks >= 0) {
4873         gameInfo.boardHeight = appData.NrRanks;
4874     }
4875     if(appData.holdingsSize >= 0) {
4876         i = appData.holdingsSize;
4877         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4878         gameInfo.holdingsSize = i;
4879     }
4880     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4881     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4882         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4883
4884     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4885     if(pawnRow < 1) pawnRow = 1;
4886
4887     /* User pieceToChar list overrules defaults */
4888     if(appData.pieceToCharTable != NULL)
4889         SetCharTable(pieceToChar, appData.pieceToCharTable);
4890
4891     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4892
4893         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4894             s = (ChessSquare) 0; /* account holding counts in guard band */
4895         for( i=0; i<BOARD_HEIGHT; i++ )
4896             initialPosition[i][j] = s;
4897
4898         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4899         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4900         initialPosition[pawnRow][j] = WhitePawn;
4901         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4902         if(gameInfo.variant == VariantXiangqi) {
4903             if(j&1) {
4904                 initialPosition[pawnRow][j] = 
4905                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4906                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4907                    initialPosition[2][j] = WhiteCannon;
4908                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4909                 }
4910             }
4911         }
4912         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4913     }
4914     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4915
4916             j=BOARD_LEFT+1;
4917             initialPosition[1][j] = WhiteBishop;
4918             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4919             j=BOARD_RGHT-2;
4920             initialPosition[1][j] = WhiteRook;
4921             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4922     }
4923
4924     if( nrCastlingRights == -1) {
4925         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4926         /*       This sets default castling rights from none to normal corners   */
4927         /* Variants with other castling rights must set them themselves above    */
4928         nrCastlingRights = 6;
4929        
4930         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4931         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4932         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4933         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4934         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4935         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4936      }
4937
4938      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4939      if(gameInfo.variant == VariantGreat) { // promotion commoners
4940         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4941         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4942         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4943         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4944      }
4945   if (appData.debugMode) {
4946     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4947   }
4948     if(shuffleOpenings) {
4949         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4950         startedFromSetupPosition = TRUE;
4951     }
4952     if(startedFromPositionFile) {
4953       /* [HGM] loadPos: use PositionFile for every new game */
4954       CopyBoard(initialPosition, filePosition);
4955       for(i=0; i<nrCastlingRights; i++)
4956           initialRights[i] = filePosition[CASTLING][i];
4957       startedFromSetupPosition = TRUE;
4958     }
4959
4960     CopyBoard(boards[0], initialPosition);
4961
4962     if(oldx != gameInfo.boardWidth ||
4963        oldy != gameInfo.boardHeight ||
4964        oldh != gameInfo.holdingsWidth
4965 #ifdef GOTHIC
4966        || oldv == VariantGothic ||        // For licensing popups
4967        gameInfo.variant == VariantGothic
4968 #endif
4969 #ifdef FALCON
4970        || oldv == VariantFalcon ||
4971        gameInfo.variant == VariantFalcon
4972 #endif
4973                                          )
4974             InitDrawingSizes(-2 ,0);
4975
4976     if (redraw)
4977       DrawPosition(TRUE, boards[currentMove]);
4978 }
4979
4980 void
4981 SendBoard(cps, moveNum)
4982      ChessProgramState *cps;
4983      int moveNum;
4984 {
4985     char message[MSG_SIZ];
4986     
4987     if (cps->useSetboard) {
4988       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4989       sprintf(message, "setboard %s\n", fen);
4990       SendToProgram(message, cps);
4991       free(fen);
4992
4993     } else {
4994       ChessSquare *bp;
4995       int i, j;
4996       /* Kludge to set black to move, avoiding the troublesome and now
4997        * deprecated "black" command.
4998        */
4999       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5000
5001       SendToProgram("edit\n", cps);
5002       SendToProgram("#\n", cps);
5003       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5004         bp = &boards[moveNum][i][BOARD_LEFT];
5005         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5006           if ((int) *bp < (int) BlackPawn) {
5007             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5008                     AAA + j, ONE + i);
5009             if(message[0] == '+' || message[0] == '~') {
5010                 sprintf(message, "%c%c%c+\n",
5011                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5012                         AAA + j, ONE + i);
5013             }
5014             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5015                 message[1] = BOARD_RGHT   - 1 - j + '1';
5016                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5017             }
5018             SendToProgram(message, cps);
5019           }
5020         }
5021       }
5022     
5023       SendToProgram("c\n", cps);
5024       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5025         bp = &boards[moveNum][i][BOARD_LEFT];
5026         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5027           if (((int) *bp != (int) EmptySquare)
5028               && ((int) *bp >= (int) BlackPawn)) {
5029             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5030                     AAA + j, ONE + i);
5031             if(message[0] == '+' || message[0] == '~') {
5032                 sprintf(message, "%c%c%c+\n",
5033                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5034                         AAA + j, ONE + i);
5035             }
5036             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5037                 message[1] = BOARD_RGHT   - 1 - j + '1';
5038                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5039             }
5040             SendToProgram(message, cps);
5041           }
5042         }
5043       }
5044     
5045       SendToProgram(".\n", cps);
5046     }
5047     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5048 }
5049
5050 int
5051 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5052 {
5053     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5054     /* [HGM] add Shogi promotions */
5055     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5056     ChessSquare piece;
5057     ChessMove moveType;
5058     Boolean premove;
5059
5060     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5061     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5062
5063     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5064       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5065         return FALSE;
5066
5067     piece = boards[currentMove][fromY][fromX];
5068     if(gameInfo.variant == VariantShogi) {
5069         promotionZoneSize = 3;
5070         highestPromotingPiece = (int)WhiteFerz;
5071     }
5072
5073     // next weed out all moves that do not touch the promotion zone at all
5074     if((int)piece >= BlackPawn) {
5075         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5076              return FALSE;
5077         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5078     } else {
5079         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5080            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5081     }
5082
5083     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5084
5085     // weed out mandatory Shogi promotions
5086     if(gameInfo.variant == VariantShogi) {
5087         if(piece >= BlackPawn) {
5088             if(toY == 0 && piece == BlackPawn ||
5089                toY == 0 && piece == BlackQueen ||
5090                toY <= 1 && piece == BlackKnight) {
5091                 *promoChoice = '+';
5092                 return FALSE;
5093             }
5094         } else {
5095             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5096                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5097                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5098                 *promoChoice = '+';
5099                 return FALSE;
5100             }
5101         }
5102     }
5103
5104     // weed out obviously illegal Pawn moves
5105     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5106         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5107         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5108         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5109         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5110         // note we are not allowed to test for valid (non-)capture, due to premove
5111     }
5112
5113     // we either have a choice what to promote to, or (in Shogi) whether to promote
5114     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5115         *promoChoice = PieceToChar(BlackFerz);  // no choice
5116         return FALSE;
5117     }
5118     if(appData.alwaysPromoteToQueen) { // predetermined
5119         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5120              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5121         else *promoChoice = PieceToChar(BlackQueen);
5122         return FALSE;
5123     }
5124
5125     // suppress promotion popup on illegal moves that are not premoves
5126     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5127               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5128     if(appData.testLegality && !premove) {
5129         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5130                         fromY, fromX, toY, toX, NULLCHAR);
5131         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5132            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5133             return FALSE;
5134     }
5135
5136     return TRUE;
5137 }
5138
5139 int
5140 InPalace(row, column)
5141      int row, column;
5142 {   /* [HGM] for Xiangqi */
5143     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5144          column < (BOARD_WIDTH + 4)/2 &&
5145          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5146     return FALSE;
5147 }
5148
5149 int
5150 PieceForSquare (x, y)
5151      int x;
5152      int y;
5153 {
5154   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5155      return -1;
5156   else
5157      return boards[currentMove][y][x];
5158 }
5159
5160 int
5161 OKToStartUserMove(x, y)
5162      int x, y;
5163 {
5164     ChessSquare from_piece;
5165     int white_piece;
5166
5167     if (matchMode) return FALSE;
5168     if (gameMode == EditPosition) return TRUE;
5169
5170     if (x >= 0 && y >= 0)
5171       from_piece = boards[currentMove][y][x];
5172     else
5173       from_piece = EmptySquare;
5174
5175     if (from_piece == EmptySquare) return FALSE;
5176
5177     white_piece = (int)from_piece >= (int)WhitePawn &&
5178       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5179
5180     switch (gameMode) {
5181       case PlayFromGameFile:
5182       case AnalyzeFile:
5183       case TwoMachinesPlay:
5184       case EndOfGame:
5185         return FALSE;
5186
5187       case IcsObserving:
5188       case IcsIdle:
5189         return FALSE;
5190
5191       case MachinePlaysWhite:
5192       case IcsPlayingBlack:
5193         if (appData.zippyPlay) return FALSE;
5194         if (white_piece) {
5195             DisplayMoveError(_("You are playing Black"));
5196             return FALSE;
5197         }
5198         break;
5199
5200       case MachinePlaysBlack:
5201       case IcsPlayingWhite:
5202         if (appData.zippyPlay) return FALSE;
5203         if (!white_piece) {
5204             DisplayMoveError(_("You are playing White"));
5205             return FALSE;
5206         }
5207         break;
5208
5209       case EditGame:
5210         if (!white_piece && WhiteOnMove(currentMove)) {
5211             DisplayMoveError(_("It is White's turn"));
5212             return FALSE;
5213         }           
5214         if (white_piece && !WhiteOnMove(currentMove)) {
5215             DisplayMoveError(_("It is Black's turn"));
5216             return FALSE;
5217         }           
5218         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5219             /* Editing correspondence game history */
5220             /* Could disallow this or prompt for confirmation */
5221             cmailOldMove = -1;
5222         }
5223         break;
5224
5225       case BeginningOfGame:
5226         if (appData.icsActive) return FALSE;
5227         if (!appData.noChessProgram) {
5228             if (!white_piece) {
5229                 DisplayMoveError(_("You are playing White"));
5230                 return FALSE;
5231             }
5232         }
5233         break;
5234         
5235       case Training:
5236         if (!white_piece && WhiteOnMove(currentMove)) {
5237             DisplayMoveError(_("It is White's turn"));
5238             return FALSE;
5239         }           
5240         if (white_piece && !WhiteOnMove(currentMove)) {
5241             DisplayMoveError(_("It is Black's turn"));
5242             return FALSE;
5243         }           
5244         break;
5245
5246       default:
5247       case IcsExamining:
5248         break;
5249     }
5250     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5251         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5252         && gameMode != AnalyzeFile && gameMode != Training) {
5253         DisplayMoveError(_("Displayed position is not current"));
5254         return FALSE;
5255     }
5256     return TRUE;
5257 }
5258
5259 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5260 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5261 int lastLoadGameUseList = FALSE;
5262 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5263 ChessMove lastLoadGameStart = (ChessMove) 0;
5264
5265 ChessMove
5266 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5267      int fromX, fromY, toX, toY;
5268      int promoChar;
5269      Boolean captureOwn;
5270 {
5271     ChessMove moveType;
5272     ChessSquare pdown, pup;
5273
5274     /* Check if the user is playing in turn.  This is complicated because we
5275        let the user "pick up" a piece before it is his turn.  So the piece he
5276        tried to pick up may have been captured by the time he puts it down!
5277        Therefore we use the color the user is supposed to be playing in this
5278        test, not the color of the piece that is currently on the starting
5279        square---except in EditGame mode, where the user is playing both
5280        sides; fortunately there the capture race can't happen.  (It can
5281        now happen in IcsExamining mode, but that's just too bad.  The user
5282        will get a somewhat confusing message in that case.)
5283        */
5284
5285     switch (gameMode) {
5286       case PlayFromGameFile:
5287       case AnalyzeFile:
5288       case TwoMachinesPlay:
5289       case EndOfGame:
5290       case IcsObserving:
5291       case IcsIdle:
5292         /* We switched into a game mode where moves are not accepted,
5293            perhaps while the mouse button was down. */
5294         return ImpossibleMove;
5295
5296       case MachinePlaysWhite:
5297         /* User is moving for Black */
5298         if (WhiteOnMove(currentMove)) {
5299             DisplayMoveError(_("It is White's turn"));
5300             return ImpossibleMove;
5301         }
5302         break;
5303
5304       case MachinePlaysBlack:
5305         /* User is moving for White */
5306         if (!WhiteOnMove(currentMove)) {
5307             DisplayMoveError(_("It is Black's turn"));
5308             return ImpossibleMove;
5309         }
5310         break;
5311
5312       case EditGame:
5313       case IcsExamining:
5314       case BeginningOfGame:
5315       case AnalyzeMode:
5316       case Training:
5317         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5318             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5319             /* User is moving for Black */
5320             if (WhiteOnMove(currentMove)) {
5321                 DisplayMoveError(_("It is White's turn"));
5322                 return ImpossibleMove;
5323             }
5324         } else {
5325             /* User is moving for White */
5326             if (!WhiteOnMove(currentMove)) {
5327                 DisplayMoveError(_("It is Black's turn"));
5328                 return ImpossibleMove;
5329             }
5330         }
5331         break;
5332
5333       case IcsPlayingBlack:
5334         /* User is moving for Black */
5335         if (WhiteOnMove(currentMove)) {
5336             if (!appData.premove) {
5337                 DisplayMoveError(_("It is White's turn"));
5338             } else if (toX >= 0 && toY >= 0) {
5339                 premoveToX = toX;
5340                 premoveToY = toY;
5341                 premoveFromX = fromX;
5342                 premoveFromY = fromY;
5343                 premovePromoChar = promoChar;
5344                 gotPremove = 1;
5345                 if (appData.debugMode) 
5346                     fprintf(debugFP, "Got premove: fromX %d,"
5347                             "fromY %d, toX %d, toY %d\n",
5348                             fromX, fromY, toX, toY);
5349             }
5350             return ImpossibleMove;
5351         }
5352         break;
5353
5354       case IcsPlayingWhite:
5355         /* User is moving for White */
5356         if (!WhiteOnMove(currentMove)) {
5357             if (!appData.premove) {
5358                 DisplayMoveError(_("It is Black's turn"));
5359             } else if (toX >= 0 && toY >= 0) {
5360                 premoveToX = toX;
5361                 premoveToY = toY;
5362                 premoveFromX = fromX;
5363                 premoveFromY = fromY;
5364                 premovePromoChar = promoChar;
5365                 gotPremove = 1;
5366                 if (appData.debugMode) 
5367                     fprintf(debugFP, "Got premove: fromX %d,"
5368                             "fromY %d, toX %d, toY %d\n",
5369                             fromX, fromY, toX, toY);
5370             }
5371             return ImpossibleMove;
5372         }
5373         break;
5374
5375       default:
5376         break;
5377
5378       case EditPosition:
5379         /* EditPosition, empty square, or different color piece;
5380            click-click move is possible */
5381         if (toX == -2 || toY == -2) {
5382             boards[0][fromY][fromX] = EmptySquare;
5383             return AmbiguousMove;
5384         } else if (toX >= 0 && toY >= 0) {
5385             boards[0][toY][toX] = boards[0][fromY][fromX];
5386             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5387                 if(boards[0][fromY][0] != EmptySquare) {
5388                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5389                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5390                 }
5391             } else
5392             if(fromX == BOARD_RGHT+1) {
5393                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5394                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5395                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5396                 }
5397             } else
5398             boards[0][fromY][fromX] = EmptySquare;
5399             return AmbiguousMove;
5400         }
5401         return ImpossibleMove;
5402     }
5403
5404     if(toX < 0 || toY < 0) return ImpossibleMove;
5405     pdown = boards[currentMove][fromY][fromX];
5406     pup = boards[currentMove][toY][toX];
5407
5408     /* [HGM] If move started in holdings, it means a drop */
5409     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5410          if( pup != EmptySquare ) return ImpossibleMove;
5411          if(appData.testLegality) {
5412              /* it would be more logical if LegalityTest() also figured out
5413               * which drops are legal. For now we forbid pawns on back rank.
5414               * Shogi is on its own here...
5415               */
5416              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5417                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5418                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5419          }
5420          return WhiteDrop; /* Not needed to specify white or black yet */
5421     }
5422
5423     userOfferedDraw = FALSE;
5424         
5425     /* [HGM] always test for legality, to get promotion info */
5426     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5427                                          fromY, fromX, toY, toX, promoChar);
5428     /* [HGM] but possibly ignore an IllegalMove result */
5429     if (appData.testLegality) {
5430         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5431             DisplayMoveError(_("Illegal move"));
5432             return ImpossibleMove;
5433         }
5434     }
5435
5436     return moveType;
5437     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5438        function is made into one that returns an OK move type if FinishMove
5439        should be called. This to give the calling driver routine the
5440        opportunity to finish the userMove input with a promotion popup,
5441        without bothering the user with this for invalid or illegal moves */
5442
5443 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5444 }
5445
5446 /* Common tail of UserMoveEvent and DropMenuEvent */
5447 int
5448 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5449      ChessMove moveType;
5450      int fromX, fromY, toX, toY;
5451      /*char*/int promoChar;
5452 {
5453     char *bookHit = 0;
5454
5455     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5456         // [HGM] superchess: suppress promotions to non-available piece
5457         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5458         if(WhiteOnMove(currentMove)) {
5459             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5460         } else {
5461             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5462         }
5463     }
5464
5465     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5466        move type in caller when we know the move is a legal promotion */
5467     if(moveType == NormalMove && promoChar)
5468         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5469
5470     /* [HGM] convert drag-and-drop piece drops to standard form */
5471     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5472          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5473            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5474                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5475            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5476            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5477            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5478            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5479          fromY = DROP_RANK;
5480     }
5481
5482     /* [HGM] <popupFix> The following if has been moved here from
5483        UserMoveEvent(). Because it seemed to belong here (why not allow
5484        piece drops in training games?), and because it can only be
5485        performed after it is known to what we promote. */
5486     if (gameMode == Training) {
5487       /* compare the move played on the board to the next move in the
5488        * game. If they match, display the move and the opponent's response. 
5489        * If they don't match, display an error message.
5490        */
5491       int saveAnimate;
5492       Board testBoard;
5493       CopyBoard(testBoard, boards[currentMove]);
5494       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5495
5496       if (CompareBoards(testBoard, boards[currentMove+1])) {
5497         ForwardInner(currentMove+1);
5498
5499         /* Autoplay the opponent's response.
5500          * if appData.animate was TRUE when Training mode was entered,
5501          * the response will be animated.
5502          */
5503         saveAnimate = appData.animate;
5504         appData.animate = animateTraining;
5505         ForwardInner(currentMove+1);
5506         appData.animate = saveAnimate;
5507
5508         /* check for the end of the game */
5509         if (currentMove >= forwardMostMove) {
5510           gameMode = PlayFromGameFile;
5511           ModeHighlight();
5512           SetTrainingModeOff();
5513           DisplayInformation(_("End of game"));
5514         }
5515       } else {
5516         DisplayError(_("Incorrect move"), 0);
5517       }
5518       return 1;
5519     }
5520
5521   /* Ok, now we know that the move is good, so we can kill
5522      the previous line in Analysis Mode */
5523   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5524                                 && currentMove < forwardMostMove) {
5525     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5526   }
5527
5528   /* If we need the chess program but it's dead, restart it */
5529   ResurrectChessProgram();
5530
5531   /* A user move restarts a paused game*/
5532   if (pausing)
5533     PauseEvent();
5534
5535   thinkOutput[0] = NULLCHAR;
5536
5537   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5538
5539   if (gameMode == BeginningOfGame) {
5540     if (appData.noChessProgram) {
5541       gameMode = EditGame;
5542       SetGameInfo();
5543     } else {
5544       char buf[MSG_SIZ];
5545       gameMode = MachinePlaysBlack;
5546       StartClocks();
5547       SetGameInfo();
5548       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5549       DisplayTitle(buf);
5550       if (first.sendName) {
5551         sprintf(buf, "name %s\n", gameInfo.white);
5552         SendToProgram(buf, &first);
5553       }
5554       StartClocks();
5555     }
5556     ModeHighlight();
5557   }
5558
5559   /* Relay move to ICS or chess engine */
5560   if (appData.icsActive) {
5561     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5562         gameMode == IcsExamining) {
5563       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5564       ics_user_moved = 1;
5565     }
5566   } else {
5567     if (first.sendTime && (gameMode == BeginningOfGame ||
5568                            gameMode == MachinePlaysWhite ||
5569                            gameMode == MachinePlaysBlack)) {
5570       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5571     }
5572     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5573          // [HGM] book: if program might be playing, let it use book
5574         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5575         first.maybeThinking = TRUE;
5576     } else SendMoveToProgram(forwardMostMove-1, &first);
5577     if (currentMove == cmailOldMove + 1) {
5578       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5579     }
5580   }
5581
5582   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5583
5584   switch (gameMode) {
5585   case EditGame:
5586     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5587     case MT_NONE:
5588     case MT_CHECK:
5589       break;
5590     case MT_CHECKMATE:
5591     case MT_STAINMATE:
5592       if (WhiteOnMove(currentMove)) {
5593         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5594       } else {
5595         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5596       }
5597       break;
5598     case MT_STALEMATE:
5599       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5600       break;
5601     }
5602     break;
5603     
5604   case MachinePlaysBlack:
5605   case MachinePlaysWhite:
5606     /* disable certain menu options while machine is thinking */
5607     SetMachineThinkingEnables();
5608     break;
5609
5610   default:
5611     break;
5612   }
5613
5614   if(bookHit) { // [HGM] book: simulate book reply
5615         static char bookMove[MSG_SIZ]; // a bit generous?
5616
5617         programStats.nodes = programStats.depth = programStats.time = 
5618         programStats.score = programStats.got_only_move = 0;
5619         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5620
5621         strcpy(bookMove, "move ");
5622         strcat(bookMove, bookHit);
5623         HandleMachineMove(bookMove, &first);
5624   }
5625   return 1;
5626 }
5627
5628 void
5629 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5630      int fromX, fromY, toX, toY;
5631      int promoChar;
5632 {
5633     /* [HGM] This routine was added to allow calling of its two logical
5634        parts from other modules in the old way. Before, UserMoveEvent()
5635        automatically called FinishMove() if the move was OK, and returned
5636        otherwise. I separated the two, in order to make it possible to
5637        slip a promotion popup in between. But that it always needs two
5638        calls, to the first part, (now called UserMoveTest() ), and to
5639        FinishMove if the first part succeeded. Calls that do not need
5640        to do anything in between, can call this routine the old way. 
5641     */
5642     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5643 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5644     if(moveType == AmbiguousMove)
5645         DrawPosition(FALSE, boards[currentMove]);
5646     else if(moveType != ImpossibleMove && moveType != Comment)
5647         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5648 }
5649
5650 void
5651 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5652      Board board;
5653      int flags;
5654      ChessMove kind;
5655      int rf, ff, rt, ft;
5656      VOIDSTAR closure;
5657 {
5658     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5659     Markers *m = (Markers *) closure;
5660     if(rf == fromY && ff == fromX)
5661         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5662                          || kind == WhiteCapturesEnPassant
5663                          || kind == BlackCapturesEnPassant);
5664     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5665 }
5666
5667 void
5668 MarkTargetSquares(int clear)
5669 {
5670   int x, y;
5671   if(!appData.markers || !appData.highlightDragging || 
5672      !appData.testLegality || gameMode == EditPosition) return;
5673   if(clear) {
5674     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5675   } else {
5676     int capt = 0;
5677     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5678     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5679       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5680       if(capt)
5681       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5682     }
5683   }
5684   DrawPosition(TRUE, NULL);
5685 }
5686
5687 void LeftClick(ClickType clickType, int xPix, int yPix)
5688 {
5689     int x, y;
5690     Boolean saveAnimate;
5691     static int second = 0, promotionChoice = 0;
5692     char promoChoice = NULLCHAR;
5693
5694     if (clickType == Press) ErrorPopDown();
5695     MarkTargetSquares(1);
5696
5697     x = EventToSquare(xPix, BOARD_WIDTH);
5698     y = EventToSquare(yPix, BOARD_HEIGHT);
5699     if (!flipView && y >= 0) {
5700         y = BOARD_HEIGHT - 1 - y;
5701     }
5702     if (flipView && x >= 0) {
5703         x = BOARD_WIDTH - 1 - x;
5704     }
5705
5706     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5707         if(clickType == Release) return; // ignore upclick of click-click destination
5708         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5709         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5710         if(gameInfo.holdingsWidth && 
5711                 (WhiteOnMove(currentMove) 
5712                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5713                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5714             // click in right holdings, for determining promotion piece
5715             ChessSquare p = boards[currentMove][y][x];
5716             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5717             if(p != EmptySquare) {
5718                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5719                 fromX = fromY = -1;
5720                 return;
5721             }
5722         }
5723         DrawPosition(FALSE, boards[currentMove]);
5724         return;
5725     }
5726
5727     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5728     if(clickType == Press
5729             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5730               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5731               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5732         return;
5733
5734     if (fromX == -1) {
5735         if (clickType == Press) {
5736             /* First square */
5737             if (OKToStartUserMove(x, y)) {
5738                 fromX = x;
5739                 fromY = y;
5740                 second = 0;
5741                 MarkTargetSquares(0);
5742                 DragPieceBegin(xPix, yPix);
5743                 if (appData.highlightDragging) {
5744                     SetHighlights(x, y, -1, -1);
5745                 }
5746             }
5747         }
5748         return;
5749     }
5750
5751     /* fromX != -1 */
5752     if (clickType == Press && gameMode != EditPosition) {
5753         ChessSquare fromP;
5754         ChessSquare toP;
5755         int frc;
5756
5757         // ignore off-board to clicks
5758         if(y < 0 || x < 0) return;
5759
5760         /* Check if clicking again on the same color piece */
5761         fromP = boards[currentMove][fromY][fromX];
5762         toP = boards[currentMove][y][x];
5763         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5764         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5765              WhitePawn <= toP && toP <= WhiteKing &&
5766              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5767              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5768             (BlackPawn <= fromP && fromP <= BlackKing && 
5769              BlackPawn <= toP && toP <= BlackKing &&
5770              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5771              !(fromP == BlackKing && toP == BlackRook && frc))) {
5772             /* Clicked again on same color piece -- changed his mind */
5773             second = (x == fromX && y == fromY);
5774             if (appData.highlightDragging) {
5775                 SetHighlights(x, y, -1, -1);
5776             } else {
5777                 ClearHighlights();
5778             }
5779             if (OKToStartUserMove(x, y)) {
5780                 fromX = x;
5781                 fromY = y;
5782                 MarkTargetSquares(0);
5783                 DragPieceBegin(xPix, yPix);
5784             }
5785             return;
5786         }
5787         // ignore clicks on holdings
5788         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5789     }
5790
5791     if (clickType == Release && x == fromX && y == fromY) {
5792         DragPieceEnd(xPix, yPix);
5793         if (appData.animateDragging) {
5794             /* Undo animation damage if any */
5795             DrawPosition(FALSE, NULL);
5796         }
5797         if (second) {
5798             /* Second up/down in same square; just abort move */
5799             second = 0;
5800             fromX = fromY = -1;
5801             ClearHighlights();
5802             gotPremove = 0;
5803             ClearPremoveHighlights();
5804         } else {
5805             /* First upclick in same square; start click-click mode */
5806             SetHighlights(x, y, -1, -1);
5807         }
5808         return;
5809     }
5810
5811     /* we now have a different from- and (possibly off-board) to-square */
5812     /* Completed move */
5813     toX = x;
5814     toY = y;
5815     saveAnimate = appData.animate;
5816     if (clickType == Press) {
5817         /* Finish clickclick move */
5818         if (appData.animate || appData.highlightLastMove) {
5819             SetHighlights(fromX, fromY, toX, toY);
5820         } else {
5821             ClearHighlights();
5822         }
5823     } else {
5824         /* Finish drag move */
5825         if (appData.highlightLastMove) {
5826             SetHighlights(fromX, fromY, toX, toY);
5827         } else {
5828             ClearHighlights();
5829         }
5830         DragPieceEnd(xPix, yPix);
5831         /* Don't animate move and drag both */
5832         appData.animate = FALSE;
5833     }
5834
5835     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5836     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5837         ChessSquare piece = boards[currentMove][fromY][fromX];
5838         if(gameMode == EditPosition && piece != EmptySquare &&
5839            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5840             int n;
5841              
5842             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5843                 n = PieceToNumber(piece - (int)BlackPawn);
5844                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5845                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5846                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5847             } else
5848             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5849                 n = PieceToNumber(piece);
5850                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5851                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5852                 boards[currentMove][n][BOARD_WIDTH-2]++;
5853             }
5854             boards[currentMove][fromY][fromX] = EmptySquare;
5855         }
5856         ClearHighlights();
5857         fromX = fromY = -1;
5858         DrawPosition(TRUE, boards[currentMove]);
5859         return;
5860     }
5861
5862     // off-board moves should not be highlighted
5863     if(x < 0 || x < 0) ClearHighlights();
5864
5865     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5866         SetHighlights(fromX, fromY, toX, toY);
5867         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5868             // [HGM] super: promotion to captured piece selected from holdings
5869             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5870             promotionChoice = TRUE;
5871             // kludge follows to temporarily execute move on display, without promoting yet
5872             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5873             boards[currentMove][toY][toX] = p;
5874             DrawPosition(FALSE, boards[currentMove]);
5875             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5876             boards[currentMove][toY][toX] = q;
5877             DisplayMessage("Click in holdings to choose piece", "");
5878             return;
5879         }
5880         PromotionPopUp();
5881     } else {
5882         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5883         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5884         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5885         fromX = fromY = -1;
5886     }
5887     appData.animate = saveAnimate;
5888     if (appData.animate || appData.animateDragging) {
5889         /* Undo animation damage if needed */
5890         DrawPosition(FALSE, NULL);
5891     }
5892 }
5893
5894 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5895 {
5896 //    char * hint = lastHint;
5897     FrontEndProgramStats stats;
5898
5899     stats.which = cps == &first ? 0 : 1;
5900     stats.depth = cpstats->depth;
5901     stats.nodes = cpstats->nodes;
5902     stats.score = cpstats->score;
5903     stats.time = cpstats->time;
5904     stats.pv = cpstats->movelist;
5905     stats.hint = lastHint;
5906     stats.an_move_index = 0;
5907     stats.an_move_count = 0;
5908
5909     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5910         stats.hint = cpstats->move_name;
5911         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5912         stats.an_move_count = cpstats->nr_moves;
5913     }
5914
5915     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5916
5917     SetProgramStats( &stats );
5918 }
5919
5920 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5921 {   // [HGM] book: this routine intercepts moves to simulate book replies
5922     char *bookHit = NULL;
5923
5924     //first determine if the incoming move brings opponent into his book
5925     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5926         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5927     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5928     if(bookHit != NULL && !cps->bookSuspend) {
5929         // make sure opponent is not going to reply after receiving move to book position
5930         SendToProgram("force\n", cps);
5931         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5932     }
5933     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5934     // now arrange restart after book miss
5935     if(bookHit) {
5936         // after a book hit we never send 'go', and the code after the call to this routine
5937         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5938         char buf[MSG_SIZ];
5939         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5940         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5941         SendToProgram(buf, cps);
5942         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5943     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5944         SendToProgram("go\n", cps);
5945         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5946     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5947         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5948             SendToProgram("go\n", cps); 
5949         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5950     }
5951     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5952 }
5953
5954 char *savedMessage;
5955 ChessProgramState *savedState;
5956 void DeferredBookMove(void)
5957 {
5958         if(savedState->lastPing != savedState->lastPong)
5959                     ScheduleDelayedEvent(DeferredBookMove, 10);
5960         else
5961         HandleMachineMove(savedMessage, savedState);
5962 }
5963
5964 void
5965 HandleMachineMove(message, cps)
5966      char *message;
5967      ChessProgramState *cps;
5968 {
5969     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5970     char realname[MSG_SIZ];
5971     int fromX, fromY, toX, toY;
5972     ChessMove moveType;
5973     char promoChar;
5974     char *p;
5975     int machineWhite;
5976     char *bookHit;
5977
5978     cps->userError = 0;
5979
5980 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5981     /*
5982      * Kludge to ignore BEL characters
5983      */
5984     while (*message == '\007') message++;
5985
5986     /*
5987      * [HGM] engine debug message: ignore lines starting with '#' character
5988      */
5989     if(cps->debug && *message == '#') return;
5990
5991     /*
5992      * Look for book output
5993      */
5994     if (cps == &first && bookRequested) {
5995         if (message[0] == '\t' || message[0] == ' ') {
5996             /* Part of the book output is here; append it */
5997             strcat(bookOutput, message);
5998             strcat(bookOutput, "  \n");
5999             return;
6000         } else if (bookOutput[0] != NULLCHAR) {
6001             /* All of book output has arrived; display it */
6002             char *p = bookOutput;
6003             while (*p != NULLCHAR) {
6004                 if (*p == '\t') *p = ' ';
6005                 p++;
6006             }
6007             DisplayInformation(bookOutput);
6008             bookRequested = FALSE;
6009             /* Fall through to parse the current output */
6010         }
6011     }
6012
6013     /*
6014      * Look for machine move.
6015      */
6016     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6017         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6018     {
6019         /* This method is only useful on engines that support ping */
6020         if (cps->lastPing != cps->lastPong) {
6021           if (gameMode == BeginningOfGame) {
6022             /* Extra move from before last new; ignore */
6023             if (appData.debugMode) {
6024                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6025             }
6026           } else {
6027             if (appData.debugMode) {
6028                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6029                         cps->which, gameMode);
6030             }
6031
6032             SendToProgram("undo\n", cps);
6033           }
6034           return;
6035         }
6036
6037         switch (gameMode) {
6038           case BeginningOfGame:
6039             /* Extra move from before last reset; ignore */
6040             if (appData.debugMode) {
6041                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6042             }
6043             return;
6044
6045           case EndOfGame:
6046           case IcsIdle:
6047           default:
6048             /* Extra move after we tried to stop.  The mode test is
6049                not a reliable way of detecting this problem, but it's
6050                the best we can do on engines that don't support ping.
6051             */
6052             if (appData.debugMode) {
6053                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6054                         cps->which, gameMode);
6055             }
6056             SendToProgram("undo\n", cps);
6057             return;
6058
6059           case MachinePlaysWhite:
6060           case IcsPlayingWhite:
6061             machineWhite = TRUE;
6062             break;
6063
6064           case MachinePlaysBlack:
6065           case IcsPlayingBlack:
6066             machineWhite = FALSE;
6067             break;
6068
6069           case TwoMachinesPlay:
6070             machineWhite = (cps->twoMachinesColor[0] == 'w');
6071             break;
6072         }
6073         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6074             if (appData.debugMode) {
6075                 fprintf(debugFP,
6076                         "Ignoring move out of turn by %s, gameMode %d"
6077                         ", forwardMost %d\n",
6078                         cps->which, gameMode, forwardMostMove);
6079             }
6080             return;
6081         }
6082
6083     if (appData.debugMode) { int f = forwardMostMove;
6084         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6085                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6086                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6087     }
6088         if(cps->alphaRank) AlphaRank(machineMove, 4);
6089         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6090                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6091             /* Machine move could not be parsed; ignore it. */
6092             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6093                     machineMove, cps->which);
6094             DisplayError(buf1, 0);
6095             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6096                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6097             if (gameMode == TwoMachinesPlay) {
6098               GameEnds(machineWhite ? BlackWins : WhiteWins,
6099                        buf1, GE_XBOARD);
6100             }
6101             return;
6102         }
6103
6104         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6105         /* So we have to redo legality test with true e.p. status here,  */
6106         /* to make sure an illegal e.p. capture does not slip through,   */
6107         /* to cause a forfeit on a justified illegal-move complaint      */
6108         /* of the opponent.                                              */
6109         if( gameMode==TwoMachinesPlay && appData.testLegality
6110             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6111                                                               ) {
6112            ChessMove moveType;
6113            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6114                              fromY, fromX, toY, toX, promoChar);
6115             if (appData.debugMode) {
6116                 int i;
6117                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6118                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6119                 fprintf(debugFP, "castling rights\n");
6120             }
6121             if(moveType == IllegalMove) {
6122                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6123                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6124                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6125                            buf1, GE_XBOARD);
6126                 return;
6127            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6128            /* [HGM] Kludge to handle engines that send FRC-style castling
6129               when they shouldn't (like TSCP-Gothic) */
6130            switch(moveType) {
6131              case WhiteASideCastleFR:
6132              case BlackASideCastleFR:
6133                toX+=2;
6134                currentMoveString[2]++;
6135                break;
6136              case WhiteHSideCastleFR:
6137              case BlackHSideCastleFR:
6138                toX--;
6139                currentMoveString[2]--;
6140                break;
6141              default: ; // nothing to do, but suppresses warning of pedantic compilers
6142            }
6143         }
6144         hintRequested = FALSE;
6145         lastHint[0] = NULLCHAR;
6146         bookRequested = FALSE;
6147         /* Program may be pondering now */
6148         cps->maybeThinking = TRUE;
6149         if (cps->sendTime == 2) cps->sendTime = 1;
6150         if (cps->offeredDraw) cps->offeredDraw--;
6151
6152 #if ZIPPY
6153         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6154             first.initDone) {
6155           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6156           ics_user_moved = 1;
6157           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6158                 char buf[3*MSG_SIZ];
6159
6160                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6161                         programStats.score / 100.,
6162                         programStats.depth,
6163                         programStats.time / 100.,
6164                         (unsigned int)programStats.nodes,
6165                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6166                         programStats.movelist);
6167                 SendToICS(buf);
6168 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6169           }
6170         }
6171 #endif
6172         /* currentMoveString is set as a side-effect of ParseOneMove */
6173         strcpy(machineMove, currentMoveString);
6174         strcat(machineMove, "\n");
6175         strcpy(moveList[forwardMostMove], machineMove);
6176
6177         /* [AS] Save move info and clear stats for next move */
6178         pvInfoList[ forwardMostMove ].score = programStats.score;
6179         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6180         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6181         ClearProgramStats();
6182         thinkOutput[0] = NULLCHAR;
6183         hiddenThinkOutputState = 0;
6184
6185         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6186
6187         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6188         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6189             int count = 0;
6190
6191             while( count < adjudicateLossPlies ) {
6192                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6193
6194                 if( count & 1 ) {
6195                     score = -score; /* Flip score for winning side */
6196                 }
6197
6198                 if( score > adjudicateLossThreshold ) {
6199                     break;
6200                 }
6201
6202                 count++;
6203             }
6204
6205             if( count >= adjudicateLossPlies ) {
6206                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6207
6208                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6209                     "Xboard adjudication", 
6210                     GE_XBOARD );
6211
6212                 return;
6213             }
6214         }
6215
6216         if( gameMode == TwoMachinesPlay ) {
6217           // [HGM] some adjudications useful with buggy engines
6218             int k, count = 0; static int bare = 1;
6219           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6220
6221
6222             if( appData.testLegality )
6223             {   /* [HGM] Some more adjudications for obstinate engines */
6224                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6225                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6226                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6227                 static int moveCount = 6;
6228                 ChessMove result;
6229                 char *reason = NULL;
6230
6231                 /* Count what is on board. */
6232                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6233                 {   ChessSquare p = boards[forwardMostMove][i][j];
6234                     int m=i;
6235
6236                     switch((int) p)
6237                     {   /* count B,N,R and other of each side */
6238                         case WhiteKing:
6239                         case BlackKing:
6240                              NrK++; break; // [HGM] atomic: count Kings
6241                         case WhiteKnight:
6242                              NrWN++; break;
6243                         case WhiteBishop:
6244                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6245                              bishopsColor |= 1 << ((i^j)&1);
6246                              NrWB++; break;
6247                         case BlackKnight:
6248                              NrBN++; break;
6249                         case BlackBishop:
6250                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6251                              bishopsColor |= 1 << ((i^j)&1);
6252                              NrBB++; break;
6253                         case WhiteRook:
6254                              NrWR++; break;
6255                         case BlackRook:
6256                              NrBR++; break;
6257                         case WhiteQueen:
6258                              NrWQ++; break;
6259                         case BlackQueen:
6260                              NrBQ++; break;
6261                         case EmptySquare: 
6262                              break;
6263                         case BlackPawn:
6264                              m = 7-i;
6265                         case WhitePawn:
6266                              PawnAdvance += m; NrPawns++;
6267                     }
6268                     NrPieces += (p != EmptySquare);
6269                     NrW += ((int)p < (int)BlackPawn);
6270                     if(gameInfo.variant == VariantXiangqi && 
6271                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6272                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6273                         NrW -= ((int)p < (int)BlackPawn);
6274                     }
6275                 }
6276
6277                 /* Some material-based adjudications that have to be made before stalemate test */
6278                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6279                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6280                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6281                      if(appData.checkMates) {
6282                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6283                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6284                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6285                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6286                          return;
6287                      }
6288                 }
6289
6290                 /* Bare King in Shatranj (loses) or Losers (wins) */
6291                 if( NrW == 1 || NrPieces - NrW == 1) {
6292                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6293                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6294                      if(appData.checkMates) {
6295                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6296                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6297                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6298                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6299                          return;
6300                      }
6301                   } else
6302                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6303                   {    /* bare King */
6304                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6305                         if(appData.checkMates) {
6306                             /* but only adjudicate if adjudication enabled */
6307                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6308                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6309                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6310                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6311                             return;
6312                         }
6313                   }
6314                 } else bare = 1;
6315
6316
6317             // don't wait for engine to announce game end if we can judge ourselves
6318             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6319               case MT_CHECK:
6320                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6321                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6322                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6323                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6324                             checkCnt++;
6325                         if(checkCnt >= 2) {
6326                             reason = "Xboard adjudication: 3rd check";
6327                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6328                             break;
6329                         }
6330                     }
6331                 }
6332               case MT_NONE:
6333               default:
6334                 break;
6335               case MT_STALEMATE:
6336               case MT_STAINMATE:
6337                 reason = "Xboard adjudication: Stalemate";
6338                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6339                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6340                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6341                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6342                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6343                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6344                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6345                                                                         EP_CHECKMATE : EP_WINS);
6346                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6347                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6348                 }
6349                 break;
6350               case MT_CHECKMATE:
6351                 reason = "Xboard adjudication: Checkmate";
6352                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6353                 break;
6354             }
6355
6356                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6357                     case EP_STALEMATE:
6358                         result = GameIsDrawn; break;
6359                     case EP_CHECKMATE:
6360                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6361                     case EP_WINS:
6362                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6363                     default:
6364                         result = (ChessMove) 0;
6365                 }
6366                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6367                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6368                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369                     GameEnds( result, reason, GE_XBOARD );
6370                     return;
6371                 }
6372
6373                 /* Next absolutely insufficient mating material. */
6374                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6375                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6376                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6377                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6378                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6379
6380                      /* always flag draws, for judging claims */
6381                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6382
6383                      if(appData.materialDraws) {
6384                          /* but only adjudicate them if adjudication enabled */
6385                          SendToProgram("force\n", cps->other); // suppress reply
6386                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6387                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6388                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6389                          return;
6390                      }
6391                 }
6392
6393                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6394                 if(NrPieces == 4 && 
6395                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6396                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6397                    || NrWN==2 || NrBN==2     /* KNNK */
6398                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6399                   ) ) {
6400                      if(--moveCount < 0 && appData.trivialDraws)
6401                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6402                           SendToProgram("force\n", cps->other); // suppress reply
6403                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6404                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6405                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6406                           return;
6407                      }
6408                 } else moveCount = 6;
6409             }
6410           }
6411           
6412           if (appData.debugMode) { int i;
6413             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6414                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6415                     appData.drawRepeats);
6416             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6417               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6418             
6419           }
6420
6421                 /* Check for rep-draws */
6422                 count = 0;
6423                 for(k = forwardMostMove-2;
6424                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6425                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6426                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6427                     k-=2)
6428                 {   int rights=0;
6429                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6430                         /* compare castling rights */
6431                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6432                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6433                                 rights++; /* King lost rights, while rook still had them */
6434                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6435                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6436                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6437                                    rights++; /* but at least one rook lost them */
6438                         }
6439                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6440                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6441                                 rights++; 
6442                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6443                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6444                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6445                                    rights++;
6446                         }
6447                         if( rights == 0 && ++count > appData.drawRepeats-2
6448                             && appData.drawRepeats > 1) {
6449                              /* adjudicate after user-specified nr of repeats */
6450                              SendToProgram("force\n", cps->other); // suppress reply
6451                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6452                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6453                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6454                                 // [HGM] xiangqi: check for forbidden perpetuals
6455                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6456                                 for(m=forwardMostMove; m>k; m-=2) {
6457                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6458                                         ourPerpetual = 0; // the current mover did not always check
6459                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6460                                         hisPerpetual = 0; // the opponent did not always check
6461                                 }
6462                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6463                                                                         ourPerpetual, hisPerpetual);
6464                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6465                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6466                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6467                                     return;
6468                                 }
6469                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6470                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6471                                 // Now check for perpetual chases
6472                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6473                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6474                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6475                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6476                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6477                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6478                                         return;
6479                                     }
6480                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6481                                         break; // Abort repetition-checking loop.
6482                                 }
6483                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6484                              }
6485                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6486                              return;
6487                         }
6488                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6489                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6490                     }
6491                 }
6492
6493                 /* Now we test for 50-move draws. Determine ply count */
6494                 count = forwardMostMove;
6495                 /* look for last irreversble move */
6496                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6497                     count--;
6498                 /* if we hit starting position, add initial plies */
6499                 if( count == backwardMostMove )
6500                     count -= initialRulePlies;
6501                 count = forwardMostMove - count; 
6502                 if( count >= 100)
6503                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6504                          /* this is used to judge if draw claims are legal */
6505                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6506                          SendToProgram("force\n", cps->other); // suppress reply
6507                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6508                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6509                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6510                          return;
6511                 }
6512
6513                 /* if draw offer is pending, treat it as a draw claim
6514                  * when draw condition present, to allow engines a way to
6515                  * claim draws before making their move to avoid a race
6516                  * condition occurring after their move
6517                  */
6518                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6519                          char *p = NULL;
6520                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6521                              p = "Draw claim: 50-move rule";
6522                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6523                              p = "Draw claim: 3-fold repetition";
6524                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6525                              p = "Draw claim: insufficient mating material";
6526                          if( p != NULL ) {
6527                              SendToProgram("force\n", cps->other); // suppress reply
6528                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6529                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6530                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6531                              return;
6532                          }
6533                 }
6534
6535
6536                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6537                     SendToProgram("force\n", cps->other); // suppress reply
6538                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6539                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540
6541                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6542
6543                     return;
6544                 }
6545         }
6546
6547         bookHit = NULL;
6548         if (gameMode == TwoMachinesPlay) {
6549             /* [HGM] relaying draw offers moved to after reception of move */
6550             /* and interpreting offer as claim if it brings draw condition */
6551             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6552                 SendToProgram("draw\n", cps->other);
6553             }
6554             if (cps->other->sendTime) {
6555                 SendTimeRemaining(cps->other,
6556                                   cps->other->twoMachinesColor[0] == 'w');
6557             }
6558             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6559             if (firstMove && !bookHit) {
6560                 firstMove = FALSE;
6561                 if (cps->other->useColors) {
6562                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6563                 }
6564                 SendToProgram("go\n", cps->other);
6565             }
6566             cps->other->maybeThinking = TRUE;
6567         }
6568
6569         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6570         
6571         if (!pausing && appData.ringBellAfterMoves) {
6572             RingBell();
6573         }
6574
6575         /* 
6576          * Reenable menu items that were disabled while
6577          * machine was thinking
6578          */
6579         if (gameMode != TwoMachinesPlay)
6580             SetUserThinkingEnables();
6581
6582         // [HGM] book: after book hit opponent has received move and is now in force mode
6583         // force the book reply into it, and then fake that it outputted this move by jumping
6584         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6585         if(bookHit) {
6586                 static char bookMove[MSG_SIZ]; // a bit generous?
6587
6588                 strcpy(bookMove, "move ");
6589                 strcat(bookMove, bookHit);
6590                 message = bookMove;
6591                 cps = cps->other;
6592                 programStats.nodes = programStats.depth = programStats.time = 
6593                 programStats.score = programStats.got_only_move = 0;
6594                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6595
6596                 if(cps->lastPing != cps->lastPong) {
6597                     savedMessage = message; // args for deferred call
6598                     savedState = cps;
6599                     ScheduleDelayedEvent(DeferredBookMove, 10);
6600                     return;
6601                 }
6602                 goto FakeBookMove;
6603         }
6604
6605         return;
6606     }
6607
6608     /* Set special modes for chess engines.  Later something general
6609      *  could be added here; for now there is just one kludge feature,
6610      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6611      *  when "xboard" is given as an interactive command.
6612      */
6613     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6614         cps->useSigint = FALSE;
6615         cps->useSigterm = FALSE;
6616     }
6617     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6618       ParseFeatures(message+8, cps);
6619       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6620     }
6621
6622     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6623      * want this, I was asked to put it in, and obliged.
6624      */
6625     if (!strncmp(message, "setboard ", 9)) {
6626         Board initial_position;
6627
6628         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6629
6630         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6631             DisplayError(_("Bad FEN received from engine"), 0);
6632             return ;
6633         } else {
6634            Reset(TRUE, FALSE);
6635            CopyBoard(boards[0], initial_position);
6636            initialRulePlies = FENrulePlies;
6637            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6638            else gameMode = MachinePlaysBlack;                 
6639            DrawPosition(FALSE, boards[currentMove]);
6640         }
6641         return;
6642     }
6643
6644     /*
6645      * Look for communication commands
6646      */
6647     if (!strncmp(message, "telluser ", 9)) {
6648         DisplayNote(message + 9);
6649         return;
6650     }
6651     if (!strncmp(message, "tellusererror ", 14)) {
6652         cps->userError = 1;
6653         DisplayError(message + 14, 0);
6654         return;
6655     }
6656     if (!strncmp(message, "tellopponent ", 13)) {
6657       if (appData.icsActive) {
6658         if (loggedOn) {
6659           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6660           SendToICS(buf1);
6661         }
6662       } else {
6663         DisplayNote(message + 13);
6664       }
6665       return;
6666     }
6667     if (!strncmp(message, "tellothers ", 11)) {
6668       if (appData.icsActive) {
6669         if (loggedOn) {
6670           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6671           SendToICS(buf1);
6672         }
6673       }
6674       return;
6675     }
6676     if (!strncmp(message, "tellall ", 8)) {
6677       if (appData.icsActive) {
6678         if (loggedOn) {
6679           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6680           SendToICS(buf1);
6681         }
6682       } else {
6683         DisplayNote(message + 8);
6684       }
6685       return;
6686     }
6687     if (strncmp(message, "warning", 7) == 0) {
6688         /* Undocumented feature, use tellusererror in new code */
6689         DisplayError(message, 0);
6690         return;
6691     }
6692     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6693         strcpy(realname, cps->tidy);
6694         strcat(realname, " query");
6695         AskQuestion(realname, buf2, buf1, cps->pr);
6696         return;
6697     }
6698     /* Commands from the engine directly to ICS.  We don't allow these to be 
6699      *  sent until we are logged on. Crafty kibitzes have been known to 
6700      *  interfere with the login process.
6701      */
6702     if (loggedOn) {
6703         if (!strncmp(message, "tellics ", 8)) {
6704             SendToICS(message + 8);
6705             SendToICS("\n");
6706             return;
6707         }
6708         if (!strncmp(message, "tellicsnoalias ", 15)) {
6709             SendToICS(ics_prefix);
6710             SendToICS(message + 15);
6711             SendToICS("\n");
6712             return;
6713         }
6714         /* The following are for backward compatibility only */
6715         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6716             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6717             SendToICS(ics_prefix);
6718             SendToICS(message);
6719             SendToICS("\n");
6720             return;
6721         }
6722     }
6723     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6724         return;
6725     }
6726     /*
6727      * If the move is illegal, cancel it and redraw the board.
6728      * Also deal with other error cases.  Matching is rather loose
6729      * here to accommodate engines written before the spec.
6730      */
6731     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6732         strncmp(message, "Error", 5) == 0) {
6733         if (StrStr(message, "name") || 
6734             StrStr(message, "rating") || StrStr(message, "?") ||
6735             StrStr(message, "result") || StrStr(message, "board") ||
6736             StrStr(message, "bk") || StrStr(message, "computer") ||
6737             StrStr(message, "variant") || StrStr(message, "hint") ||
6738             StrStr(message, "random") || StrStr(message, "depth") ||
6739             StrStr(message, "accepted")) {
6740             return;
6741         }
6742         if (StrStr(message, "protover")) {
6743           /* Program is responding to input, so it's apparently done
6744              initializing, and this error message indicates it is
6745              protocol version 1.  So we don't need to wait any longer
6746              for it to initialize and send feature commands. */
6747           FeatureDone(cps, 1);
6748           cps->protocolVersion = 1;
6749           return;
6750         }
6751         cps->maybeThinking = FALSE;
6752
6753         if (StrStr(message, "draw")) {
6754             /* Program doesn't have "draw" command */
6755             cps->sendDrawOffers = 0;
6756             return;
6757         }
6758         if (cps->sendTime != 1 &&
6759             (StrStr(message, "time") || StrStr(message, "otim"))) {
6760           /* Program apparently doesn't have "time" or "otim" command */
6761           cps->sendTime = 0;
6762           return;
6763         }
6764         if (StrStr(message, "analyze")) {
6765             cps->analysisSupport = FALSE;
6766             cps->analyzing = FALSE;
6767             Reset(FALSE, TRUE);
6768             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6769             DisplayError(buf2, 0);
6770             return;
6771         }
6772         if (StrStr(message, "(no matching move)st")) {
6773           /* Special kludge for GNU Chess 4 only */
6774           cps->stKludge = TRUE;
6775           SendTimeControl(cps, movesPerSession, timeControl,
6776                           timeIncrement, appData.searchDepth,
6777                           searchTime);
6778           return;
6779         }
6780         if (StrStr(message, "(no matching move)sd")) {
6781           /* Special kludge for GNU Chess 4 only */
6782           cps->sdKludge = TRUE;
6783           SendTimeControl(cps, movesPerSession, timeControl,
6784                           timeIncrement, appData.searchDepth,
6785                           searchTime);
6786           return;
6787         }
6788         if (!StrStr(message, "llegal")) {
6789             return;
6790         }
6791         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6792             gameMode == IcsIdle) return;
6793         if (forwardMostMove <= backwardMostMove) return;
6794         if (pausing) PauseEvent();
6795       if(appData.forceIllegal) {
6796             // [HGM] illegal: machine refused move; force position after move into it
6797           SendToProgram("force\n", cps);
6798           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6799                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6800                 // when black is to move, while there might be nothing on a2 or black
6801                 // might already have the move. So send the board as if white has the move.
6802                 // But first we must change the stm of the engine, as it refused the last move
6803                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6804                 if(WhiteOnMove(forwardMostMove)) {
6805                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6806                     SendBoard(cps, forwardMostMove); // kludgeless board
6807                 } else {
6808                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6809                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6810                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6811                 }
6812           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6813             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6814                  gameMode == TwoMachinesPlay)
6815               SendToProgram("go\n", cps);
6816             return;
6817       } else
6818         if (gameMode == PlayFromGameFile) {
6819             /* Stop reading this game file */
6820             gameMode = EditGame;
6821             ModeHighlight();
6822         }
6823         currentMove = --forwardMostMove;
6824         DisplayMove(currentMove-1); /* before DisplayMoveError */
6825         SwitchClocks();
6826         DisplayBothClocks();
6827         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6828                 parseList[currentMove], cps->which);
6829         DisplayMoveError(buf1);
6830         DrawPosition(FALSE, boards[currentMove]);
6831
6832         /* [HGM] illegal-move claim should forfeit game when Xboard */
6833         /* only passes fully legal moves                            */
6834         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6835             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6836                                 "False illegal-move claim", GE_XBOARD );
6837         }
6838         return;
6839     }
6840     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6841         /* Program has a broken "time" command that
6842            outputs a string not ending in newline.
6843            Don't use it. */
6844         cps->sendTime = 0;
6845     }
6846     
6847     /*
6848      * If chess program startup fails, exit with an error message.
6849      * Attempts to recover here are futile.
6850      */
6851     if ((StrStr(message, "unknown host") != NULL)
6852         || (StrStr(message, "No remote directory") != NULL)
6853         || (StrStr(message, "not found") != NULL)
6854         || (StrStr(message, "No such file") != NULL)
6855         || (StrStr(message, "can't alloc") != NULL)
6856         || (StrStr(message, "Permission denied") != NULL)) {
6857
6858         cps->maybeThinking = FALSE;
6859         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6860                 cps->which, cps->program, cps->host, message);
6861         RemoveInputSource(cps->isr);
6862         DisplayFatalError(buf1, 0, 1);
6863         return;
6864     }
6865     
6866     /* 
6867      * Look for hint output
6868      */
6869     if (sscanf(message, "Hint: %s", buf1) == 1) {
6870         if (cps == &first && hintRequested) {
6871             hintRequested = FALSE;
6872             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6873                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6874                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6875                                     PosFlags(forwardMostMove),
6876                                     fromY, fromX, toY, toX, promoChar, buf1);
6877                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6878                 DisplayInformation(buf2);
6879             } else {
6880                 /* Hint move could not be parsed!? */
6881               snprintf(buf2, sizeof(buf2),
6882                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6883                         buf1, cps->which);
6884                 DisplayError(buf2, 0);
6885             }
6886         } else {
6887             strcpy(lastHint, buf1);
6888         }
6889         return;
6890     }
6891
6892     /*
6893      * Ignore other messages if game is not in progress
6894      */
6895     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6896         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6897
6898     /*
6899      * look for win, lose, draw, or draw offer
6900      */
6901     if (strncmp(message, "1-0", 3) == 0) {
6902         char *p, *q, *r = "";
6903         p = strchr(message, '{');
6904         if (p) {
6905             q = strchr(p, '}');
6906             if (q) {
6907                 *q = NULLCHAR;
6908                 r = p + 1;
6909             }
6910         }
6911         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6912         return;
6913     } else if (strncmp(message, "0-1", 3) == 0) {
6914         char *p, *q, *r = "";
6915         p = strchr(message, '{');
6916         if (p) {
6917             q = strchr(p, '}');
6918             if (q) {
6919                 *q = NULLCHAR;
6920                 r = p + 1;
6921             }
6922         }
6923         /* Kludge for Arasan 4.1 bug */
6924         if (strcmp(r, "Black resigns") == 0) {
6925             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6926             return;
6927         }
6928         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6929         return;
6930     } else if (strncmp(message, "1/2", 3) == 0) {
6931         char *p, *q, *r = "";
6932         p = strchr(message, '{');
6933         if (p) {
6934             q = strchr(p, '}');
6935             if (q) {
6936                 *q = NULLCHAR;
6937                 r = p + 1;
6938             }
6939         }
6940             
6941         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6942         return;
6943
6944     } else if (strncmp(message, "White resign", 12) == 0) {
6945         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6946         return;
6947     } else if (strncmp(message, "Black resign", 12) == 0) {
6948         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6949         return;
6950     } else if (strncmp(message, "White matches", 13) == 0 ||
6951                strncmp(message, "Black matches", 13) == 0   ) {
6952         /* [HGM] ignore GNUShogi noises */
6953         return;
6954     } else if (strncmp(message, "White", 5) == 0 &&
6955                message[5] != '(' &&
6956                StrStr(message, "Black") == NULL) {
6957         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6958         return;
6959     } else if (strncmp(message, "Black", 5) == 0 &&
6960                message[5] != '(') {
6961         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6962         return;
6963     } else if (strcmp(message, "resign") == 0 ||
6964                strcmp(message, "computer resigns") == 0) {
6965         switch (gameMode) {
6966           case MachinePlaysBlack:
6967           case IcsPlayingBlack:
6968             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6969             break;
6970           case MachinePlaysWhite:
6971           case IcsPlayingWhite:
6972             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6973             break;
6974           case TwoMachinesPlay:
6975             if (cps->twoMachinesColor[0] == 'w')
6976               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6977             else
6978               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6979             break;
6980           default:
6981             /* can't happen */
6982             break;
6983         }
6984         return;
6985     } else if (strncmp(message, "opponent mates", 14) == 0) {
6986         switch (gameMode) {
6987           case MachinePlaysBlack:
6988           case IcsPlayingBlack:
6989             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6990             break;
6991           case MachinePlaysWhite:
6992           case IcsPlayingWhite:
6993             GameEnds(BlackWins, "Black mates", GE_ENGINE);
6994             break;
6995           case TwoMachinesPlay:
6996             if (cps->twoMachinesColor[0] == 'w')
6997               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6998             else
6999               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7000             break;
7001           default:
7002             /* can't happen */
7003             break;
7004         }
7005         return;
7006     } else if (strncmp(message, "computer mates", 14) == 0) {
7007         switch (gameMode) {
7008           case MachinePlaysBlack:
7009           case IcsPlayingBlack:
7010             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7011             break;
7012           case MachinePlaysWhite:
7013           case IcsPlayingWhite:
7014             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7015             break;
7016           case TwoMachinesPlay:
7017             if (cps->twoMachinesColor[0] == 'w')
7018               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7019             else
7020               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7021             break;
7022           default:
7023             /* can't happen */
7024             break;
7025         }
7026         return;
7027     } else if (strncmp(message, "checkmate", 9) == 0) {
7028         if (WhiteOnMove(forwardMostMove)) {
7029             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7030         } else {
7031             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7032         }
7033         return;
7034     } else if (strstr(message, "Draw") != NULL ||
7035                strstr(message, "game is a draw") != NULL) {
7036         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7037         return;
7038     } else if (strstr(message, "offer") != NULL &&
7039                strstr(message, "draw") != NULL) {
7040 #if ZIPPY
7041         if (appData.zippyPlay && first.initDone) {
7042             /* Relay offer to ICS */
7043             SendToICS(ics_prefix);
7044             SendToICS("draw\n");
7045         }
7046 #endif
7047         cps->offeredDraw = 2; /* valid until this engine moves twice */
7048         if (gameMode == TwoMachinesPlay) {
7049             if (cps->other->offeredDraw) {
7050                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7051             /* [HGM] in two-machine mode we delay relaying draw offer      */
7052             /* until after we also have move, to see if it is really claim */
7053             }
7054         } else if (gameMode == MachinePlaysWhite ||
7055                    gameMode == MachinePlaysBlack) {
7056           if (userOfferedDraw) {
7057             DisplayInformation(_("Machine accepts your draw offer"));
7058             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7059           } else {
7060             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7061           }
7062         }
7063     }
7064
7065     
7066     /*
7067      * Look for thinking output
7068      */
7069     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7070           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7071                                 ) {
7072         int plylev, mvleft, mvtot, curscore, time;
7073         char mvname[MOVE_LEN];
7074         u64 nodes; // [DM]
7075         char plyext;
7076         int ignore = FALSE;
7077         int prefixHint = FALSE;
7078         mvname[0] = NULLCHAR;
7079
7080         switch (gameMode) {
7081           case MachinePlaysBlack:
7082           case IcsPlayingBlack:
7083             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7084             break;
7085           case MachinePlaysWhite:
7086           case IcsPlayingWhite:
7087             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7088             break;
7089           case AnalyzeMode:
7090           case AnalyzeFile:
7091             break;
7092           case IcsObserving: /* [DM] icsEngineAnalyze */
7093             if (!appData.icsEngineAnalyze) ignore = TRUE;
7094             break;
7095           case TwoMachinesPlay:
7096             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7097                 ignore = TRUE;
7098             }
7099             break;
7100           default:
7101             ignore = TRUE;
7102             break;
7103         }
7104
7105         if (!ignore) {
7106             buf1[0] = NULLCHAR;
7107             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7108                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7109
7110                 if (plyext != ' ' && plyext != '\t') {
7111                     time *= 100;
7112                 }
7113
7114                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7115                 if( cps->scoreIsAbsolute && 
7116                     ( gameMode == MachinePlaysBlack ||
7117                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7118                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7119                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7120                      !WhiteOnMove(currentMove)
7121                     ) )
7122                 {
7123                     curscore = -curscore;
7124                 }
7125
7126
7127                 programStats.depth = plylev;
7128                 programStats.nodes = nodes;
7129                 programStats.time = time;
7130                 programStats.score = curscore;
7131                 programStats.got_only_move = 0;
7132
7133                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7134                         int ticklen;
7135
7136                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7137                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7138                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7139                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7140                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7141                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7142                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7143                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7144                 }
7145
7146                 /* Buffer overflow protection */
7147                 if (buf1[0] != NULLCHAR) {
7148                     if (strlen(buf1) >= sizeof(programStats.movelist)
7149                         && appData.debugMode) {
7150                         fprintf(debugFP,
7151                                 "PV is too long; using the first %u bytes.\n",
7152                                 (unsigned) sizeof(programStats.movelist) - 1);
7153                     }
7154
7155                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7156                 } else {
7157                     sprintf(programStats.movelist, " no PV\n");
7158                 }
7159
7160                 if (programStats.seen_stat) {
7161                     programStats.ok_to_send = 1;
7162                 }
7163
7164                 if (strchr(programStats.movelist, '(') != NULL) {
7165                     programStats.line_is_book = 1;
7166                     programStats.nr_moves = 0;
7167                     programStats.moves_left = 0;
7168                 } else {
7169                     programStats.line_is_book = 0;
7170                 }
7171
7172                 SendProgramStatsToFrontend( cps, &programStats );
7173
7174                 /* 
7175                     [AS] Protect the thinkOutput buffer from overflow... this
7176                     is only useful if buf1 hasn't overflowed first!
7177                 */
7178                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7179                         plylev, 
7180                         (gameMode == TwoMachinesPlay ?
7181                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7182                         ((double) curscore) / 100.0,
7183                         prefixHint ? lastHint : "",
7184                         prefixHint ? " " : "" );
7185
7186                 if( buf1[0] != NULLCHAR ) {
7187                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7188
7189                     if( strlen(buf1) > max_len ) {
7190                         if( appData.debugMode) {
7191                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7192                         }
7193                         buf1[max_len+1] = '\0';
7194                     }
7195
7196                     strcat( thinkOutput, buf1 );
7197                 }
7198
7199                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7200                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7201                     DisplayMove(currentMove - 1);
7202                 }
7203                 return;
7204
7205             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7206                 /* crafty (9.25+) says "(only move) <move>"
7207                  * if there is only 1 legal move
7208                  */
7209                 sscanf(p, "(only move) %s", buf1);
7210                 sprintf(thinkOutput, "%s (only move)", buf1);
7211                 sprintf(programStats.movelist, "%s (only move)", buf1);
7212                 programStats.depth = 1;
7213                 programStats.nr_moves = 1;
7214                 programStats.moves_left = 1;
7215                 programStats.nodes = 1;
7216                 programStats.time = 1;
7217                 programStats.got_only_move = 1;
7218
7219                 /* Not really, but we also use this member to
7220                    mean "line isn't going to change" (Crafty
7221                    isn't searching, so stats won't change) */
7222                 programStats.line_is_book = 1;
7223
7224                 SendProgramStatsToFrontend( cps, &programStats );
7225                 
7226                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7227                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7228                     DisplayMove(currentMove - 1);
7229                 }
7230                 return;
7231             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7232                               &time, &nodes, &plylev, &mvleft,
7233                               &mvtot, mvname) >= 5) {
7234                 /* The stat01: line is from Crafty (9.29+) in response
7235                    to the "." command */
7236                 programStats.seen_stat = 1;
7237                 cps->maybeThinking = TRUE;
7238
7239                 if (programStats.got_only_move || !appData.periodicUpdates)
7240                   return;
7241
7242                 programStats.depth = plylev;
7243                 programStats.time = time;
7244                 programStats.nodes = nodes;
7245                 programStats.moves_left = mvleft;
7246                 programStats.nr_moves = mvtot;
7247                 strcpy(programStats.move_name, mvname);
7248                 programStats.ok_to_send = 1;
7249                 programStats.movelist[0] = '\0';
7250
7251                 SendProgramStatsToFrontend( cps, &programStats );
7252
7253                 return;
7254
7255             } else if (strncmp(message,"++",2) == 0) {
7256                 /* Crafty 9.29+ outputs this */
7257                 programStats.got_fail = 2;
7258                 return;
7259
7260             } else if (strncmp(message,"--",2) == 0) {
7261                 /* Crafty 9.29+ outputs this */
7262                 programStats.got_fail = 1;
7263                 return;
7264
7265             } else if (thinkOutput[0] != NULLCHAR &&
7266                        strncmp(message, "    ", 4) == 0) {
7267                 unsigned message_len;
7268
7269                 p = message;
7270                 while (*p && *p == ' ') p++;
7271
7272                 message_len = strlen( p );
7273
7274                 /* [AS] Avoid buffer overflow */
7275                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7276                     strcat(thinkOutput, " ");
7277                     strcat(thinkOutput, p);
7278                 }
7279
7280                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7281                     strcat(programStats.movelist, " ");
7282                     strcat(programStats.movelist, p);
7283                 }
7284
7285                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7286                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7287                     DisplayMove(currentMove - 1);
7288                 }
7289                 return;
7290             }
7291         }
7292         else {
7293             buf1[0] = NULLCHAR;
7294
7295             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7296                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7297             {
7298                 ChessProgramStats cpstats;
7299
7300                 if (plyext != ' ' && plyext != '\t') {
7301                     time *= 100;
7302                 }
7303
7304                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7305                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7306                     curscore = -curscore;
7307                 }
7308
7309                 cpstats.depth = plylev;
7310                 cpstats.nodes = nodes;
7311                 cpstats.time = time;
7312                 cpstats.score = curscore;
7313                 cpstats.got_only_move = 0;
7314                 cpstats.movelist[0] = '\0';
7315
7316                 if (buf1[0] != NULLCHAR) {
7317                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7318                 }
7319
7320                 cpstats.ok_to_send = 0;
7321                 cpstats.line_is_book = 0;
7322                 cpstats.nr_moves = 0;
7323                 cpstats.moves_left = 0;
7324
7325                 SendProgramStatsToFrontend( cps, &cpstats );
7326             }
7327         }
7328     }
7329 }
7330
7331
7332 /* Parse a game score from the character string "game", and
7333    record it as the history of the current game.  The game
7334    score is NOT assumed to start from the standard position. 
7335    The display is not updated in any way.
7336    */
7337 void
7338 ParseGameHistory(game)
7339      char *game;
7340 {
7341     ChessMove moveType;
7342     int fromX, fromY, toX, toY, boardIndex;
7343     char promoChar;
7344     char *p, *q;
7345     char buf[MSG_SIZ];
7346
7347     if (appData.debugMode)
7348       fprintf(debugFP, "Parsing game history: %s\n", game);
7349
7350     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7351     gameInfo.site = StrSave(appData.icsHost);
7352     gameInfo.date = PGNDate();
7353     gameInfo.round = StrSave("-");
7354
7355     /* Parse out names of players */
7356     while (*game == ' ') game++;
7357     p = buf;
7358     while (*game != ' ') *p++ = *game++;
7359     *p = NULLCHAR;
7360     gameInfo.white = StrSave(buf);
7361     while (*game == ' ') game++;
7362     p = buf;
7363     while (*game != ' ' && *game != '\n') *p++ = *game++;
7364     *p = NULLCHAR;
7365     gameInfo.black = StrSave(buf);
7366
7367     /* Parse moves */
7368     boardIndex = blackPlaysFirst ? 1 : 0;
7369     yynewstr(game);
7370     for (;;) {
7371         yyboardindex = boardIndex;
7372         moveType = (ChessMove) yylex();
7373         switch (moveType) {
7374           case IllegalMove:             /* maybe suicide chess, etc. */
7375   if (appData.debugMode) {
7376     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7377     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7378     setbuf(debugFP, NULL);
7379   }
7380           case WhitePromotionChancellor:
7381           case BlackPromotionChancellor:
7382           case WhitePromotionArchbishop:
7383           case BlackPromotionArchbishop:
7384           case WhitePromotionQueen:
7385           case BlackPromotionQueen:
7386           case WhitePromotionRook:
7387           case BlackPromotionRook:
7388           case WhitePromotionBishop:
7389           case BlackPromotionBishop:
7390           case WhitePromotionKnight:
7391           case BlackPromotionKnight:
7392           case WhitePromotionKing:
7393           case BlackPromotionKing:
7394           case NormalMove:
7395           case WhiteCapturesEnPassant:
7396           case BlackCapturesEnPassant:
7397           case WhiteKingSideCastle:
7398           case WhiteQueenSideCastle:
7399           case BlackKingSideCastle:
7400           case BlackQueenSideCastle:
7401           case WhiteKingSideCastleWild:
7402           case WhiteQueenSideCastleWild:
7403           case BlackKingSideCastleWild:
7404           case BlackQueenSideCastleWild:
7405           /* PUSH Fabien */
7406           case WhiteHSideCastleFR:
7407           case WhiteASideCastleFR:
7408           case BlackHSideCastleFR:
7409           case BlackASideCastleFR:
7410           /* POP Fabien */
7411             fromX = currentMoveString[0] - AAA;
7412             fromY = currentMoveString[1] - ONE;
7413             toX = currentMoveString[2] - AAA;
7414             toY = currentMoveString[3] - ONE;
7415             promoChar = currentMoveString[4];
7416             break;
7417           case WhiteDrop:
7418           case BlackDrop:
7419             fromX = moveType == WhiteDrop ?
7420               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7421             (int) CharToPiece(ToLower(currentMoveString[0]));
7422             fromY = DROP_RANK;
7423             toX = currentMoveString[2] - AAA;
7424             toY = currentMoveString[3] - ONE;
7425             promoChar = NULLCHAR;
7426             break;
7427           case AmbiguousMove:
7428             /* bug? */
7429             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7430   if (appData.debugMode) {
7431     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7432     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7433     setbuf(debugFP, NULL);
7434   }
7435             DisplayError(buf, 0);
7436             return;
7437           case ImpossibleMove:
7438             /* bug? */
7439             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7440   if (appData.debugMode) {
7441     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7442     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7443     setbuf(debugFP, NULL);
7444   }
7445             DisplayError(buf, 0);
7446             return;
7447           case (ChessMove) 0:   /* end of file */
7448             if (boardIndex < backwardMostMove) {
7449                 /* Oops, gap.  How did that happen? */
7450                 DisplayError(_("Gap in move list"), 0);
7451                 return;
7452             }
7453             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7454             if (boardIndex > forwardMostMove) {
7455                 forwardMostMove = boardIndex;
7456             }
7457             return;
7458           case ElapsedTime:
7459             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7460                 strcat(parseList[boardIndex-1], " ");
7461                 strcat(parseList[boardIndex-1], yy_text);
7462             }
7463             continue;
7464           case Comment:
7465           case PGNTag:
7466           case NAG:
7467           default:
7468             /* ignore */
7469             continue;
7470           case WhiteWins:
7471           case BlackWins:
7472           case GameIsDrawn:
7473           case GameUnfinished:
7474             if (gameMode == IcsExamining) {
7475                 if (boardIndex < backwardMostMove) {
7476                     /* Oops, gap.  How did that happen? */
7477                     return;
7478                 }
7479                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7480                 return;
7481             }
7482             gameInfo.result = moveType;
7483             p = strchr(yy_text, '{');
7484             if (p == NULL) p = strchr(yy_text, '(');
7485             if (p == NULL) {
7486                 p = yy_text;
7487                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7488             } else {
7489                 q = strchr(p, *p == '{' ? '}' : ')');
7490                 if (q != NULL) *q = NULLCHAR;
7491                 p++;
7492             }
7493             gameInfo.resultDetails = StrSave(p);
7494             continue;
7495         }
7496         if (boardIndex >= forwardMostMove &&
7497             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7498             backwardMostMove = blackPlaysFirst ? 1 : 0;
7499             return;
7500         }
7501         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7502                                  fromY, fromX, toY, toX, promoChar,
7503                                  parseList[boardIndex]);
7504         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7505         /* currentMoveString is set as a side-effect of yylex */
7506         strcpy(moveList[boardIndex], currentMoveString);
7507         strcat(moveList[boardIndex], "\n");
7508         boardIndex++;
7509         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7510         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7511           case MT_NONE:
7512           case MT_STALEMATE:
7513           default:
7514             break;
7515           case MT_CHECK:
7516             if(gameInfo.variant != VariantShogi)
7517                 strcat(parseList[boardIndex - 1], "+");
7518             break;
7519           case MT_CHECKMATE:
7520           case MT_STAINMATE:
7521             strcat(parseList[boardIndex - 1], "#");
7522             break;
7523         }
7524     }
7525 }
7526
7527
7528 /* Apply a move to the given board  */
7529 void
7530 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7531      int fromX, fromY, toX, toY;
7532      int promoChar;
7533      Board board;
7534 {
7535   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7536
7537     /* [HGM] compute & store e.p. status and castling rights for new position */
7538     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7539     { int i;
7540
7541       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7542       oldEP = (signed char)board[EP_STATUS];
7543       board[EP_STATUS] = EP_NONE;
7544
7545       if( board[toY][toX] != EmptySquare ) 
7546            board[EP_STATUS] = EP_CAPTURE;  
7547
7548       if( board[fromY][fromX] == WhitePawn ) {
7549            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7550                board[EP_STATUS] = EP_PAWN_MOVE;
7551            if( toY-fromY==2) {
7552                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7553                         gameInfo.variant != VariantBerolina || toX < fromX)
7554                       board[EP_STATUS] = toX | berolina;
7555                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7556                         gameInfo.variant != VariantBerolina || toX > fromX) 
7557                       board[EP_STATUS] = toX;
7558            }
7559       } else 
7560       if( board[fromY][fromX] == BlackPawn ) {
7561            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7562                board[EP_STATUS] = EP_PAWN_MOVE; 
7563            if( toY-fromY== -2) {
7564                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7565                         gameInfo.variant != VariantBerolina || toX < fromX)
7566                       board[EP_STATUS] = toX | berolina;
7567                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7568                         gameInfo.variant != VariantBerolina || toX > fromX) 
7569                       board[EP_STATUS] = toX;
7570            }
7571        }
7572
7573        for(i=0; i<nrCastlingRights; i++) {
7574            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7575               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7576              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7577        }
7578
7579     }
7580
7581   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7582   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7583        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7584          
7585   if (fromX == toX && fromY == toY) return;
7586
7587   if (fromY == DROP_RANK) {
7588         /* must be first */
7589         piece = board[toY][toX] = (ChessSquare) fromX;
7590   } else {
7591      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7592      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7593      if(gameInfo.variant == VariantKnightmate)
7594          king += (int) WhiteUnicorn - (int) WhiteKing;
7595
7596     /* Code added by Tord: */
7597     /* FRC castling assumed when king captures friendly rook. */
7598     if (board[fromY][fromX] == WhiteKing &&
7599              board[toY][toX] == WhiteRook) {
7600       board[fromY][fromX] = EmptySquare;
7601       board[toY][toX] = EmptySquare;
7602       if(toX > fromX) {
7603         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7604       } else {
7605         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7606       }
7607     } else if (board[fromY][fromX] == BlackKing &&
7608                board[toY][toX] == BlackRook) {
7609       board[fromY][fromX] = EmptySquare;
7610       board[toY][toX] = EmptySquare;
7611       if(toX > fromX) {
7612         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7613       } else {
7614         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7615       }
7616     /* End of code added by Tord */
7617
7618     } else if (board[fromY][fromX] == king
7619         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7620         && toY == fromY && toX > fromX+1) {
7621         board[fromY][fromX] = EmptySquare;
7622         board[toY][toX] = king;
7623         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7624         board[fromY][BOARD_RGHT-1] = EmptySquare;
7625     } else if (board[fromY][fromX] == king
7626         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7627                && toY == fromY && toX < fromX-1) {
7628         board[fromY][fromX] = EmptySquare;
7629         board[toY][toX] = king;
7630         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7631         board[fromY][BOARD_LEFT] = EmptySquare;
7632     } else if (board[fromY][fromX] == WhitePawn
7633                && toY == BOARD_HEIGHT-1
7634                && gameInfo.variant != VariantXiangqi
7635                ) {
7636         /* white pawn promotion */
7637         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7638         if (board[toY][toX] == EmptySquare) {
7639             board[toY][toX] = WhiteQueen;
7640         }
7641         if(gameInfo.variant==VariantBughouse ||
7642            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7643             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7644         board[fromY][fromX] = EmptySquare;
7645     } else if ((fromY == BOARD_HEIGHT-4)
7646                && (toX != fromX)
7647                && gameInfo.variant != VariantXiangqi
7648                && gameInfo.variant != VariantBerolina
7649                && (board[fromY][fromX] == WhitePawn)
7650                && (board[toY][toX] == EmptySquare)) {
7651         board[fromY][fromX] = EmptySquare;
7652         board[toY][toX] = WhitePawn;
7653         captured = board[toY - 1][toX];
7654         board[toY - 1][toX] = EmptySquare;
7655     } else if ((fromY == BOARD_HEIGHT-4)
7656                && (toX == fromX)
7657                && gameInfo.variant == VariantBerolina
7658                && (board[fromY][fromX] == WhitePawn)
7659                && (board[toY][toX] == EmptySquare)) {
7660         board[fromY][fromX] = EmptySquare;
7661         board[toY][toX] = WhitePawn;
7662         if(oldEP & EP_BEROLIN_A) {
7663                 captured = board[fromY][fromX-1];
7664                 board[fromY][fromX-1] = EmptySquare;
7665         }else{  captured = board[fromY][fromX+1];
7666                 board[fromY][fromX+1] = EmptySquare;
7667         }
7668     } else if (board[fromY][fromX] == king
7669         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7670                && toY == fromY && toX > fromX+1) {
7671         board[fromY][fromX] = EmptySquare;
7672         board[toY][toX] = king;
7673         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7674         board[fromY][BOARD_RGHT-1] = EmptySquare;
7675     } else if (board[fromY][fromX] == king
7676         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7677                && toY == fromY && toX < fromX-1) {
7678         board[fromY][fromX] = EmptySquare;
7679         board[toY][toX] = king;
7680         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7681         board[fromY][BOARD_LEFT] = EmptySquare;
7682     } else if (fromY == 7 && fromX == 3
7683                && board[fromY][fromX] == BlackKing
7684                && toY == 7 && toX == 5) {
7685         board[fromY][fromX] = EmptySquare;
7686         board[toY][toX] = BlackKing;
7687         board[fromY][7] = EmptySquare;
7688         board[toY][4] = BlackRook;
7689     } else if (fromY == 7 && fromX == 3
7690                && board[fromY][fromX] == BlackKing
7691                && toY == 7 && toX == 1) {
7692         board[fromY][fromX] = EmptySquare;
7693         board[toY][toX] = BlackKing;
7694         board[fromY][0] = EmptySquare;
7695         board[toY][2] = BlackRook;
7696     } else if (board[fromY][fromX] == BlackPawn
7697                && toY == 0
7698                && gameInfo.variant != VariantXiangqi
7699                ) {
7700         /* black pawn promotion */
7701         board[0][toX] = CharToPiece(ToLower(promoChar));
7702         if (board[0][toX] == EmptySquare) {
7703             board[0][toX] = BlackQueen;
7704         }
7705         if(gameInfo.variant==VariantBughouse ||
7706            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7707             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7708         board[fromY][fromX] = EmptySquare;
7709     } else if ((fromY == 3)
7710                && (toX != fromX)
7711                && gameInfo.variant != VariantXiangqi
7712                && gameInfo.variant != VariantBerolina
7713                && (board[fromY][fromX] == BlackPawn)
7714                && (board[toY][toX] == EmptySquare)) {
7715         board[fromY][fromX] = EmptySquare;
7716         board[toY][toX] = BlackPawn;
7717         captured = board[toY + 1][toX];
7718         board[toY + 1][toX] = EmptySquare;
7719     } else if ((fromY == 3)
7720                && (toX == fromX)
7721                && gameInfo.variant == VariantBerolina
7722                && (board[fromY][fromX] == BlackPawn)
7723                && (board[toY][toX] == EmptySquare)) {
7724         board[fromY][fromX] = EmptySquare;
7725         board[toY][toX] = BlackPawn;
7726         if(oldEP & EP_BEROLIN_A) {
7727                 captured = board[fromY][fromX-1];
7728                 board[fromY][fromX-1] = EmptySquare;
7729         }else{  captured = board[fromY][fromX+1];
7730                 board[fromY][fromX+1] = EmptySquare;
7731         }
7732     } else {
7733         board[toY][toX] = board[fromY][fromX];
7734         board[fromY][fromX] = EmptySquare;
7735     }
7736
7737     /* [HGM] now we promote for Shogi, if needed */
7738     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7739         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7740   }
7741
7742     if (gameInfo.holdingsWidth != 0) {
7743
7744       /* !!A lot more code needs to be written to support holdings  */
7745       /* [HGM] OK, so I have written it. Holdings are stored in the */
7746       /* penultimate board files, so they are automaticlly stored   */
7747       /* in the game history.                                       */
7748       if (fromY == DROP_RANK) {
7749         /* Delete from holdings, by decreasing count */
7750         /* and erasing image if necessary            */
7751         p = (int) fromX;
7752         if(p < (int) BlackPawn) { /* white drop */
7753              p -= (int)WhitePawn;
7754                  p = PieceToNumber((ChessSquare)p);
7755              if(p >= gameInfo.holdingsSize) p = 0;
7756              if(--board[p][BOARD_WIDTH-2] <= 0)
7757                   board[p][BOARD_WIDTH-1] = EmptySquare;
7758              if((int)board[p][BOARD_WIDTH-2] < 0)
7759                         board[p][BOARD_WIDTH-2] = 0;
7760         } else {                  /* black drop */
7761              p -= (int)BlackPawn;
7762                  p = PieceToNumber((ChessSquare)p);
7763              if(p >= gameInfo.holdingsSize) p = 0;
7764              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7765                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7766              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7767                         board[BOARD_HEIGHT-1-p][1] = 0;
7768         }
7769       }
7770       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7771           && gameInfo.variant != VariantBughouse        ) {
7772         /* [HGM] holdings: Add to holdings, if holdings exist */
7773         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7774                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7775                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7776         }
7777         p = (int) captured;
7778         if (p >= (int) BlackPawn) {
7779           p -= (int)BlackPawn;
7780           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7781                   /* in Shogi restore piece to its original  first */
7782                   captured = (ChessSquare) (DEMOTED captured);
7783                   p = DEMOTED p;
7784           }
7785           p = PieceToNumber((ChessSquare)p);
7786           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7787           board[p][BOARD_WIDTH-2]++;
7788           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7789         } else {
7790           p -= (int)WhitePawn;
7791           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7792                   captured = (ChessSquare) (DEMOTED captured);
7793                   p = DEMOTED p;
7794           }
7795           p = PieceToNumber((ChessSquare)p);
7796           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7797           board[BOARD_HEIGHT-1-p][1]++;
7798           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7799         }
7800       }
7801     } else if (gameInfo.variant == VariantAtomic) {
7802       if (captured != EmptySquare) {
7803         int y, x;
7804         for (y = toY-1; y <= toY+1; y++) {
7805           for (x = toX-1; x <= toX+1; x++) {
7806             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7807                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7808               board[y][x] = EmptySquare;
7809             }
7810           }
7811         }
7812         board[toY][toX] = EmptySquare;
7813       }
7814     }
7815     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7816         /* [HGM] Shogi promotions */
7817         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7818     }
7819
7820     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7821                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7822         // [HGM] superchess: take promotion piece out of holdings
7823         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7824         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7825             if(!--board[k][BOARD_WIDTH-2])
7826                 board[k][BOARD_WIDTH-1] = EmptySquare;
7827         } else {
7828             if(!--board[BOARD_HEIGHT-1-k][1])
7829                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7830         }
7831     }
7832
7833 }
7834
7835 /* Updates forwardMostMove */
7836 void
7837 MakeMove(fromX, fromY, toX, toY, promoChar)
7838      int fromX, fromY, toX, toY;
7839      int promoChar;
7840 {
7841 //    forwardMostMove++; // [HGM] bare: moved downstream
7842
7843     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7844         int timeLeft; static int lastLoadFlag=0; int king, piece;
7845         piece = boards[forwardMostMove][fromY][fromX];
7846         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7847         if(gameInfo.variant == VariantKnightmate)
7848             king += (int) WhiteUnicorn - (int) WhiteKing;
7849         if(forwardMostMove == 0) {
7850             if(blackPlaysFirst) 
7851                 fprintf(serverMoves, "%s;", second.tidy);
7852             fprintf(serverMoves, "%s;", first.tidy);
7853             if(!blackPlaysFirst) 
7854                 fprintf(serverMoves, "%s;", second.tidy);
7855         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7856         lastLoadFlag = loadFlag;
7857         // print base move
7858         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7859         // print castling suffix
7860         if( toY == fromY && piece == king ) {
7861             if(toX-fromX > 1)
7862                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7863             if(fromX-toX >1)
7864                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7865         }
7866         // e.p. suffix
7867         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7868              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7869              boards[forwardMostMove][toY][toX] == EmptySquare
7870              && fromX != toX )
7871                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7872         // promotion suffix
7873         if(promoChar != NULLCHAR)
7874                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7875         if(!loadFlag) {
7876             fprintf(serverMoves, "/%d/%d",
7877                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7878             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7879             else                      timeLeft = blackTimeRemaining/1000;
7880             fprintf(serverMoves, "/%d", timeLeft);
7881         }
7882         fflush(serverMoves);
7883     }
7884
7885     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7886       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7887                         0, 1);
7888       return;
7889     }
7890     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7891     if (commentList[forwardMostMove+1] != NULL) {
7892         free(commentList[forwardMostMove+1]);
7893         commentList[forwardMostMove+1] = NULL;
7894     }
7895     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7896     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7897     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7898     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7899     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7900     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7901     gameInfo.result = GameUnfinished;
7902     if (gameInfo.resultDetails != NULL) {
7903         free(gameInfo.resultDetails);
7904         gameInfo.resultDetails = NULL;
7905     }
7906     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7907                               moveList[forwardMostMove - 1]);
7908     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7909                              PosFlags(forwardMostMove - 1),
7910                              fromY, fromX, toY, toX, promoChar,
7911                              parseList[forwardMostMove - 1]);
7912     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7913       case MT_NONE:
7914       case MT_STALEMATE:
7915       default:
7916         break;
7917       case MT_CHECK:
7918         if(gameInfo.variant != VariantShogi)
7919             strcat(parseList[forwardMostMove - 1], "+");
7920         break;
7921       case MT_CHECKMATE:
7922       case MT_STAINMATE:
7923         strcat(parseList[forwardMostMove - 1], "#");
7924         break;
7925     }
7926     if (appData.debugMode) {
7927         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7928     }
7929
7930 }
7931
7932 /* Updates currentMove if not pausing */
7933 void
7934 ShowMove(fromX, fromY, toX, toY)
7935 {
7936     int instant = (gameMode == PlayFromGameFile) ?
7937         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7938     if(appData.noGUI) return;
7939     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7940         if (!instant) {
7941             if (forwardMostMove == currentMove + 1) {
7942                 AnimateMove(boards[forwardMostMove - 1],
7943                             fromX, fromY, toX, toY);
7944             }
7945             if (appData.highlightLastMove) {
7946                 SetHighlights(fromX, fromY, toX, toY);
7947             }
7948         }
7949         currentMove = forwardMostMove;
7950     }
7951
7952     if (instant) return;
7953
7954     DisplayMove(currentMove - 1);
7955     DrawPosition(FALSE, boards[currentMove]);
7956     DisplayBothClocks();
7957     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7958 }
7959
7960 void SendEgtPath(ChessProgramState *cps)
7961 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7962         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7963
7964         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7965
7966         while(*p) {
7967             char c, *q = name+1, *r, *s;
7968
7969             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7970             while(*p && *p != ',') *q++ = *p++;
7971             *q++ = ':'; *q = 0;
7972             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7973                 strcmp(name, ",nalimov:") == 0 ) {
7974                 // take nalimov path from the menu-changeable option first, if it is defined
7975                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7976                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7977             } else
7978             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7979                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7980                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7981                 s = r = StrStr(s, ":") + 1; // beginning of path info
7982                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7983                 c = *r; *r = 0;             // temporarily null-terminate path info
7984                     *--q = 0;               // strip of trailig ':' from name
7985                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7986                 *r = c;
7987                 SendToProgram(buf,cps);     // send egtbpath command for this format
7988             }
7989             if(*p == ',') p++; // read away comma to position for next format name
7990         }
7991 }
7992
7993 void
7994 InitChessProgram(cps, setup)
7995      ChessProgramState *cps;
7996      int setup; /* [HGM] needed to setup FRC opening position */
7997 {
7998     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7999     if (appData.noChessProgram) return;
8000     hintRequested = FALSE;
8001     bookRequested = FALSE;
8002
8003     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8004     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8005     if(cps->memSize) { /* [HGM] memory */
8006         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8007         SendToProgram(buf, cps);
8008     }
8009     SendEgtPath(cps); /* [HGM] EGT */
8010     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8011         sprintf(buf, "cores %d\n", appData.smpCores);
8012         SendToProgram(buf, cps);
8013     }
8014
8015     SendToProgram(cps->initString, cps);
8016     if (gameInfo.variant != VariantNormal &&
8017         gameInfo.variant != VariantLoadable
8018         /* [HGM] also send variant if board size non-standard */
8019         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8020                                             ) {
8021       char *v = VariantName(gameInfo.variant);
8022       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8023         /* [HGM] in protocol 1 we have to assume all variants valid */
8024         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8025         DisplayFatalError(buf, 0, 1);
8026         return;
8027       }
8028
8029       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8030       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8031       if( gameInfo.variant == VariantXiangqi )
8032            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8033       if( gameInfo.variant == VariantShogi )
8034            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8035       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8036            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8037       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8038                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8039            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8040       if( gameInfo.variant == VariantCourier )
8041            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8042       if( gameInfo.variant == VariantSuper )
8043            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8044       if( gameInfo.variant == VariantGreat )
8045            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8046
8047       if(overruled) {
8048            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8049                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8050            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8051            if(StrStr(cps->variants, b) == NULL) { 
8052                // specific sized variant not known, check if general sizing allowed
8053                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8054                    if(StrStr(cps->variants, "boardsize") == NULL) {
8055                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8056                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8057                        DisplayFatalError(buf, 0, 1);
8058                        return;
8059                    }
8060                    /* [HGM] here we really should compare with the maximum supported board size */
8061                }
8062            }
8063       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8064       sprintf(buf, "variant %s\n", b);
8065       SendToProgram(buf, cps);
8066     }
8067     currentlyInitializedVariant = gameInfo.variant;
8068
8069     /* [HGM] send opening position in FRC to first engine */
8070     if(setup) {
8071           SendToProgram("force\n", cps);
8072           SendBoard(cps, 0);
8073           /* engine is now in force mode! Set flag to wake it up after first move. */
8074           setboardSpoiledMachineBlack = 1;
8075     }
8076
8077     if (cps->sendICS) {
8078       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8079       SendToProgram(buf, cps);
8080     }
8081     cps->maybeThinking = FALSE;
8082     cps->offeredDraw = 0;
8083     if (!appData.icsActive) {
8084         SendTimeControl(cps, movesPerSession, timeControl,
8085                         timeIncrement, appData.searchDepth,
8086                         searchTime);
8087     }
8088     if (appData.showThinking 
8089         // [HGM] thinking: four options require thinking output to be sent
8090         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8091                                 ) {
8092         SendToProgram("post\n", cps);
8093     }
8094     SendToProgram("hard\n", cps);
8095     if (!appData.ponderNextMove) {
8096         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8097            it without being sure what state we are in first.  "hard"
8098            is not a toggle, so that one is OK.
8099          */
8100         SendToProgram("easy\n", cps);
8101     }
8102     if (cps->usePing) {
8103       sprintf(buf, "ping %d\n", ++cps->lastPing);
8104       SendToProgram(buf, cps);
8105     }
8106     cps->initDone = TRUE;
8107 }   
8108
8109
8110 void
8111 StartChessProgram(cps)
8112      ChessProgramState *cps;
8113 {
8114     char buf[MSG_SIZ];
8115     int err;
8116
8117     if (appData.noChessProgram) return;
8118     cps->initDone = FALSE;
8119
8120     if (strcmp(cps->host, "localhost") == 0) {
8121         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8122     } else if (*appData.remoteShell == NULLCHAR) {
8123         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8124     } else {
8125         if (*appData.remoteUser == NULLCHAR) {
8126           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8127                     cps->program);
8128         } else {
8129           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8130                     cps->host, appData.remoteUser, cps->program);
8131         }
8132         err = StartChildProcess(buf, "", &cps->pr);
8133     }
8134     
8135     if (err != 0) {
8136         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8137         DisplayFatalError(buf, err, 1);
8138         cps->pr = NoProc;
8139         cps->isr = NULL;
8140         return;
8141     }
8142     
8143     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8144     if (cps->protocolVersion > 1) {
8145       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8146       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8147       cps->comboCnt = 0;  //                and values of combo boxes
8148       SendToProgram(buf, cps);
8149     } else {
8150       SendToProgram("xboard\n", cps);
8151     }
8152 }
8153
8154
8155 void
8156 TwoMachinesEventIfReady P((void))
8157 {
8158   if (first.lastPing != first.lastPong) {
8159     DisplayMessage("", _("Waiting for first chess program"));
8160     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8161     return;
8162   }
8163   if (second.lastPing != second.lastPong) {
8164     DisplayMessage("", _("Waiting for second chess program"));
8165     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8166     return;
8167   }
8168   ThawUI();
8169   TwoMachinesEvent();
8170 }
8171
8172 void
8173 NextMatchGame P((void))
8174 {
8175     int index; /* [HGM] autoinc: step load index during match */
8176     Reset(FALSE, TRUE);
8177     if (*appData.loadGameFile != NULLCHAR) {
8178         index = appData.loadGameIndex;
8179         if(index < 0) { // [HGM] autoinc
8180             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8181             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8182         } 
8183         LoadGameFromFile(appData.loadGameFile,
8184                          index,
8185                          appData.loadGameFile, FALSE);
8186     } else if (*appData.loadPositionFile != NULLCHAR) {
8187         index = appData.loadPositionIndex;
8188         if(index < 0) { // [HGM] autoinc
8189             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8190             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8191         } 
8192         LoadPositionFromFile(appData.loadPositionFile,
8193                              index,
8194                              appData.loadPositionFile);
8195     }
8196     TwoMachinesEventIfReady();
8197 }
8198
8199 void UserAdjudicationEvent( int result )
8200 {
8201     ChessMove gameResult = GameIsDrawn;
8202
8203     if( result > 0 ) {
8204         gameResult = WhiteWins;
8205     }
8206     else if( result < 0 ) {
8207         gameResult = BlackWins;
8208     }
8209
8210     if( gameMode == TwoMachinesPlay ) {
8211         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8212     }
8213 }
8214
8215
8216 // [HGM] save: calculate checksum of game to make games easily identifiable
8217 int StringCheckSum(char *s)
8218 {
8219         int i = 0;
8220         if(s==NULL) return 0;
8221         while(*s) i = i*259 + *s++;
8222         return i;
8223 }
8224
8225 int GameCheckSum()
8226 {
8227         int i, sum=0;
8228         for(i=backwardMostMove; i<forwardMostMove; i++) {
8229                 sum += pvInfoList[i].depth;
8230                 sum += StringCheckSum(parseList[i]);
8231                 sum += StringCheckSum(commentList[i]);
8232                 sum *= 261;
8233         }
8234         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8235         return sum + StringCheckSum(commentList[i]);
8236 } // end of save patch
8237
8238 void
8239 GameEnds(result, resultDetails, whosays)
8240      ChessMove result;
8241      char *resultDetails;
8242      int whosays;
8243 {
8244     GameMode nextGameMode;
8245     int isIcsGame;
8246     char buf[MSG_SIZ];
8247
8248     if(endingGame) return; /* [HGM] crash: forbid recursion */
8249     endingGame = 1;
8250
8251     if (appData.debugMode) {
8252       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8253               result, resultDetails ? resultDetails : "(null)", whosays);
8254     }
8255
8256     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8257         /* If we are playing on ICS, the server decides when the
8258            game is over, but the engine can offer to draw, claim 
8259            a draw, or resign. 
8260          */
8261 #if ZIPPY
8262         if (appData.zippyPlay && first.initDone) {
8263             if (result == GameIsDrawn) {
8264                 /* In case draw still needs to be claimed */
8265                 SendToICS(ics_prefix);
8266                 SendToICS("draw\n");
8267             } else if (StrCaseStr(resultDetails, "resign")) {
8268                 SendToICS(ics_prefix);
8269                 SendToICS("resign\n");
8270             }
8271         }
8272 #endif
8273         endingGame = 0; /* [HGM] crash */
8274         return;
8275     }
8276
8277     /* If we're loading the game from a file, stop */
8278     if (whosays == GE_FILE) {
8279       (void) StopLoadGameTimer();
8280       gameFileFP = NULL;
8281     }
8282
8283     /* Cancel draw offers */
8284     first.offeredDraw = second.offeredDraw = 0;
8285
8286     /* If this is an ICS game, only ICS can really say it's done;
8287        if not, anyone can. */
8288     isIcsGame = (gameMode == IcsPlayingWhite || 
8289                  gameMode == IcsPlayingBlack || 
8290                  gameMode == IcsObserving    || 
8291                  gameMode == IcsExamining);
8292
8293     if (!isIcsGame || whosays == GE_ICS) {
8294         /* OK -- not an ICS game, or ICS said it was done */
8295         StopClocks();
8296         if (!isIcsGame && !appData.noChessProgram) 
8297           SetUserThinkingEnables();
8298     
8299         /* [HGM] if a machine claims the game end we verify this claim */
8300         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8301             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8302                 char claimer;
8303                 ChessMove trueResult = (ChessMove) -1;
8304
8305                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8306                                             first.twoMachinesColor[0] :
8307                                             second.twoMachinesColor[0] ;
8308
8309                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8310                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8311                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8312                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8313                 } else
8314                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8315                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8316                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8317                 } else
8318                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8319                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8320                 }
8321
8322                 // now verify win claims, but not in drop games, as we don't understand those yet
8323                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8324                                                  || gameInfo.variant == VariantGreat) &&
8325                     (result == WhiteWins && claimer == 'w' ||
8326                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8327                       if (appData.debugMode) {
8328                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8329                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8330                       }
8331                       if(result != trueResult) {
8332                               sprintf(buf, "False win claim: '%s'", resultDetails);
8333                               result = claimer == 'w' ? BlackWins : WhiteWins;
8334                               resultDetails = buf;
8335                       }
8336                 } else
8337                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8338                     && (forwardMostMove <= backwardMostMove ||
8339                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8340                         (claimer=='b')==(forwardMostMove&1))
8341                                                                                   ) {
8342                       /* [HGM] verify: draws that were not flagged are false claims */
8343                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8344                       result = claimer == 'w' ? BlackWins : WhiteWins;
8345                       resultDetails = buf;
8346                 }
8347                 /* (Claiming a loss is accepted no questions asked!) */
8348             }
8349             /* [HGM] bare: don't allow bare King to win */
8350             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8351                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8352                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8353                && result != GameIsDrawn)
8354             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8355                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8356                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8357                         if(p >= 0 && p <= (int)WhiteKing) k++;
8358                 }
8359                 if (appData.debugMode) {
8360                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8361                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8362                 }
8363                 if(k <= 1) {
8364                         result = GameIsDrawn;
8365                         sprintf(buf, "%s but bare king", resultDetails);
8366                         resultDetails = buf;
8367                 }
8368             }
8369         }
8370
8371
8372         if(serverMoves != NULL && !loadFlag) { char c = '=';
8373             if(result==WhiteWins) c = '+';
8374             if(result==BlackWins) c = '-';
8375             if(resultDetails != NULL)
8376                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8377         }
8378         if (resultDetails != NULL) {
8379             gameInfo.result = result;
8380             gameInfo.resultDetails = StrSave(resultDetails);
8381
8382             /* display last move only if game was not loaded from file */
8383             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8384                 DisplayMove(currentMove - 1);
8385     
8386             if (forwardMostMove != 0) {
8387                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8388                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8389                                                                 ) {
8390                     if (*appData.saveGameFile != NULLCHAR) {
8391                         SaveGameToFile(appData.saveGameFile, TRUE);
8392                     } else if (appData.autoSaveGames) {
8393                         AutoSaveGame();
8394                     }
8395                     if (*appData.savePositionFile != NULLCHAR) {
8396                         SavePositionToFile(appData.savePositionFile);
8397                     }
8398                 }
8399             }
8400
8401             /* Tell program how game ended in case it is learning */
8402             /* [HGM] Moved this to after saving the PGN, just in case */
8403             /* engine died and we got here through time loss. In that */
8404             /* case we will get a fatal error writing the pipe, which */
8405             /* would otherwise lose us the PGN.                       */
8406             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8407             /* output during GameEnds should never be fatal anymore   */
8408             if (gameMode == MachinePlaysWhite ||
8409                 gameMode == MachinePlaysBlack ||
8410                 gameMode == TwoMachinesPlay ||
8411                 gameMode == IcsPlayingWhite ||
8412                 gameMode == IcsPlayingBlack ||
8413                 gameMode == BeginningOfGame) {
8414                 char buf[MSG_SIZ];
8415                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8416                         resultDetails);
8417                 if (first.pr != NoProc) {
8418                     SendToProgram(buf, &first);
8419                 }
8420                 if (second.pr != NoProc &&
8421                     gameMode == TwoMachinesPlay) {
8422                     SendToProgram(buf, &second);
8423                 }
8424             }
8425         }
8426
8427         if (appData.icsActive) {
8428             if (appData.quietPlay &&
8429                 (gameMode == IcsPlayingWhite ||
8430                  gameMode == IcsPlayingBlack)) {
8431                 SendToICS(ics_prefix);
8432                 SendToICS("set shout 1\n");
8433             }
8434             nextGameMode = IcsIdle;
8435             ics_user_moved = FALSE;
8436             /* clean up premove.  It's ugly when the game has ended and the
8437              * premove highlights are still on the board.
8438              */
8439             if (gotPremove) {
8440               gotPremove = FALSE;
8441               ClearPremoveHighlights();
8442               DrawPosition(FALSE, boards[currentMove]);
8443             }
8444             if (whosays == GE_ICS) {
8445                 switch (result) {
8446                 case WhiteWins:
8447                     if (gameMode == IcsPlayingWhite)
8448                         PlayIcsWinSound();
8449                     else if(gameMode == IcsPlayingBlack)
8450                         PlayIcsLossSound();
8451                     break;
8452                 case BlackWins:
8453                     if (gameMode == IcsPlayingBlack)
8454                         PlayIcsWinSound();
8455                     else if(gameMode == IcsPlayingWhite)
8456                         PlayIcsLossSound();
8457                     break;
8458                 case GameIsDrawn:
8459                     PlayIcsDrawSound();
8460                     break;
8461                 default:
8462                     PlayIcsUnfinishedSound();
8463                 }
8464             }
8465         } else if (gameMode == EditGame ||
8466                    gameMode == PlayFromGameFile || 
8467                    gameMode == AnalyzeMode || 
8468                    gameMode == AnalyzeFile) {
8469             nextGameMode = gameMode;
8470         } else {
8471             nextGameMode = EndOfGame;
8472         }
8473         pausing = FALSE;
8474         ModeHighlight();
8475     } else {
8476         nextGameMode = gameMode;
8477     }
8478
8479     if (appData.noChessProgram) {
8480         gameMode = nextGameMode;
8481         ModeHighlight();
8482         endingGame = 0; /* [HGM] crash */
8483         return;
8484     }
8485
8486     if (first.reuse) {
8487         /* Put first chess program into idle state */
8488         if (first.pr != NoProc &&
8489             (gameMode == MachinePlaysWhite ||
8490              gameMode == MachinePlaysBlack ||
8491              gameMode == TwoMachinesPlay ||
8492              gameMode == IcsPlayingWhite ||
8493              gameMode == IcsPlayingBlack ||
8494              gameMode == BeginningOfGame)) {
8495             SendToProgram("force\n", &first);
8496             if (first.usePing) {
8497               char buf[MSG_SIZ];
8498               sprintf(buf, "ping %d\n", ++first.lastPing);
8499               SendToProgram(buf, &first);
8500             }
8501         }
8502     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8503         /* Kill off first chess program */
8504         if (first.isr != NULL)
8505           RemoveInputSource(first.isr);
8506         first.isr = NULL;
8507     
8508         if (first.pr != NoProc) {
8509             ExitAnalyzeMode();
8510             DoSleep( appData.delayBeforeQuit );
8511             SendToProgram("quit\n", &first);
8512             DoSleep( appData.delayAfterQuit );
8513             DestroyChildProcess(first.pr, first.useSigterm);
8514         }
8515         first.pr = NoProc;
8516     }
8517     if (second.reuse) {
8518         /* Put second chess program into idle state */
8519         if (second.pr != NoProc &&
8520             gameMode == TwoMachinesPlay) {
8521             SendToProgram("force\n", &second);
8522             if (second.usePing) {
8523               char buf[MSG_SIZ];
8524               sprintf(buf, "ping %d\n", ++second.lastPing);
8525               SendToProgram(buf, &second);
8526             }
8527         }
8528     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8529         /* Kill off second chess program */
8530         if (second.isr != NULL)
8531           RemoveInputSource(second.isr);
8532         second.isr = NULL;
8533     
8534         if (second.pr != NoProc) {
8535             DoSleep( appData.delayBeforeQuit );
8536             SendToProgram("quit\n", &second);
8537             DoSleep( appData.delayAfterQuit );
8538             DestroyChildProcess(second.pr, second.useSigterm);
8539         }
8540         second.pr = NoProc;
8541     }
8542
8543     if (matchMode && gameMode == TwoMachinesPlay) {
8544         switch (result) {
8545         case WhiteWins:
8546           if (first.twoMachinesColor[0] == 'w') {
8547             first.matchWins++;
8548           } else {
8549             second.matchWins++;
8550           }
8551           break;
8552         case BlackWins:
8553           if (first.twoMachinesColor[0] == 'b') {
8554             first.matchWins++;
8555           } else {
8556             second.matchWins++;
8557           }
8558           break;
8559         default:
8560           break;
8561         }
8562         if (matchGame < appData.matchGames) {
8563             char *tmp;
8564             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8565                 tmp = first.twoMachinesColor;
8566                 first.twoMachinesColor = second.twoMachinesColor;
8567                 second.twoMachinesColor = tmp;
8568             }
8569             gameMode = nextGameMode;
8570             matchGame++;
8571             if(appData.matchPause>10000 || appData.matchPause<10)
8572                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8573             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8574             endingGame = 0; /* [HGM] crash */
8575             return;
8576         } else {
8577             char buf[MSG_SIZ];
8578             gameMode = nextGameMode;
8579             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8580                     first.tidy, second.tidy,
8581                     first.matchWins, second.matchWins,
8582                     appData.matchGames - (first.matchWins + second.matchWins));
8583             DisplayFatalError(buf, 0, 0);
8584         }
8585     }
8586     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8587         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8588       ExitAnalyzeMode();
8589     gameMode = nextGameMode;
8590     ModeHighlight();
8591     endingGame = 0;  /* [HGM] crash */
8592 }
8593
8594 /* Assumes program was just initialized (initString sent).
8595    Leaves program in force mode. */
8596 void
8597 FeedMovesToProgram(cps, upto) 
8598      ChessProgramState *cps;
8599      int upto;
8600 {
8601     int i;
8602     
8603     if (appData.debugMode)
8604       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8605               startedFromSetupPosition ? "position and " : "",
8606               backwardMostMove, upto, cps->which);
8607     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8608         // [HGM] variantswitch: make engine aware of new variant
8609         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8610                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8611         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8612         SendToProgram(buf, cps);
8613         currentlyInitializedVariant = gameInfo.variant;
8614     }
8615     SendToProgram("force\n", cps);
8616     if (startedFromSetupPosition) {
8617         SendBoard(cps, backwardMostMove);
8618     if (appData.debugMode) {
8619         fprintf(debugFP, "feedMoves\n");
8620     }
8621     }
8622     for (i = backwardMostMove; i < upto; i++) {
8623         SendMoveToProgram(i, cps);
8624     }
8625 }
8626
8627
8628 void
8629 ResurrectChessProgram()
8630 {
8631      /* The chess program may have exited.
8632         If so, restart it and feed it all the moves made so far. */
8633
8634     if (appData.noChessProgram || first.pr != NoProc) return;
8635     
8636     StartChessProgram(&first);
8637     InitChessProgram(&first, FALSE);
8638     FeedMovesToProgram(&first, currentMove);
8639
8640     if (!first.sendTime) {
8641         /* can't tell gnuchess what its clock should read,
8642            so we bow to its notion. */
8643         ResetClocks();
8644         timeRemaining[0][currentMove] = whiteTimeRemaining;
8645         timeRemaining[1][currentMove] = blackTimeRemaining;
8646     }
8647
8648     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8649                 appData.icsEngineAnalyze) && first.analysisSupport) {
8650       SendToProgram("analyze\n", &first);
8651       first.analyzing = TRUE;
8652     }
8653 }
8654
8655 /*
8656  * Button procedures
8657  */
8658 void
8659 Reset(redraw, init)
8660      int redraw, init;
8661 {
8662     int i;
8663
8664     if (appData.debugMode) {
8665         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8666                 redraw, init, gameMode);
8667     }
8668     CleanupTail(); // [HGM] vari: delete any stored variations
8669     pausing = pauseExamInvalid = FALSE;
8670     startedFromSetupPosition = blackPlaysFirst = FALSE;
8671     firstMove = TRUE;
8672     whiteFlag = blackFlag = FALSE;
8673     userOfferedDraw = FALSE;
8674     hintRequested = bookRequested = FALSE;
8675     first.maybeThinking = FALSE;
8676     second.maybeThinking = FALSE;
8677     first.bookSuspend = FALSE; // [HGM] book
8678     second.bookSuspend = FALSE;
8679     thinkOutput[0] = NULLCHAR;
8680     lastHint[0] = NULLCHAR;
8681     ClearGameInfo(&gameInfo);
8682     gameInfo.variant = StringToVariant(appData.variant);
8683     ics_user_moved = ics_clock_paused = FALSE;
8684     ics_getting_history = H_FALSE;
8685     ics_gamenum = -1;
8686     white_holding[0] = black_holding[0] = NULLCHAR;
8687     ClearProgramStats();
8688     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8689     
8690     ResetFrontEnd();
8691     ClearHighlights();
8692     flipView = appData.flipView;
8693     ClearPremoveHighlights();
8694     gotPremove = FALSE;
8695     alarmSounded = FALSE;
8696
8697     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8698     if(appData.serverMovesName != NULL) {
8699         /* [HGM] prepare to make moves file for broadcasting */
8700         clock_t t = clock();
8701         if(serverMoves != NULL) fclose(serverMoves);
8702         serverMoves = fopen(appData.serverMovesName, "r");
8703         if(serverMoves != NULL) {
8704             fclose(serverMoves);
8705             /* delay 15 sec before overwriting, so all clients can see end */
8706             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8707         }
8708         serverMoves = fopen(appData.serverMovesName, "w");
8709     }
8710
8711     ExitAnalyzeMode();
8712     gameMode = BeginningOfGame;
8713     ModeHighlight();
8714     if(appData.icsActive) gameInfo.variant = VariantNormal;
8715     currentMove = forwardMostMove = backwardMostMove = 0;
8716     InitPosition(redraw);
8717     for (i = 0; i < MAX_MOVES; i++) {
8718         if (commentList[i] != NULL) {
8719             free(commentList[i]);
8720             commentList[i] = NULL;
8721         }
8722     }
8723     ResetClocks();
8724     timeRemaining[0][0] = whiteTimeRemaining;
8725     timeRemaining[1][0] = blackTimeRemaining;
8726     if (first.pr == NULL) {
8727         StartChessProgram(&first);
8728     }
8729     if (init) {
8730             InitChessProgram(&first, startedFromSetupPosition);
8731     }
8732     DisplayTitle("");
8733     DisplayMessage("", "");
8734     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8735     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8736 }
8737
8738 void
8739 AutoPlayGameLoop()
8740 {
8741     for (;;) {
8742         if (!AutoPlayOneMove())
8743           return;
8744         if (matchMode || appData.timeDelay == 0)
8745           continue;
8746         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8747           return;
8748         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8749         break;
8750     }
8751 }
8752
8753
8754 int
8755 AutoPlayOneMove()
8756 {
8757     int fromX, fromY, toX, toY;
8758
8759     if (appData.debugMode) {
8760       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8761     }
8762
8763     if (gameMode != PlayFromGameFile)
8764       return FALSE;
8765
8766     if (currentMove >= forwardMostMove) {
8767       gameMode = EditGame;
8768       ModeHighlight();
8769
8770       /* [AS] Clear current move marker at the end of a game */
8771       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8772
8773       return FALSE;
8774     }
8775     
8776     toX = moveList[currentMove][2] - AAA;
8777     toY = moveList[currentMove][3] - ONE;
8778
8779     if (moveList[currentMove][1] == '@') {
8780         if (appData.highlightLastMove) {
8781             SetHighlights(-1, -1, toX, toY);
8782         }
8783     } else {
8784         fromX = moveList[currentMove][0] - AAA;
8785         fromY = moveList[currentMove][1] - ONE;
8786
8787         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8788
8789         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8790
8791         if (appData.highlightLastMove) {
8792             SetHighlights(fromX, fromY, toX, toY);
8793         }
8794     }
8795     DisplayMove(currentMove);
8796     SendMoveToProgram(currentMove++, &first);
8797     DisplayBothClocks();
8798     DrawPosition(FALSE, boards[currentMove]);
8799     // [HGM] PV info: always display, routine tests if empty
8800     DisplayComment(currentMove - 1, commentList[currentMove]);
8801     return TRUE;
8802 }
8803
8804
8805 int
8806 LoadGameOneMove(readAhead)
8807      ChessMove readAhead;
8808 {
8809     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8810     char promoChar = NULLCHAR;
8811     ChessMove moveType;
8812     char move[MSG_SIZ];
8813     char *p, *q;
8814     
8815     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8816         gameMode != AnalyzeMode && gameMode != Training) {
8817         gameFileFP = NULL;
8818         return FALSE;
8819     }
8820     
8821     yyboardindex = forwardMostMove;
8822     if (readAhead != (ChessMove)0) {
8823       moveType = readAhead;
8824     } else {
8825       if (gameFileFP == NULL)
8826           return FALSE;
8827       moveType = (ChessMove) yylex();
8828     }
8829     
8830     done = FALSE;
8831     switch (moveType) {
8832       case Comment:
8833         if (appData.debugMode) 
8834           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8835         p = yy_text;
8836
8837         /* append the comment but don't display it */
8838         AppendComment(currentMove, p, FALSE);
8839         return TRUE;
8840
8841       case WhiteCapturesEnPassant:
8842       case BlackCapturesEnPassant:
8843       case WhitePromotionChancellor:
8844       case BlackPromotionChancellor:
8845       case WhitePromotionArchbishop:
8846       case BlackPromotionArchbishop:
8847       case WhitePromotionCentaur:
8848       case BlackPromotionCentaur:
8849       case WhitePromotionQueen:
8850       case BlackPromotionQueen:
8851       case WhitePromotionRook:
8852       case BlackPromotionRook:
8853       case WhitePromotionBishop:
8854       case BlackPromotionBishop:
8855       case WhitePromotionKnight:
8856       case BlackPromotionKnight:
8857       case WhitePromotionKing:
8858       case BlackPromotionKing:
8859       case NormalMove:
8860       case WhiteKingSideCastle:
8861       case WhiteQueenSideCastle:
8862       case BlackKingSideCastle:
8863       case BlackQueenSideCastle:
8864       case WhiteKingSideCastleWild:
8865       case WhiteQueenSideCastleWild:
8866       case BlackKingSideCastleWild:
8867       case BlackQueenSideCastleWild:
8868       /* PUSH Fabien */
8869       case WhiteHSideCastleFR:
8870       case WhiteASideCastleFR:
8871       case BlackHSideCastleFR:
8872       case BlackASideCastleFR:
8873       /* POP Fabien */
8874         if (appData.debugMode)
8875           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8876         fromX = currentMoveString[0] - AAA;
8877         fromY = currentMoveString[1] - ONE;
8878         toX = currentMoveString[2] - AAA;
8879         toY = currentMoveString[3] - ONE;
8880         promoChar = currentMoveString[4];
8881         break;
8882
8883       case WhiteDrop:
8884       case BlackDrop:
8885         if (appData.debugMode)
8886           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8887         fromX = moveType == WhiteDrop ?
8888           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8889         (int) CharToPiece(ToLower(currentMoveString[0]));
8890         fromY = DROP_RANK;
8891         toX = currentMoveString[2] - AAA;
8892         toY = currentMoveString[3] - ONE;
8893         break;
8894
8895       case WhiteWins:
8896       case BlackWins:
8897       case GameIsDrawn:
8898       case GameUnfinished:
8899         if (appData.debugMode)
8900           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8901         p = strchr(yy_text, '{');
8902         if (p == NULL) p = strchr(yy_text, '(');
8903         if (p == NULL) {
8904             p = yy_text;
8905             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8906         } else {
8907             q = strchr(p, *p == '{' ? '}' : ')');
8908             if (q != NULL) *q = NULLCHAR;
8909             p++;
8910         }
8911         GameEnds(moveType, p, GE_FILE);
8912         done = TRUE;
8913         if (cmailMsgLoaded) {
8914             ClearHighlights();
8915             flipView = WhiteOnMove(currentMove);
8916             if (moveType == GameUnfinished) flipView = !flipView;
8917             if (appData.debugMode)
8918               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8919         }
8920         break;
8921
8922       case (ChessMove) 0:       /* end of file */
8923         if (appData.debugMode)
8924           fprintf(debugFP, "Parser hit end of file\n");
8925         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8926           case MT_NONE:
8927           case MT_CHECK:
8928             break;
8929           case MT_CHECKMATE:
8930           case MT_STAINMATE:
8931             if (WhiteOnMove(currentMove)) {
8932                 GameEnds(BlackWins, "Black mates", GE_FILE);
8933             } else {
8934                 GameEnds(WhiteWins, "White mates", GE_FILE);
8935             }
8936             break;
8937           case MT_STALEMATE:
8938             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8939             break;
8940         }
8941         done = TRUE;
8942         break;
8943
8944       case MoveNumberOne:
8945         if (lastLoadGameStart == GNUChessGame) {
8946             /* GNUChessGames have numbers, but they aren't move numbers */
8947             if (appData.debugMode)
8948               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8949                       yy_text, (int) moveType);
8950             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8951         }
8952         /* else fall thru */
8953
8954       case XBoardGame:
8955       case GNUChessGame:
8956       case PGNTag:
8957         /* Reached start of next game in file */
8958         if (appData.debugMode)
8959           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8960         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8961           case MT_NONE:
8962           case MT_CHECK:
8963             break;
8964           case MT_CHECKMATE:
8965           case MT_STAINMATE:
8966             if (WhiteOnMove(currentMove)) {
8967                 GameEnds(BlackWins, "Black mates", GE_FILE);
8968             } else {
8969                 GameEnds(WhiteWins, "White mates", GE_FILE);
8970             }
8971             break;
8972           case MT_STALEMATE:
8973             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8974             break;
8975         }
8976         done = TRUE;
8977         break;
8978
8979       case PositionDiagram:     /* should not happen; ignore */
8980       case ElapsedTime:         /* ignore */
8981       case NAG:                 /* ignore */
8982         if (appData.debugMode)
8983           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8984                   yy_text, (int) moveType);
8985         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8986
8987       case IllegalMove:
8988         if (appData.testLegality) {
8989             if (appData.debugMode)
8990               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8991             sprintf(move, _("Illegal move: %d.%s%s"),
8992                     (forwardMostMove / 2) + 1,
8993                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8994             DisplayError(move, 0);
8995             done = TRUE;
8996         } else {
8997             if (appData.debugMode)
8998               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8999                       yy_text, currentMoveString);
9000             fromX = currentMoveString[0] - AAA;
9001             fromY = currentMoveString[1] - ONE;
9002             toX = currentMoveString[2] - AAA;
9003             toY = currentMoveString[3] - ONE;
9004             promoChar = currentMoveString[4];
9005         }
9006         break;
9007
9008       case AmbiguousMove:
9009         if (appData.debugMode)
9010           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9011         sprintf(move, _("Ambiguous move: %d.%s%s"),
9012                 (forwardMostMove / 2) + 1,
9013                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9014         DisplayError(move, 0);
9015         done = TRUE;
9016         break;
9017
9018       default:
9019       case ImpossibleMove:
9020         if (appData.debugMode)
9021           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9022         sprintf(move, _("Illegal move: %d.%s%s"),
9023                 (forwardMostMove / 2) + 1,
9024                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9025         DisplayError(move, 0);
9026         done = TRUE;
9027         break;
9028     }
9029
9030     if (done) {
9031         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9032             DrawPosition(FALSE, boards[currentMove]);
9033             DisplayBothClocks();
9034             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9035               DisplayComment(currentMove - 1, commentList[currentMove]);
9036         }
9037         (void) StopLoadGameTimer();
9038         gameFileFP = NULL;
9039         cmailOldMove = forwardMostMove;
9040         return FALSE;
9041     } else {
9042         /* currentMoveString is set as a side-effect of yylex */
9043         strcat(currentMoveString, "\n");
9044         strcpy(moveList[forwardMostMove], currentMoveString);
9045         
9046         thinkOutput[0] = NULLCHAR;
9047         MakeMove(fromX, fromY, toX, toY, promoChar);
9048         currentMove = forwardMostMove;
9049         return TRUE;
9050     }
9051 }
9052
9053 /* Load the nth game from the given file */
9054 int
9055 LoadGameFromFile(filename, n, title, useList)
9056      char *filename;
9057      int n;
9058      char *title;
9059      /*Boolean*/ int useList;
9060 {
9061     FILE *f;
9062     char buf[MSG_SIZ];
9063
9064     if (strcmp(filename, "-") == 0) {
9065         f = stdin;
9066         title = "stdin";
9067     } else {
9068         f = fopen(filename, "rb");
9069         if (f == NULL) {
9070           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9071             DisplayError(buf, errno);
9072             return FALSE;
9073         }
9074     }
9075     if (fseek(f, 0, 0) == -1) {
9076         /* f is not seekable; probably a pipe */
9077         useList = FALSE;
9078     }
9079     if (useList && n == 0) {
9080         int error = GameListBuild(f);
9081         if (error) {
9082             DisplayError(_("Cannot build game list"), error);
9083         } else if (!ListEmpty(&gameList) &&
9084                    ((ListGame *) gameList.tailPred)->number > 1) {
9085             GameListPopUp(f, title);
9086             return TRUE;
9087         }
9088         GameListDestroy();
9089         n = 1;
9090     }
9091     if (n == 0) n = 1;
9092     return LoadGame(f, n, title, FALSE);
9093 }
9094
9095
9096 void
9097 MakeRegisteredMove()
9098 {
9099     int fromX, fromY, toX, toY;
9100     char promoChar;
9101     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9102         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9103           case CMAIL_MOVE:
9104           case CMAIL_DRAW:
9105             if (appData.debugMode)
9106               fprintf(debugFP, "Restoring %s for game %d\n",
9107                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9108     
9109             thinkOutput[0] = NULLCHAR;
9110             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9111             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9112             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9113             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9114             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9115             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9116             MakeMove(fromX, fromY, toX, toY, promoChar);
9117             ShowMove(fromX, fromY, toX, toY);
9118               
9119             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9120               case MT_NONE:
9121               case MT_CHECK:
9122                 break;
9123                 
9124               case MT_CHECKMATE:
9125               case MT_STAINMATE:
9126                 if (WhiteOnMove(currentMove)) {
9127                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9128                 } else {
9129                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9130                 }
9131                 break;
9132                 
9133               case MT_STALEMATE:
9134                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9135                 break;
9136             }
9137
9138             break;
9139             
9140           case CMAIL_RESIGN:
9141             if (WhiteOnMove(currentMove)) {
9142                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9143             } else {
9144                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9145             }
9146             break;
9147             
9148           case CMAIL_ACCEPT:
9149             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9150             break;
9151               
9152           default:
9153             break;
9154         }
9155     }
9156
9157     return;
9158 }
9159
9160 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9161 int
9162 CmailLoadGame(f, gameNumber, title, useList)
9163      FILE *f;
9164      int gameNumber;
9165      char *title;
9166      int useList;
9167 {
9168     int retVal;
9169
9170     if (gameNumber > nCmailGames) {
9171         DisplayError(_("No more games in this message"), 0);
9172         return FALSE;
9173     }
9174     if (f == lastLoadGameFP) {
9175         int offset = gameNumber - lastLoadGameNumber;
9176         if (offset == 0) {
9177             cmailMsg[0] = NULLCHAR;
9178             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9179                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9180                 nCmailMovesRegistered--;
9181             }
9182             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9183             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9184                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9185             }
9186         } else {
9187             if (! RegisterMove()) return FALSE;
9188         }
9189     }
9190
9191     retVal = LoadGame(f, gameNumber, title, useList);
9192
9193     /* Make move registered during previous look at this game, if any */
9194     MakeRegisteredMove();
9195
9196     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9197         commentList[currentMove]
9198           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9199         DisplayComment(currentMove - 1, commentList[currentMove]);
9200     }
9201
9202     return retVal;
9203 }
9204
9205 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9206 int
9207 ReloadGame(offset)
9208      int offset;
9209 {
9210     int gameNumber = lastLoadGameNumber + offset;
9211     if (lastLoadGameFP == NULL) {
9212         DisplayError(_("No game has been loaded yet"), 0);
9213         return FALSE;
9214     }
9215     if (gameNumber <= 0) {
9216         DisplayError(_("Can't back up any further"), 0);
9217         return FALSE;
9218     }
9219     if (cmailMsgLoaded) {
9220         return CmailLoadGame(lastLoadGameFP, gameNumber,
9221                              lastLoadGameTitle, lastLoadGameUseList);
9222     } else {
9223         return LoadGame(lastLoadGameFP, gameNumber,
9224                         lastLoadGameTitle, lastLoadGameUseList);
9225     }
9226 }
9227
9228
9229
9230 /* Load the nth game from open file f */
9231 int
9232 LoadGame(f, gameNumber, title, useList)
9233      FILE *f;
9234      int gameNumber;
9235      char *title;
9236      int useList;
9237 {
9238     ChessMove cm;
9239     char buf[MSG_SIZ];
9240     int gn = gameNumber;
9241     ListGame *lg = NULL;
9242     int numPGNTags = 0;
9243     int err;
9244     GameMode oldGameMode;
9245     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9246
9247     if (appData.debugMode) 
9248         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9249
9250     if (gameMode == Training )
9251         SetTrainingModeOff();
9252
9253     oldGameMode = gameMode;
9254     if (gameMode != BeginningOfGame) {
9255       Reset(FALSE, TRUE);
9256     }
9257
9258     gameFileFP = f;
9259     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9260         fclose(lastLoadGameFP);
9261     }
9262
9263     if (useList) {
9264         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9265         
9266         if (lg) {
9267             fseek(f, lg->offset, 0);
9268             GameListHighlight(gameNumber);
9269             gn = 1;
9270         }
9271         else {
9272             DisplayError(_("Game number out of range"), 0);
9273             return FALSE;
9274         }
9275     } else {
9276         GameListDestroy();
9277         if (fseek(f, 0, 0) == -1) {
9278             if (f == lastLoadGameFP ?
9279                 gameNumber == lastLoadGameNumber + 1 :
9280                 gameNumber == 1) {
9281                 gn = 1;
9282             } else {
9283                 DisplayError(_("Can't seek on game file"), 0);
9284                 return FALSE;
9285             }
9286         }
9287     }
9288     lastLoadGameFP = f;
9289     lastLoadGameNumber = gameNumber;
9290     strcpy(lastLoadGameTitle, title);
9291     lastLoadGameUseList = useList;
9292
9293     yynewfile(f);
9294
9295     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9296       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9297                 lg->gameInfo.black);
9298             DisplayTitle(buf);
9299     } else if (*title != NULLCHAR) {
9300         if (gameNumber > 1) {
9301             sprintf(buf, "%s %d", title, gameNumber);
9302             DisplayTitle(buf);
9303         } else {
9304             DisplayTitle(title);
9305         }
9306     }
9307
9308     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9309         gameMode = PlayFromGameFile;
9310         ModeHighlight();
9311     }
9312
9313     currentMove = forwardMostMove = backwardMostMove = 0;
9314     CopyBoard(boards[0], initialPosition);
9315     StopClocks();
9316
9317     /*
9318      * Skip the first gn-1 games in the file.
9319      * Also skip over anything that precedes an identifiable 
9320      * start of game marker, to avoid being confused by 
9321      * garbage at the start of the file.  Currently 
9322      * recognized start of game markers are the move number "1",
9323      * the pattern "gnuchess .* game", the pattern
9324      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9325      * A game that starts with one of the latter two patterns
9326      * will also have a move number 1, possibly
9327      * following a position diagram.
9328      * 5-4-02: Let's try being more lenient and allowing a game to
9329      * start with an unnumbered move.  Does that break anything?
9330      */
9331     cm = lastLoadGameStart = (ChessMove) 0;
9332     while (gn > 0) {
9333         yyboardindex = forwardMostMove;
9334         cm = (ChessMove) yylex();
9335         switch (cm) {
9336           case (ChessMove) 0:
9337             if (cmailMsgLoaded) {
9338                 nCmailGames = CMAIL_MAX_GAMES - gn;
9339             } else {
9340                 Reset(TRUE, TRUE);
9341                 DisplayError(_("Game not found in file"), 0);
9342             }
9343             return FALSE;
9344
9345           case GNUChessGame:
9346           case XBoardGame:
9347             gn--;
9348             lastLoadGameStart = cm;
9349             break;
9350             
9351           case MoveNumberOne:
9352             switch (lastLoadGameStart) {
9353               case GNUChessGame:
9354               case XBoardGame:
9355               case PGNTag:
9356                 break;
9357               case MoveNumberOne:
9358               case (ChessMove) 0:
9359                 gn--;           /* count this game */
9360                 lastLoadGameStart = cm;
9361                 break;
9362               default:
9363                 /* impossible */
9364                 break;
9365             }
9366             break;
9367
9368           case PGNTag:
9369             switch (lastLoadGameStart) {
9370               case GNUChessGame:
9371               case PGNTag:
9372               case MoveNumberOne:
9373               case (ChessMove) 0:
9374                 gn--;           /* count this game */
9375                 lastLoadGameStart = cm;
9376                 break;
9377               case XBoardGame:
9378                 lastLoadGameStart = cm; /* game counted already */
9379                 break;
9380               default:
9381                 /* impossible */
9382                 break;
9383             }
9384             if (gn > 0) {
9385                 do {
9386                     yyboardindex = forwardMostMove;
9387                     cm = (ChessMove) yylex();
9388                 } while (cm == PGNTag || cm == Comment);
9389             }
9390             break;
9391
9392           case WhiteWins:
9393           case BlackWins:
9394           case GameIsDrawn:
9395             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9396                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9397                     != CMAIL_OLD_RESULT) {
9398                     nCmailResults ++ ;
9399                     cmailResult[  CMAIL_MAX_GAMES
9400                                 - gn - 1] = CMAIL_OLD_RESULT;
9401                 }
9402             }
9403             break;
9404
9405           case NormalMove:
9406             /* Only a NormalMove can be at the start of a game
9407              * without a position diagram. */
9408             if (lastLoadGameStart == (ChessMove) 0) {
9409               gn--;
9410               lastLoadGameStart = MoveNumberOne;
9411             }
9412             break;
9413
9414           default:
9415             break;
9416         }
9417     }
9418     
9419     if (appData.debugMode)
9420       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9421
9422     if (cm == XBoardGame) {
9423         /* Skip any header junk before position diagram and/or move 1 */
9424         for (;;) {
9425             yyboardindex = forwardMostMove;
9426             cm = (ChessMove) yylex();
9427
9428             if (cm == (ChessMove) 0 ||
9429                 cm == GNUChessGame || cm == XBoardGame) {
9430                 /* Empty game; pretend end-of-file and handle later */
9431                 cm = (ChessMove) 0;
9432                 break;
9433             }
9434
9435             if (cm == MoveNumberOne || cm == PositionDiagram ||
9436                 cm == PGNTag || cm == Comment)
9437               break;
9438         }
9439     } else if (cm == GNUChessGame) {
9440         if (gameInfo.event != NULL) {
9441             free(gameInfo.event);
9442         }
9443         gameInfo.event = StrSave(yy_text);
9444     }   
9445
9446     startedFromSetupPosition = FALSE;
9447     while (cm == PGNTag) {
9448         if (appData.debugMode) 
9449           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9450         err = ParsePGNTag(yy_text, &gameInfo);
9451         if (!err) numPGNTags++;
9452
9453         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9454         if(gameInfo.variant != oldVariant) {
9455             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9456             InitPosition(TRUE);
9457             oldVariant = gameInfo.variant;
9458             if (appData.debugMode) 
9459               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9460         }
9461
9462
9463         if (gameInfo.fen != NULL) {
9464           Board initial_position;
9465           startedFromSetupPosition = TRUE;
9466           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9467             Reset(TRUE, TRUE);
9468             DisplayError(_("Bad FEN position in file"), 0);
9469             return FALSE;
9470           }
9471           CopyBoard(boards[0], initial_position);
9472           if (blackPlaysFirst) {
9473             currentMove = forwardMostMove = backwardMostMove = 1;
9474             CopyBoard(boards[1], initial_position);
9475             strcpy(moveList[0], "");
9476             strcpy(parseList[0], "");
9477             timeRemaining[0][1] = whiteTimeRemaining;
9478             timeRemaining[1][1] = blackTimeRemaining;
9479             if (commentList[0] != NULL) {
9480               commentList[1] = commentList[0];
9481               commentList[0] = NULL;
9482             }
9483           } else {
9484             currentMove = forwardMostMove = backwardMostMove = 0;
9485           }
9486           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9487           {   int i;
9488               initialRulePlies = FENrulePlies;
9489               for( i=0; i< nrCastlingRights; i++ )
9490                   initialRights[i] = initial_position[CASTLING][i];
9491           }
9492           yyboardindex = forwardMostMove;
9493           free(gameInfo.fen);
9494           gameInfo.fen = NULL;
9495         }
9496
9497         yyboardindex = forwardMostMove;
9498         cm = (ChessMove) yylex();
9499
9500         /* Handle comments interspersed among the tags */
9501         while (cm == Comment) {
9502             char *p;
9503             if (appData.debugMode) 
9504               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9505             p = yy_text;
9506             AppendComment(currentMove, p, FALSE);
9507             yyboardindex = forwardMostMove;
9508             cm = (ChessMove) yylex();
9509         }
9510     }
9511
9512     /* don't rely on existence of Event tag since if game was
9513      * pasted from clipboard the Event tag may not exist
9514      */
9515     if (numPGNTags > 0){
9516         char *tags;
9517         if (gameInfo.variant == VariantNormal) {
9518           gameInfo.variant = StringToVariant(gameInfo.event);
9519         }
9520         if (!matchMode) {
9521           if( appData.autoDisplayTags ) {
9522             tags = PGNTags(&gameInfo);
9523             TagsPopUp(tags, CmailMsg());
9524             free(tags);
9525           }
9526         }
9527     } else {
9528         /* Make something up, but don't display it now */
9529         SetGameInfo();
9530         TagsPopDown();
9531     }
9532
9533     if (cm == PositionDiagram) {
9534         int i, j;
9535         char *p;
9536         Board initial_position;
9537
9538         if (appData.debugMode)
9539           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9540
9541         if (!startedFromSetupPosition) {
9542             p = yy_text;
9543             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9544               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9545                 switch (*p) {
9546                   case '[':
9547                   case '-':
9548                   case ' ':
9549                   case '\t':
9550                   case '\n':
9551                   case '\r':
9552                     break;
9553                   default:
9554                     initial_position[i][j++] = CharToPiece(*p);
9555                     break;
9556                 }
9557             while (*p == ' ' || *p == '\t' ||
9558                    *p == '\n' || *p == '\r') p++;
9559         
9560             if (strncmp(p, "black", strlen("black"))==0)
9561               blackPlaysFirst = TRUE;
9562             else
9563               blackPlaysFirst = FALSE;
9564             startedFromSetupPosition = TRUE;
9565         
9566             CopyBoard(boards[0], initial_position);
9567             if (blackPlaysFirst) {
9568                 currentMove = forwardMostMove = backwardMostMove = 1;
9569                 CopyBoard(boards[1], initial_position);
9570                 strcpy(moveList[0], "");
9571                 strcpy(parseList[0], "");
9572                 timeRemaining[0][1] = whiteTimeRemaining;
9573                 timeRemaining[1][1] = blackTimeRemaining;
9574                 if (commentList[0] != NULL) {
9575                     commentList[1] = commentList[0];
9576                     commentList[0] = NULL;
9577                 }
9578             } else {
9579                 currentMove = forwardMostMove = backwardMostMove = 0;
9580             }
9581         }
9582         yyboardindex = forwardMostMove;
9583         cm = (ChessMove) yylex();
9584     }
9585
9586     if (first.pr == NoProc) {
9587         StartChessProgram(&first);
9588     }
9589     InitChessProgram(&first, FALSE);
9590     SendToProgram("force\n", &first);
9591     if (startedFromSetupPosition) {
9592         SendBoard(&first, forwardMostMove);
9593     if (appData.debugMode) {
9594         fprintf(debugFP, "Load Game\n");
9595     }
9596         DisplayBothClocks();
9597     }      
9598
9599     /* [HGM] server: flag to write setup moves in broadcast file as one */
9600     loadFlag = appData.suppressLoadMoves;
9601
9602     while (cm == Comment) {
9603         char *p;
9604         if (appData.debugMode) 
9605           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9606         p = yy_text;
9607         AppendComment(currentMove, p, FALSE);
9608         yyboardindex = forwardMostMove;
9609         cm = (ChessMove) yylex();
9610     }
9611
9612     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9613         cm == WhiteWins || cm == BlackWins ||
9614         cm == GameIsDrawn || cm == GameUnfinished) {
9615         DisplayMessage("", _("No moves in game"));
9616         if (cmailMsgLoaded) {
9617             if (appData.debugMode)
9618               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9619             ClearHighlights();
9620             flipView = FALSE;
9621         }
9622         DrawPosition(FALSE, boards[currentMove]);
9623         DisplayBothClocks();
9624         gameMode = EditGame;
9625         ModeHighlight();
9626         gameFileFP = NULL;
9627         cmailOldMove = 0;
9628         return TRUE;
9629     }
9630
9631     // [HGM] PV info: routine tests if comment empty
9632     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9633         DisplayComment(currentMove - 1, commentList[currentMove]);
9634     }
9635     if (!matchMode && appData.timeDelay != 0) 
9636       DrawPosition(FALSE, boards[currentMove]);
9637
9638     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9639       programStats.ok_to_send = 1;
9640     }
9641
9642     /* if the first token after the PGN tags is a move
9643      * and not move number 1, retrieve it from the parser 
9644      */
9645     if (cm != MoveNumberOne)
9646         LoadGameOneMove(cm);
9647
9648     /* load the remaining moves from the file */
9649     while (LoadGameOneMove((ChessMove)0)) {
9650       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9651       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9652     }
9653
9654     /* rewind to the start of the game */
9655     currentMove = backwardMostMove;
9656
9657     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9658
9659     if (oldGameMode == AnalyzeFile ||
9660         oldGameMode == AnalyzeMode) {
9661       AnalyzeFileEvent();
9662     }
9663
9664     if (matchMode || appData.timeDelay == 0) {
9665       ToEndEvent();
9666       gameMode = EditGame;
9667       ModeHighlight();
9668     } else if (appData.timeDelay > 0) {
9669       AutoPlayGameLoop();
9670     }
9671
9672     if (appData.debugMode) 
9673         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9674
9675     loadFlag = 0; /* [HGM] true game starts */
9676     return TRUE;
9677 }
9678
9679 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9680 int
9681 ReloadPosition(offset)
9682      int offset;
9683 {
9684     int positionNumber = lastLoadPositionNumber + offset;
9685     if (lastLoadPositionFP == NULL) {
9686         DisplayError(_("No position has been loaded yet"), 0);
9687         return FALSE;
9688     }
9689     if (positionNumber <= 0) {
9690         DisplayError(_("Can't back up any further"), 0);
9691         return FALSE;
9692     }
9693     return LoadPosition(lastLoadPositionFP, positionNumber,
9694                         lastLoadPositionTitle);
9695 }
9696
9697 /* Load the nth position from the given file */
9698 int
9699 LoadPositionFromFile(filename, n, title)
9700      char *filename;
9701      int n;
9702      char *title;
9703 {
9704     FILE *f;
9705     char buf[MSG_SIZ];
9706
9707     if (strcmp(filename, "-") == 0) {
9708         return LoadPosition(stdin, n, "stdin");
9709     } else {
9710         f = fopen(filename, "rb");
9711         if (f == NULL) {
9712             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9713             DisplayError(buf, errno);
9714             return FALSE;
9715         } else {
9716             return LoadPosition(f, n, title);
9717         }
9718     }
9719 }
9720
9721 /* Load the nth position from the given open file, and close it */
9722 int
9723 LoadPosition(f, positionNumber, title)
9724      FILE *f;
9725      int positionNumber;
9726      char *title;
9727 {
9728     char *p, line[MSG_SIZ];
9729     Board initial_position;
9730     int i, j, fenMode, pn;
9731     
9732     if (gameMode == Training )
9733         SetTrainingModeOff();
9734
9735     if (gameMode != BeginningOfGame) {
9736         Reset(FALSE, TRUE);
9737     }
9738     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9739         fclose(lastLoadPositionFP);
9740     }
9741     if (positionNumber == 0) positionNumber = 1;
9742     lastLoadPositionFP = f;
9743     lastLoadPositionNumber = positionNumber;
9744     strcpy(lastLoadPositionTitle, title);
9745     if (first.pr == NoProc) {
9746       StartChessProgram(&first);
9747       InitChessProgram(&first, FALSE);
9748     }    
9749     pn = positionNumber;
9750     if (positionNumber < 0) {
9751         /* Negative position number means to seek to that byte offset */
9752         if (fseek(f, -positionNumber, 0) == -1) {
9753             DisplayError(_("Can't seek on position file"), 0);
9754             return FALSE;
9755         };
9756         pn = 1;
9757     } else {
9758         if (fseek(f, 0, 0) == -1) {
9759             if (f == lastLoadPositionFP ?
9760                 positionNumber == lastLoadPositionNumber + 1 :
9761                 positionNumber == 1) {
9762                 pn = 1;
9763             } else {
9764                 DisplayError(_("Can't seek on position file"), 0);
9765                 return FALSE;
9766             }
9767         }
9768     }
9769     /* See if this file is FEN or old-style xboard */
9770     if (fgets(line, MSG_SIZ, f) == NULL) {
9771         DisplayError(_("Position not found in file"), 0);
9772         return FALSE;
9773     }
9774     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9775     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9776
9777     if (pn >= 2) {
9778         if (fenMode || line[0] == '#') pn--;
9779         while (pn > 0) {
9780             /* skip positions before number pn */
9781             if (fgets(line, MSG_SIZ, f) == NULL) {
9782                 Reset(TRUE, TRUE);
9783                 DisplayError(_("Position not found in file"), 0);
9784                 return FALSE;
9785             }
9786             if (fenMode || line[0] == '#') pn--;
9787         }
9788     }
9789
9790     if (fenMode) {
9791         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9792             DisplayError(_("Bad FEN position in file"), 0);
9793             return FALSE;
9794         }
9795     } else {
9796         (void) fgets(line, MSG_SIZ, f);
9797         (void) fgets(line, MSG_SIZ, f);
9798     
9799         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9800             (void) fgets(line, MSG_SIZ, f);
9801             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9802                 if (*p == ' ')
9803                   continue;
9804                 initial_position[i][j++] = CharToPiece(*p);
9805             }
9806         }
9807     
9808         blackPlaysFirst = FALSE;
9809         if (!feof(f)) {
9810             (void) fgets(line, MSG_SIZ, f);
9811             if (strncmp(line, "black", strlen("black"))==0)
9812               blackPlaysFirst = TRUE;
9813         }
9814     }
9815     startedFromSetupPosition = TRUE;
9816     
9817     SendToProgram("force\n", &first);
9818     CopyBoard(boards[0], initial_position);
9819     if (blackPlaysFirst) {
9820         currentMove = forwardMostMove = backwardMostMove = 1;
9821         strcpy(moveList[0], "");
9822         strcpy(parseList[0], "");
9823         CopyBoard(boards[1], initial_position);
9824         DisplayMessage("", _("Black to play"));
9825     } else {
9826         currentMove = forwardMostMove = backwardMostMove = 0;
9827         DisplayMessage("", _("White to play"));
9828     }
9829     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9830     SendBoard(&first, forwardMostMove);
9831     if (appData.debugMode) {
9832 int i, j;
9833   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9834   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9835         fprintf(debugFP, "Load Position\n");
9836     }
9837
9838     if (positionNumber > 1) {
9839         sprintf(line, "%s %d", title, positionNumber);
9840         DisplayTitle(line);
9841     } else {
9842         DisplayTitle(title);
9843     }
9844     gameMode = EditGame;
9845     ModeHighlight();
9846     ResetClocks();
9847     timeRemaining[0][1] = whiteTimeRemaining;
9848     timeRemaining[1][1] = blackTimeRemaining;
9849     DrawPosition(FALSE, boards[currentMove]);
9850    
9851     return TRUE;
9852 }
9853
9854
9855 void
9856 CopyPlayerNameIntoFileName(dest, src)
9857      char **dest, *src;
9858 {
9859     while (*src != NULLCHAR && *src != ',') {
9860         if (*src == ' ') {
9861             *(*dest)++ = '_';
9862             src++;
9863         } else {
9864             *(*dest)++ = *src++;
9865         }
9866     }
9867 }
9868
9869 char *DefaultFileName(ext)
9870      char *ext;
9871 {
9872     static char def[MSG_SIZ];
9873     char *p;
9874
9875     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9876         p = def;
9877         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9878         *p++ = '-';
9879         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9880         *p++ = '.';
9881         strcpy(p, ext);
9882     } else {
9883         def[0] = NULLCHAR;
9884     }
9885     return def;
9886 }
9887
9888 /* Save the current game to the given file */
9889 int
9890 SaveGameToFile(filename, append)
9891      char *filename;
9892      int append;
9893 {
9894     FILE *f;
9895     char buf[MSG_SIZ];
9896
9897     if (strcmp(filename, "-") == 0) {
9898         return SaveGame(stdout, 0, NULL);
9899     } else {
9900         f = fopen(filename, append ? "a" : "w");
9901         if (f == NULL) {
9902             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9903             DisplayError(buf, errno);
9904             return FALSE;
9905         } else {
9906             return SaveGame(f, 0, NULL);
9907         }
9908     }
9909 }
9910
9911 char *
9912 SavePart(str)
9913      char *str;
9914 {
9915     static char buf[MSG_SIZ];
9916     char *p;
9917     
9918     p = strchr(str, ' ');
9919     if (p == NULL) return str;
9920     strncpy(buf, str, p - str);
9921     buf[p - str] = NULLCHAR;
9922     return buf;
9923 }
9924
9925 #define PGN_MAX_LINE 75
9926
9927 #define PGN_SIDE_WHITE  0
9928 #define PGN_SIDE_BLACK  1
9929
9930 /* [AS] */
9931 static int FindFirstMoveOutOfBook( int side )
9932 {
9933     int result = -1;
9934
9935     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9936         int index = backwardMostMove;
9937         int has_book_hit = 0;
9938
9939         if( (index % 2) != side ) {
9940             index++;
9941         }
9942
9943         while( index < forwardMostMove ) {
9944             /* Check to see if engine is in book */
9945             int depth = pvInfoList[index].depth;
9946             int score = pvInfoList[index].score;
9947             int in_book = 0;
9948
9949             if( depth <= 2 ) {
9950                 in_book = 1;
9951             }
9952             else if( score == 0 && depth == 63 ) {
9953                 in_book = 1; /* Zappa */
9954             }
9955             else if( score == 2 && depth == 99 ) {
9956                 in_book = 1; /* Abrok */
9957             }
9958
9959             has_book_hit += in_book;
9960
9961             if( ! in_book ) {
9962                 result = index;
9963
9964                 break;
9965             }
9966
9967             index += 2;
9968         }
9969     }
9970
9971     return result;
9972 }
9973
9974 /* [AS] */
9975 void GetOutOfBookInfo( char * buf )
9976 {
9977     int oob[2];
9978     int i;
9979     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9980
9981     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9982     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9983
9984     *buf = '\0';
9985
9986     if( oob[0] >= 0 || oob[1] >= 0 ) {
9987         for( i=0; i<2; i++ ) {
9988             int idx = oob[i];
9989
9990             if( idx >= 0 ) {
9991                 if( i > 0 && oob[0] >= 0 ) {
9992                     strcat( buf, "   " );
9993                 }
9994
9995                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9996                 sprintf( buf+strlen(buf), "%s%.2f", 
9997                     pvInfoList[idx].score >= 0 ? "+" : "",
9998                     pvInfoList[idx].score / 100.0 );
9999             }
10000         }
10001     }
10002 }
10003
10004 /* Save game in PGN style and close the file */
10005 int
10006 SaveGamePGN(f)
10007      FILE *f;
10008 {
10009     int i, offset, linelen, newblock;
10010     time_t tm;
10011 //    char *movetext;
10012     char numtext[32];
10013     int movelen, numlen, blank;
10014     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10015
10016     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10017     
10018     tm = time((time_t *) NULL);
10019     
10020     PrintPGNTags(f, &gameInfo);
10021     
10022     if (backwardMostMove > 0 || startedFromSetupPosition) {
10023         char *fen = PositionToFEN(backwardMostMove, NULL);
10024         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10025         fprintf(f, "\n{--------------\n");
10026         PrintPosition(f, backwardMostMove);
10027         fprintf(f, "--------------}\n");
10028         free(fen);
10029     }
10030     else {
10031         /* [AS] Out of book annotation */
10032         if( appData.saveOutOfBookInfo ) {
10033             char buf[64];
10034
10035             GetOutOfBookInfo( buf );
10036
10037             if( buf[0] != '\0' ) {
10038                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10039             }
10040         }
10041
10042         fprintf(f, "\n");
10043     }
10044
10045     i = backwardMostMove;
10046     linelen = 0;
10047     newblock = TRUE;
10048
10049     while (i < forwardMostMove) {
10050         /* Print comments preceding this move */
10051         if (commentList[i] != NULL) {
10052             if (linelen > 0) fprintf(f, "\n");
10053             fprintf(f, "%s", commentList[i]);
10054             linelen = 0;
10055             newblock = TRUE;
10056         }
10057
10058         /* Format move number */
10059         if ((i % 2) == 0) {
10060             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10061         } else {
10062             if (newblock) {
10063                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10064             } else {
10065                 numtext[0] = NULLCHAR;
10066             }
10067         }
10068         numlen = strlen(numtext);
10069         newblock = FALSE;
10070
10071         /* Print move number */
10072         blank = linelen > 0 && numlen > 0;
10073         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10074             fprintf(f, "\n");
10075             linelen = 0;
10076             blank = 0;
10077         }
10078         if (blank) {
10079             fprintf(f, " ");
10080             linelen++;
10081         }
10082         fprintf(f, "%s", numtext);
10083         linelen += numlen;
10084
10085         /* Get move */
10086         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10087         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10088
10089         /* Print move */
10090         blank = linelen > 0 && movelen > 0;
10091         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10092             fprintf(f, "\n");
10093             linelen = 0;
10094             blank = 0;
10095         }
10096         if (blank) {
10097             fprintf(f, " ");
10098             linelen++;
10099         }
10100         fprintf(f, "%s", move_buffer);
10101         linelen += movelen;
10102
10103         /* [AS] Add PV info if present */
10104         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10105             /* [HGM] add time */
10106             char buf[MSG_SIZ]; int seconds;
10107
10108             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10109
10110             if( seconds <= 0) buf[0] = 0; else
10111             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10112                 seconds = (seconds + 4)/10; // round to full seconds
10113                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10114                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10115             }
10116
10117             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10118                 pvInfoList[i].score >= 0 ? "+" : "",
10119                 pvInfoList[i].score / 100.0,
10120                 pvInfoList[i].depth,
10121                 buf );
10122
10123             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10124
10125             /* Print score/depth */
10126             blank = linelen > 0 && movelen > 0;
10127             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10128                 fprintf(f, "\n");
10129                 linelen = 0;
10130                 blank = 0;
10131             }
10132             if (blank) {
10133                 fprintf(f, " ");
10134                 linelen++;
10135             }
10136             fprintf(f, "%s", move_buffer);
10137             linelen += movelen;
10138         }
10139
10140         i++;
10141     }
10142     
10143     /* Start a new line */
10144     if (linelen > 0) fprintf(f, "\n");
10145
10146     /* Print comments after last move */
10147     if (commentList[i] != NULL) {
10148         fprintf(f, "%s\n", commentList[i]);
10149     }
10150
10151     /* Print result */
10152     if (gameInfo.resultDetails != NULL &&
10153         gameInfo.resultDetails[0] != NULLCHAR) {
10154         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10155                 PGNResult(gameInfo.result));
10156     } else {
10157         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10158     }
10159
10160     fclose(f);
10161     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10162     return TRUE;
10163 }
10164
10165 /* Save game in old style and close the file */
10166 int
10167 SaveGameOldStyle(f)
10168      FILE *f;
10169 {
10170     int i, offset;
10171     time_t tm;
10172     
10173     tm = time((time_t *) NULL);
10174     
10175     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10176     PrintOpponents(f);
10177     
10178     if (backwardMostMove > 0 || startedFromSetupPosition) {
10179         fprintf(f, "\n[--------------\n");
10180         PrintPosition(f, backwardMostMove);
10181         fprintf(f, "--------------]\n");
10182     } else {
10183         fprintf(f, "\n");
10184     }
10185
10186     i = backwardMostMove;
10187     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10188
10189     while (i < forwardMostMove) {
10190         if (commentList[i] != NULL) {
10191             fprintf(f, "[%s]\n", commentList[i]);
10192         }
10193
10194         if ((i % 2) == 1) {
10195             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10196             i++;
10197         } else {
10198             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10199             i++;
10200             if (commentList[i] != NULL) {
10201                 fprintf(f, "\n");
10202                 continue;
10203             }
10204             if (i >= forwardMostMove) {
10205                 fprintf(f, "\n");
10206                 break;
10207             }
10208             fprintf(f, "%s\n", parseList[i]);
10209             i++;
10210         }
10211     }
10212     
10213     if (commentList[i] != NULL) {
10214         fprintf(f, "[%s]\n", commentList[i]);
10215     }
10216
10217     /* This isn't really the old style, but it's close enough */
10218     if (gameInfo.resultDetails != NULL &&
10219         gameInfo.resultDetails[0] != NULLCHAR) {
10220         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10221                 gameInfo.resultDetails);
10222     } else {
10223         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10224     }
10225
10226     fclose(f);
10227     return TRUE;
10228 }
10229
10230 /* Save the current game to open file f and close the file */
10231 int
10232 SaveGame(f, dummy, dummy2)
10233      FILE *f;
10234      int dummy;
10235      char *dummy2;
10236 {
10237     if (gameMode == EditPosition) EditPositionDone(TRUE);
10238     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10239     if (appData.oldSaveStyle)
10240       return SaveGameOldStyle(f);
10241     else
10242       return SaveGamePGN(f);
10243 }
10244
10245 /* Save the current position to the given file */
10246 int
10247 SavePositionToFile(filename)
10248      char *filename;
10249 {
10250     FILE *f;
10251     char buf[MSG_SIZ];
10252
10253     if (strcmp(filename, "-") == 0) {
10254         return SavePosition(stdout, 0, NULL);
10255     } else {
10256         f = fopen(filename, "a");
10257         if (f == NULL) {
10258             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10259             DisplayError(buf, errno);
10260             return FALSE;
10261         } else {
10262             SavePosition(f, 0, NULL);
10263             return TRUE;
10264         }
10265     }
10266 }
10267
10268 /* Save the current position to the given open file and close the file */
10269 int
10270 SavePosition(f, dummy, dummy2)
10271      FILE *f;
10272      int dummy;
10273      char *dummy2;
10274 {
10275     time_t tm;
10276     char *fen;
10277     
10278     if (gameMode == EditPosition) EditPositionDone(TRUE);
10279     if (appData.oldSaveStyle) {
10280         tm = time((time_t *) NULL);
10281     
10282         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10283         PrintOpponents(f);
10284         fprintf(f, "[--------------\n");
10285         PrintPosition(f, currentMove);
10286         fprintf(f, "--------------]\n");
10287     } else {
10288         fen = PositionToFEN(currentMove, NULL);
10289         fprintf(f, "%s\n", fen);
10290         free(fen);
10291     }
10292     fclose(f);
10293     return TRUE;
10294 }
10295
10296 void
10297 ReloadCmailMsgEvent(unregister)
10298      int unregister;
10299 {
10300 #if !WIN32
10301     static char *inFilename = NULL;
10302     static char *outFilename;
10303     int i;
10304     struct stat inbuf, outbuf;
10305     int status;
10306     
10307     /* Any registered moves are unregistered if unregister is set, */
10308     /* i.e. invoked by the signal handler */
10309     if (unregister) {
10310         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10311             cmailMoveRegistered[i] = FALSE;
10312             if (cmailCommentList[i] != NULL) {
10313                 free(cmailCommentList[i]);
10314                 cmailCommentList[i] = NULL;
10315             }
10316         }
10317         nCmailMovesRegistered = 0;
10318     }
10319
10320     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10321         cmailResult[i] = CMAIL_NOT_RESULT;
10322     }
10323     nCmailResults = 0;
10324
10325     if (inFilename == NULL) {
10326         /* Because the filenames are static they only get malloced once  */
10327         /* and they never get freed                                      */
10328         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10329         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10330
10331         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10332         sprintf(outFilename, "%s.out", appData.cmailGameName);
10333     }
10334     
10335     status = stat(outFilename, &outbuf);
10336     if (status < 0) {
10337         cmailMailedMove = FALSE;
10338     } else {
10339         status = stat(inFilename, &inbuf);
10340         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10341     }
10342     
10343     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10344        counts the games, notes how each one terminated, etc.
10345        
10346        It would be nice to remove this kludge and instead gather all
10347        the information while building the game list.  (And to keep it
10348        in the game list nodes instead of having a bunch of fixed-size
10349        parallel arrays.)  Note this will require getting each game's
10350        termination from the PGN tags, as the game list builder does
10351        not process the game moves.  --mann
10352        */
10353     cmailMsgLoaded = TRUE;
10354     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10355     
10356     /* Load first game in the file or popup game menu */
10357     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10358
10359 #endif /* !WIN32 */
10360     return;
10361 }
10362
10363 int
10364 RegisterMove()
10365 {
10366     FILE *f;
10367     char string[MSG_SIZ];
10368
10369     if (   cmailMailedMove
10370         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10371         return TRUE;            /* Allow free viewing  */
10372     }
10373
10374     /* Unregister move to ensure that we don't leave RegisterMove        */
10375     /* with the move registered when the conditions for registering no   */
10376     /* longer hold                                                       */
10377     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10378         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10379         nCmailMovesRegistered --;
10380
10381         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10382           {
10383               free(cmailCommentList[lastLoadGameNumber - 1]);
10384               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10385           }
10386     }
10387
10388     if (cmailOldMove == -1) {
10389         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10390         return FALSE;
10391     }
10392
10393     if (currentMove > cmailOldMove + 1) {
10394         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10395         return FALSE;
10396     }
10397
10398     if (currentMove < cmailOldMove) {
10399         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10400         return FALSE;
10401     }
10402
10403     if (forwardMostMove > currentMove) {
10404         /* Silently truncate extra moves */
10405         TruncateGame();
10406     }
10407
10408     if (   (currentMove == cmailOldMove + 1)
10409         || (   (currentMove == cmailOldMove)
10410             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10411                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10412         if (gameInfo.result != GameUnfinished) {
10413             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10414         }
10415
10416         if (commentList[currentMove] != NULL) {
10417             cmailCommentList[lastLoadGameNumber - 1]
10418               = StrSave(commentList[currentMove]);
10419         }
10420         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10421
10422         if (appData.debugMode)
10423           fprintf(debugFP, "Saving %s for game %d\n",
10424                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10425
10426         sprintf(string,
10427                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10428         
10429         f = fopen(string, "w");
10430         if (appData.oldSaveStyle) {
10431             SaveGameOldStyle(f); /* also closes the file */
10432             
10433             sprintf(string, "%s.pos.out", appData.cmailGameName);
10434             f = fopen(string, "w");
10435             SavePosition(f, 0, NULL); /* also closes the file */
10436         } else {
10437             fprintf(f, "{--------------\n");
10438             PrintPosition(f, currentMove);
10439             fprintf(f, "--------------}\n\n");
10440             
10441             SaveGame(f, 0, NULL); /* also closes the file*/
10442         }
10443         
10444         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10445         nCmailMovesRegistered ++;
10446     } else if (nCmailGames == 1) {
10447         DisplayError(_("You have not made a move yet"), 0);
10448         return FALSE;
10449     }
10450
10451     return TRUE;
10452 }
10453
10454 void
10455 MailMoveEvent()
10456 {
10457 #if !WIN32
10458     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10459     FILE *commandOutput;
10460     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10461     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10462     int nBuffers;
10463     int i;
10464     int archived;
10465     char *arcDir;
10466
10467     if (! cmailMsgLoaded) {
10468         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10469         return;
10470     }
10471
10472     if (nCmailGames == nCmailResults) {
10473         DisplayError(_("No unfinished games"), 0);
10474         return;
10475     }
10476
10477 #if CMAIL_PROHIBIT_REMAIL
10478     if (cmailMailedMove) {
10479         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);
10480         DisplayError(msg, 0);
10481         return;
10482     }
10483 #endif
10484
10485     if (! (cmailMailedMove || RegisterMove())) return;
10486     
10487     if (   cmailMailedMove
10488         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10489         sprintf(string, partCommandString,
10490                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10491         commandOutput = popen(string, "r");
10492
10493         if (commandOutput == NULL) {
10494             DisplayError(_("Failed to invoke cmail"), 0);
10495         } else {
10496             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10497                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10498             }
10499             if (nBuffers > 1) {
10500                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10501                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10502                 nBytes = MSG_SIZ - 1;
10503             } else {
10504                 (void) memcpy(msg, buffer, nBytes);
10505             }
10506             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10507
10508             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10509                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10510
10511                 archived = TRUE;
10512                 for (i = 0; i < nCmailGames; i ++) {
10513                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10514                         archived = FALSE;
10515                     }
10516                 }
10517                 if (   archived
10518                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10519                         != NULL)) {
10520                     sprintf(buffer, "%s/%s.%s.archive",
10521                             arcDir,
10522                             appData.cmailGameName,
10523                             gameInfo.date);
10524                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10525                     cmailMsgLoaded = FALSE;
10526                 }
10527             }
10528
10529             DisplayInformation(msg);
10530             pclose(commandOutput);
10531         }
10532     } else {
10533         if ((*cmailMsg) != '\0') {
10534             DisplayInformation(cmailMsg);
10535         }
10536     }
10537
10538     return;
10539 #endif /* !WIN32 */
10540 }
10541
10542 char *
10543 CmailMsg()
10544 {
10545 #if WIN32
10546     return NULL;
10547 #else
10548     int  prependComma = 0;
10549     char number[5];
10550     char string[MSG_SIZ];       /* Space for game-list */
10551     int  i;
10552     
10553     if (!cmailMsgLoaded) return "";
10554
10555     if (cmailMailedMove) {
10556         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10557     } else {
10558         /* Create a list of games left */
10559         sprintf(string, "[");
10560         for (i = 0; i < nCmailGames; i ++) {
10561             if (! (   cmailMoveRegistered[i]
10562                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10563                 if (prependComma) {
10564                     sprintf(number, ",%d", i + 1);
10565                 } else {
10566                     sprintf(number, "%d", i + 1);
10567                     prependComma = 1;
10568                 }
10569                 
10570                 strcat(string, number);
10571             }
10572         }
10573         strcat(string, "]");
10574
10575         if (nCmailMovesRegistered + nCmailResults == 0) {
10576             switch (nCmailGames) {
10577               case 1:
10578                 sprintf(cmailMsg,
10579                         _("Still need to make move for game\n"));
10580                 break;
10581                 
10582               case 2:
10583                 sprintf(cmailMsg,
10584                         _("Still need to make moves for both games\n"));
10585                 break;
10586                 
10587               default:
10588                 sprintf(cmailMsg,
10589                         _("Still need to make moves for all %d games\n"),
10590                         nCmailGames);
10591                 break;
10592             }
10593         } else {
10594             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10595               case 1:
10596                 sprintf(cmailMsg,
10597                         _("Still need to make a move for game %s\n"),
10598                         string);
10599                 break;
10600                 
10601               case 0:
10602                 if (nCmailResults == nCmailGames) {
10603                     sprintf(cmailMsg, _("No unfinished games\n"));
10604                 } else {
10605                     sprintf(cmailMsg, _("Ready to send mail\n"));
10606                 }
10607                 break;
10608                 
10609               default:
10610                 sprintf(cmailMsg,
10611                         _("Still need to make moves for games %s\n"),
10612                         string);
10613             }
10614         }
10615     }
10616     return cmailMsg;
10617 #endif /* WIN32 */
10618 }
10619
10620 void
10621 ResetGameEvent()
10622 {
10623     if (gameMode == Training)
10624       SetTrainingModeOff();
10625
10626     Reset(TRUE, TRUE);
10627     cmailMsgLoaded = FALSE;
10628     if (appData.icsActive) {
10629       SendToICS(ics_prefix);
10630       SendToICS("refresh\n");
10631     }
10632 }
10633
10634 void
10635 ExitEvent(status)
10636      int status;
10637 {
10638     exiting++;
10639     if (exiting > 2) {
10640       /* Give up on clean exit */
10641       exit(status);
10642     }
10643     if (exiting > 1) {
10644       /* Keep trying for clean exit */
10645       return;
10646     }
10647
10648     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10649
10650     if (telnetISR != NULL) {
10651       RemoveInputSource(telnetISR);
10652     }
10653     if (icsPR != NoProc) {
10654       DestroyChildProcess(icsPR, TRUE);
10655     }
10656
10657     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10658     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10659
10660     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10661     /* make sure this other one finishes before killing it!                  */
10662     if(endingGame) { int count = 0;
10663         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10664         while(endingGame && count++ < 10) DoSleep(1);
10665         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10666     }
10667
10668     /* Kill off chess programs */
10669     if (first.pr != NoProc) {
10670         ExitAnalyzeMode();
10671         
10672         DoSleep( appData.delayBeforeQuit );
10673         SendToProgram("quit\n", &first);
10674         DoSleep( appData.delayAfterQuit );
10675         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10676     }
10677     if (second.pr != NoProc) {
10678         DoSleep( appData.delayBeforeQuit );
10679         SendToProgram("quit\n", &second);
10680         DoSleep( appData.delayAfterQuit );
10681         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10682     }
10683     if (first.isr != NULL) {
10684         RemoveInputSource(first.isr);
10685     }
10686     if (second.isr != NULL) {
10687         RemoveInputSource(second.isr);
10688     }
10689
10690     ShutDownFrontEnd();
10691     exit(status);
10692 }
10693
10694 void
10695 PauseEvent()
10696 {
10697     if (appData.debugMode)
10698         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10699     if (pausing) {
10700         pausing = FALSE;
10701         ModeHighlight();
10702         if (gameMode == MachinePlaysWhite ||
10703             gameMode == MachinePlaysBlack) {
10704             StartClocks();
10705         } else {
10706             DisplayBothClocks();
10707         }
10708         if (gameMode == PlayFromGameFile) {
10709             if (appData.timeDelay >= 0) 
10710                 AutoPlayGameLoop();
10711         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10712             Reset(FALSE, TRUE);
10713             SendToICS(ics_prefix);
10714             SendToICS("refresh\n");
10715         } else if (currentMove < forwardMostMove) {
10716             ForwardInner(forwardMostMove);
10717         }
10718         pauseExamInvalid = FALSE;
10719     } else {
10720         switch (gameMode) {
10721           default:
10722             return;
10723           case IcsExamining:
10724             pauseExamForwardMostMove = forwardMostMove;
10725             pauseExamInvalid = FALSE;
10726             /* fall through */
10727           case IcsObserving:
10728           case IcsPlayingWhite:
10729           case IcsPlayingBlack:
10730             pausing = TRUE;
10731             ModeHighlight();
10732             return;
10733           case PlayFromGameFile:
10734             (void) StopLoadGameTimer();
10735             pausing = TRUE;
10736             ModeHighlight();
10737             break;
10738           case BeginningOfGame:
10739             if (appData.icsActive) return;
10740             /* else fall through */
10741           case MachinePlaysWhite:
10742           case MachinePlaysBlack:
10743           case TwoMachinesPlay:
10744             if (forwardMostMove == 0)
10745               return;           /* don't pause if no one has moved */
10746             if ((gameMode == MachinePlaysWhite &&
10747                  !WhiteOnMove(forwardMostMove)) ||
10748                 (gameMode == MachinePlaysBlack &&
10749                  WhiteOnMove(forwardMostMove))) {
10750                 StopClocks();
10751             }
10752             pausing = TRUE;
10753             ModeHighlight();
10754             break;
10755         }
10756     }
10757 }
10758
10759 void
10760 EditCommentEvent()
10761 {
10762     char title[MSG_SIZ];
10763
10764     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10765         strcpy(title, _("Edit comment"));
10766     } else {
10767         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10768                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10769                 parseList[currentMove - 1]);
10770     }
10771
10772     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10773 }
10774
10775
10776 void
10777 EditTagsEvent()
10778 {
10779     char *tags = PGNTags(&gameInfo);
10780     EditTagsPopUp(tags);
10781     free(tags);
10782 }
10783
10784 void
10785 AnalyzeModeEvent()
10786 {
10787     if (appData.noChessProgram || gameMode == AnalyzeMode)
10788       return;
10789
10790     if (gameMode != AnalyzeFile) {
10791         if (!appData.icsEngineAnalyze) {
10792                EditGameEvent();
10793                if (gameMode != EditGame) return;
10794         }
10795         ResurrectChessProgram();
10796         SendToProgram("analyze\n", &first);
10797         first.analyzing = TRUE;
10798         /*first.maybeThinking = TRUE;*/
10799         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10800         EngineOutputPopUp();
10801     }
10802     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10803     pausing = FALSE;
10804     ModeHighlight();
10805     SetGameInfo();
10806
10807     StartAnalysisClock();
10808     GetTimeMark(&lastNodeCountTime);
10809     lastNodeCount = 0;
10810 }
10811
10812 void
10813 AnalyzeFileEvent()
10814 {
10815     if (appData.noChessProgram || gameMode == AnalyzeFile)
10816       return;
10817
10818     if (gameMode != AnalyzeMode) {
10819         EditGameEvent();
10820         if (gameMode != EditGame) return;
10821         ResurrectChessProgram();
10822         SendToProgram("analyze\n", &first);
10823         first.analyzing = TRUE;
10824         /*first.maybeThinking = TRUE;*/
10825         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10826         EngineOutputPopUp();
10827     }
10828     gameMode = AnalyzeFile;
10829     pausing = FALSE;
10830     ModeHighlight();
10831     SetGameInfo();
10832
10833     StartAnalysisClock();
10834     GetTimeMark(&lastNodeCountTime);
10835     lastNodeCount = 0;
10836 }
10837
10838 void
10839 MachineWhiteEvent()
10840 {
10841     char buf[MSG_SIZ];
10842     char *bookHit = NULL;
10843
10844     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10845       return;
10846
10847
10848     if (gameMode == PlayFromGameFile || 
10849         gameMode == TwoMachinesPlay  || 
10850         gameMode == Training         || 
10851         gameMode == AnalyzeMode      || 
10852         gameMode == EndOfGame)
10853         EditGameEvent();
10854
10855     if (gameMode == EditPosition) 
10856         EditPositionDone(TRUE);
10857
10858     if (!WhiteOnMove(currentMove)) {
10859         DisplayError(_("It is not White's turn"), 0);
10860         return;
10861     }
10862   
10863     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10864       ExitAnalyzeMode();
10865
10866     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10867         gameMode == AnalyzeFile)
10868         TruncateGame();
10869
10870     ResurrectChessProgram();    /* in case it isn't running */
10871     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10872         gameMode = MachinePlaysWhite;
10873         ResetClocks();
10874     } else
10875     gameMode = MachinePlaysWhite;
10876     pausing = FALSE;
10877     ModeHighlight();
10878     SetGameInfo();
10879     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10880     DisplayTitle(buf);
10881     if (first.sendName) {
10882       sprintf(buf, "name %s\n", gameInfo.black);
10883       SendToProgram(buf, &first);
10884     }
10885     if (first.sendTime) {
10886       if (first.useColors) {
10887         SendToProgram("black\n", &first); /*gnu kludge*/
10888       }
10889       SendTimeRemaining(&first, TRUE);
10890     }
10891     if (first.useColors) {
10892       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10893     }
10894     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10895     SetMachineThinkingEnables();
10896     first.maybeThinking = TRUE;
10897     StartClocks();
10898     firstMove = FALSE;
10899
10900     if (appData.autoFlipView && !flipView) {
10901       flipView = !flipView;
10902       DrawPosition(FALSE, NULL);
10903       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10904     }
10905
10906     if(bookHit) { // [HGM] book: simulate book reply
10907         static char bookMove[MSG_SIZ]; // a bit generous?
10908
10909         programStats.nodes = programStats.depth = programStats.time = 
10910         programStats.score = programStats.got_only_move = 0;
10911         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10912
10913         strcpy(bookMove, "move ");
10914         strcat(bookMove, bookHit);
10915         HandleMachineMove(bookMove, &first);
10916     }
10917 }
10918
10919 void
10920 MachineBlackEvent()
10921 {
10922     char buf[MSG_SIZ];
10923    char *bookHit = NULL;
10924
10925     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10926         return;
10927
10928
10929     if (gameMode == PlayFromGameFile || 
10930         gameMode == TwoMachinesPlay  || 
10931         gameMode == Training         || 
10932         gameMode == AnalyzeMode      || 
10933         gameMode == EndOfGame)
10934         EditGameEvent();
10935
10936     if (gameMode == EditPosition) 
10937         EditPositionDone(TRUE);
10938
10939     if (WhiteOnMove(currentMove)) {
10940         DisplayError(_("It is not Black's turn"), 0);
10941         return;
10942     }
10943     
10944     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10945       ExitAnalyzeMode();
10946
10947     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10948         gameMode == AnalyzeFile)
10949         TruncateGame();
10950
10951     ResurrectChessProgram();    /* in case it isn't running */
10952     gameMode = MachinePlaysBlack;
10953     pausing = FALSE;
10954     ModeHighlight();
10955     SetGameInfo();
10956     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10957     DisplayTitle(buf);
10958     if (first.sendName) {
10959       sprintf(buf, "name %s\n", gameInfo.white);
10960       SendToProgram(buf, &first);
10961     }
10962     if (first.sendTime) {
10963       if (first.useColors) {
10964         SendToProgram("white\n", &first); /*gnu kludge*/
10965       }
10966       SendTimeRemaining(&first, FALSE);
10967     }
10968     if (first.useColors) {
10969       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10970     }
10971     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10972     SetMachineThinkingEnables();
10973     first.maybeThinking = TRUE;
10974     StartClocks();
10975
10976     if (appData.autoFlipView && flipView) {
10977       flipView = !flipView;
10978       DrawPosition(FALSE, NULL);
10979       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10980     }
10981     if(bookHit) { // [HGM] book: simulate book reply
10982         static char bookMove[MSG_SIZ]; // a bit generous?
10983
10984         programStats.nodes = programStats.depth = programStats.time = 
10985         programStats.score = programStats.got_only_move = 0;
10986         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10987
10988         strcpy(bookMove, "move ");
10989         strcat(bookMove, bookHit);
10990         HandleMachineMove(bookMove, &first);
10991     }
10992 }
10993
10994
10995 void
10996 DisplayTwoMachinesTitle()
10997 {
10998     char buf[MSG_SIZ];
10999     if (appData.matchGames > 0) {
11000         if (first.twoMachinesColor[0] == 'w') {
11001             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11002                     gameInfo.white, gameInfo.black,
11003                     first.matchWins, second.matchWins,
11004                     matchGame - 1 - (first.matchWins + second.matchWins));
11005         } else {
11006             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11007                     gameInfo.white, gameInfo.black,
11008                     second.matchWins, first.matchWins,
11009                     matchGame - 1 - (first.matchWins + second.matchWins));
11010         }
11011     } else {
11012         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11013     }
11014     DisplayTitle(buf);
11015 }
11016
11017 void
11018 TwoMachinesEvent P((void))
11019 {
11020     int i;
11021     char buf[MSG_SIZ];
11022     ChessProgramState *onmove;
11023     char *bookHit = NULL;
11024     
11025     if (appData.noChessProgram) return;
11026
11027     switch (gameMode) {
11028       case TwoMachinesPlay:
11029         return;
11030       case MachinePlaysWhite:
11031       case MachinePlaysBlack:
11032         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11033             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11034             return;
11035         }
11036         /* fall through */
11037       case BeginningOfGame:
11038       case PlayFromGameFile:
11039       case EndOfGame:
11040         EditGameEvent();
11041         if (gameMode != EditGame) return;
11042         break;
11043       case EditPosition:
11044         EditPositionDone(TRUE);
11045         break;
11046       case AnalyzeMode:
11047       case AnalyzeFile:
11048         ExitAnalyzeMode();
11049         break;
11050       case EditGame:
11051       default:
11052         break;
11053     }
11054
11055 //    forwardMostMove = currentMove;
11056     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11057     ResurrectChessProgram();    /* in case first program isn't running */
11058
11059     if (second.pr == NULL) {
11060         StartChessProgram(&second);
11061         if (second.protocolVersion == 1) {
11062           TwoMachinesEventIfReady();
11063         } else {
11064           /* kludge: allow timeout for initial "feature" command */
11065           FreezeUI();
11066           DisplayMessage("", _("Starting second chess program"));
11067           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11068         }
11069         return;
11070     }
11071     DisplayMessage("", "");
11072     InitChessProgram(&second, FALSE);
11073     SendToProgram("force\n", &second);
11074     if (startedFromSetupPosition) {
11075         SendBoard(&second, backwardMostMove);
11076     if (appData.debugMode) {
11077         fprintf(debugFP, "Two Machines\n");
11078     }
11079     }
11080     for (i = backwardMostMove; i < forwardMostMove; i++) {
11081         SendMoveToProgram(i, &second);
11082     }
11083
11084     gameMode = TwoMachinesPlay;
11085     pausing = FALSE;
11086     ModeHighlight();
11087     SetGameInfo();
11088     DisplayTwoMachinesTitle();
11089     firstMove = TRUE;
11090     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11091         onmove = &first;
11092     } else {
11093         onmove = &second;
11094     }
11095
11096     SendToProgram(first.computerString, &first);
11097     if (first.sendName) {
11098       sprintf(buf, "name %s\n", second.tidy);
11099       SendToProgram(buf, &first);
11100     }
11101     SendToProgram(second.computerString, &second);
11102     if (second.sendName) {
11103       sprintf(buf, "name %s\n", first.tidy);
11104       SendToProgram(buf, &second);
11105     }
11106
11107     ResetClocks();
11108     if (!first.sendTime || !second.sendTime) {
11109         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11110         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11111     }
11112     if (onmove->sendTime) {
11113       if (onmove->useColors) {
11114         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11115       }
11116       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11117     }
11118     if (onmove->useColors) {
11119       SendToProgram(onmove->twoMachinesColor, onmove);
11120     }
11121     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11122 //    SendToProgram("go\n", onmove);
11123     onmove->maybeThinking = TRUE;
11124     SetMachineThinkingEnables();
11125
11126     StartClocks();
11127
11128     if(bookHit) { // [HGM] book: simulate book reply
11129         static char bookMove[MSG_SIZ]; // a bit generous?
11130
11131         programStats.nodes = programStats.depth = programStats.time = 
11132         programStats.score = programStats.got_only_move = 0;
11133         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11134
11135         strcpy(bookMove, "move ");
11136         strcat(bookMove, bookHit);
11137         savedMessage = bookMove; // args for deferred call
11138         savedState = onmove;
11139         ScheduleDelayedEvent(DeferredBookMove, 1);
11140     }
11141 }
11142
11143 void
11144 TrainingEvent()
11145 {
11146     if (gameMode == Training) {
11147       SetTrainingModeOff();
11148       gameMode = PlayFromGameFile;
11149       DisplayMessage("", _("Training mode off"));
11150     } else {
11151       gameMode = Training;
11152       animateTraining = appData.animate;
11153
11154       /* make sure we are not already at the end of the game */
11155       if (currentMove < forwardMostMove) {
11156         SetTrainingModeOn();
11157         DisplayMessage("", _("Training mode on"));
11158       } else {
11159         gameMode = PlayFromGameFile;
11160         DisplayError(_("Already at end of game"), 0);
11161       }
11162     }
11163     ModeHighlight();
11164 }
11165
11166 void
11167 IcsClientEvent()
11168 {
11169     if (!appData.icsActive) return;
11170     switch (gameMode) {
11171       case IcsPlayingWhite:
11172       case IcsPlayingBlack:
11173       case IcsObserving:
11174       case IcsIdle:
11175       case BeginningOfGame:
11176       case IcsExamining:
11177         return;
11178
11179       case EditGame:
11180         break;
11181
11182       case EditPosition:
11183         EditPositionDone(TRUE);
11184         break;
11185
11186       case AnalyzeMode:
11187       case AnalyzeFile:
11188         ExitAnalyzeMode();
11189         break;
11190         
11191       default:
11192         EditGameEvent();
11193         break;
11194     }
11195
11196     gameMode = IcsIdle;
11197     ModeHighlight();
11198     return;
11199 }
11200
11201
11202 void
11203 EditGameEvent()
11204 {
11205     int i;
11206
11207     switch (gameMode) {
11208       case Training:
11209         SetTrainingModeOff();
11210         break;
11211       case MachinePlaysWhite:
11212       case MachinePlaysBlack:
11213       case BeginningOfGame:
11214         SendToProgram("force\n", &first);
11215         SetUserThinkingEnables();
11216         break;
11217       case PlayFromGameFile:
11218         (void) StopLoadGameTimer();
11219         if (gameFileFP != NULL) {
11220             gameFileFP = NULL;
11221         }
11222         break;
11223       case EditPosition:
11224         EditPositionDone(TRUE);
11225         break;
11226       case AnalyzeMode:
11227       case AnalyzeFile:
11228         ExitAnalyzeMode();
11229         SendToProgram("force\n", &first);
11230         break;
11231       case TwoMachinesPlay:
11232         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11233         ResurrectChessProgram();
11234         SetUserThinkingEnables();
11235         break;
11236       case EndOfGame:
11237         ResurrectChessProgram();
11238         break;
11239       case IcsPlayingBlack:
11240       case IcsPlayingWhite:
11241         DisplayError(_("Warning: You are still playing a game"), 0);
11242         break;
11243       case IcsObserving:
11244         DisplayError(_("Warning: You are still observing a game"), 0);
11245         break;
11246       case IcsExamining:
11247         DisplayError(_("Warning: You are still examining a game"), 0);
11248         break;
11249       case IcsIdle:
11250         break;
11251       case EditGame:
11252       default:
11253         return;
11254     }
11255     
11256     pausing = FALSE;
11257     StopClocks();
11258     first.offeredDraw = second.offeredDraw = 0;
11259
11260     if (gameMode == PlayFromGameFile) {
11261         whiteTimeRemaining = timeRemaining[0][currentMove];
11262         blackTimeRemaining = timeRemaining[1][currentMove];
11263         DisplayTitle("");
11264     }
11265
11266     if (gameMode == MachinePlaysWhite ||
11267         gameMode == MachinePlaysBlack ||
11268         gameMode == TwoMachinesPlay ||
11269         gameMode == EndOfGame) {
11270         i = forwardMostMove;
11271         while (i > currentMove) {
11272             SendToProgram("undo\n", &first);
11273             i--;
11274         }
11275         whiteTimeRemaining = timeRemaining[0][currentMove];
11276         blackTimeRemaining = timeRemaining[1][currentMove];
11277         DisplayBothClocks();
11278         if (whiteFlag || blackFlag) {
11279             whiteFlag = blackFlag = 0;
11280         }
11281         DisplayTitle("");
11282     }           
11283     
11284     gameMode = EditGame;
11285     ModeHighlight();
11286     SetGameInfo();
11287 }
11288
11289
11290 void
11291 EditPositionEvent()
11292 {
11293     if (gameMode == EditPosition) {
11294         EditGameEvent();
11295         return;
11296     }
11297     
11298     EditGameEvent();
11299     if (gameMode != EditGame) return;
11300     
11301     gameMode = EditPosition;
11302     ModeHighlight();
11303     SetGameInfo();
11304     if (currentMove > 0)
11305       CopyBoard(boards[0], boards[currentMove]);
11306     
11307     blackPlaysFirst = !WhiteOnMove(currentMove);
11308     ResetClocks();
11309     currentMove = forwardMostMove = backwardMostMove = 0;
11310     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11311     DisplayMove(-1);
11312 }
11313
11314 void
11315 ExitAnalyzeMode()
11316 {
11317     /* [DM] icsEngineAnalyze - possible call from other functions */
11318     if (appData.icsEngineAnalyze) {
11319         appData.icsEngineAnalyze = FALSE;
11320
11321         DisplayMessage("",_("Close ICS engine analyze..."));
11322     }
11323     if (first.analysisSupport && first.analyzing) {
11324       SendToProgram("exit\n", &first);
11325       first.analyzing = FALSE;
11326     }
11327     thinkOutput[0] = NULLCHAR;
11328 }
11329
11330 void
11331 EditPositionDone(Boolean fakeRights)
11332 {
11333     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11334
11335     startedFromSetupPosition = TRUE;
11336     InitChessProgram(&first, FALSE);
11337     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11338       boards[0][EP_STATUS] = EP_NONE;
11339       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11340     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11341         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11342         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11343       } else boards[0][CASTLING][2] = NoRights;
11344     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11345         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11346         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11347       } else boards[0][CASTLING][5] = NoRights;
11348     }
11349     SendToProgram("force\n", &first);
11350     if (blackPlaysFirst) {
11351         strcpy(moveList[0], "");
11352         strcpy(parseList[0], "");
11353         currentMove = forwardMostMove = backwardMostMove = 1;
11354         CopyBoard(boards[1], boards[0]);
11355     } else {
11356         currentMove = forwardMostMove = backwardMostMove = 0;
11357     }
11358     SendBoard(&first, forwardMostMove);
11359     if (appData.debugMode) {
11360         fprintf(debugFP, "EditPosDone\n");
11361     }
11362     DisplayTitle("");
11363     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11364     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11365     gameMode = EditGame;
11366     ModeHighlight();
11367     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11368     ClearHighlights(); /* [AS] */
11369 }
11370
11371 /* Pause for `ms' milliseconds */
11372 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11373 void
11374 TimeDelay(ms)
11375      long ms;
11376 {
11377     TimeMark m1, m2;
11378
11379     GetTimeMark(&m1);
11380     do {
11381         GetTimeMark(&m2);
11382     } while (SubtractTimeMarks(&m2, &m1) < ms);
11383 }
11384
11385 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11386 void
11387 SendMultiLineToICS(buf)
11388      char *buf;
11389 {
11390     char temp[MSG_SIZ+1], *p;
11391     int len;
11392
11393     len = strlen(buf);
11394     if (len > MSG_SIZ)
11395       len = MSG_SIZ;
11396   
11397     strncpy(temp, buf, len);
11398     temp[len] = 0;
11399
11400     p = temp;
11401     while (*p) {
11402         if (*p == '\n' || *p == '\r')
11403           *p = ' ';
11404         ++p;
11405     }
11406
11407     strcat(temp, "\n");
11408     SendToICS(temp);
11409     SendToPlayer(temp, strlen(temp));
11410 }
11411
11412 void
11413 SetWhiteToPlayEvent()
11414 {
11415     if (gameMode == EditPosition) {
11416         blackPlaysFirst = FALSE;
11417         DisplayBothClocks();    /* works because currentMove is 0 */
11418     } else if (gameMode == IcsExamining) {
11419         SendToICS(ics_prefix);
11420         SendToICS("tomove white\n");
11421     }
11422 }
11423
11424 void
11425 SetBlackToPlayEvent()
11426 {
11427     if (gameMode == EditPosition) {
11428         blackPlaysFirst = TRUE;
11429         currentMove = 1;        /* kludge */
11430         DisplayBothClocks();
11431         currentMove = 0;
11432     } else if (gameMode == IcsExamining) {
11433         SendToICS(ics_prefix);
11434         SendToICS("tomove black\n");
11435     }
11436 }
11437
11438 void
11439 EditPositionMenuEvent(selection, x, y)
11440      ChessSquare selection;
11441      int x, y;
11442 {
11443     char buf[MSG_SIZ];
11444     ChessSquare piece = boards[0][y][x];
11445
11446     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11447
11448     switch (selection) {
11449       case ClearBoard:
11450         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11451             SendToICS(ics_prefix);
11452             SendToICS("bsetup clear\n");
11453         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11454             SendToICS(ics_prefix);
11455             SendToICS("clearboard\n");
11456         } else {
11457             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11458                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11459                 for (y = 0; y < BOARD_HEIGHT; y++) {
11460                     if (gameMode == IcsExamining) {
11461                         if (boards[currentMove][y][x] != EmptySquare) {
11462                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11463                                     AAA + x, ONE + y);
11464                             SendToICS(buf);
11465                         }
11466                     } else {
11467                         boards[0][y][x] = p;
11468                     }
11469                 }
11470             }
11471         }
11472         if (gameMode == EditPosition) {
11473             DrawPosition(FALSE, boards[0]);
11474         }
11475         break;
11476
11477       case WhitePlay:
11478         SetWhiteToPlayEvent();
11479         break;
11480
11481       case BlackPlay:
11482         SetBlackToPlayEvent();
11483         break;
11484
11485       case EmptySquare:
11486         if (gameMode == IcsExamining) {
11487             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11488             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11489             SendToICS(buf);
11490         } else {
11491             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11492                 if(x == BOARD_LEFT-2) {
11493                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11494                     boards[0][y][1] = 0;
11495                 } else
11496                 if(x == BOARD_RGHT+1) {
11497                     if(y >= gameInfo.holdingsSize) break;
11498                     boards[0][y][BOARD_WIDTH-2] = 0;
11499                 } else break;
11500             }
11501             boards[0][y][x] = EmptySquare;
11502             DrawPosition(FALSE, boards[0]);
11503         }
11504         break;
11505
11506       case PromotePiece:
11507         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11508            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11509             selection = (ChessSquare) (PROMOTED piece);
11510         } else if(piece == EmptySquare) selection = WhiteSilver;
11511         else selection = (ChessSquare)((int)piece - 1);
11512         goto defaultlabel;
11513
11514       case DemotePiece:
11515         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11516            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11517             selection = (ChessSquare) (DEMOTED piece);
11518         } else if(piece == EmptySquare) selection = BlackSilver;
11519         else selection = (ChessSquare)((int)piece + 1);       
11520         goto defaultlabel;
11521
11522       case WhiteQueen:
11523       case BlackQueen:
11524         if(gameInfo.variant == VariantShatranj ||
11525            gameInfo.variant == VariantXiangqi  ||
11526            gameInfo.variant == VariantCourier    )
11527             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11528         goto defaultlabel;
11529
11530       case WhiteKing:
11531       case BlackKing:
11532         if(gameInfo.variant == VariantXiangqi)
11533             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11534         if(gameInfo.variant == VariantKnightmate)
11535             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11536       default:
11537         defaultlabel:
11538         if (gameMode == IcsExamining) {
11539             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11540             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11541                     PieceToChar(selection), AAA + x, ONE + y);
11542             SendToICS(buf);
11543         } else {
11544             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11545                 int n;
11546                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11547                     n = PieceToNumber(selection - BlackPawn);
11548                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11549                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11550                     boards[0][BOARD_HEIGHT-1-n][1]++;
11551                 } else
11552                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11553                     n = PieceToNumber(selection);
11554                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11555                     boards[0][n][BOARD_WIDTH-1] = selection;
11556                     boards[0][n][BOARD_WIDTH-2]++;
11557                 }
11558             } else
11559             boards[0][y][x] = selection;
11560             DrawPosition(TRUE, boards[0]);
11561         }
11562         break;
11563     }
11564 }
11565
11566
11567 void
11568 DropMenuEvent(selection, x, y)
11569      ChessSquare selection;
11570      int x, y;
11571 {
11572     ChessMove moveType;
11573
11574     switch (gameMode) {
11575       case IcsPlayingWhite:
11576       case MachinePlaysBlack:
11577         if (!WhiteOnMove(currentMove)) {
11578             DisplayMoveError(_("It is Black's turn"));
11579             return;
11580         }
11581         moveType = WhiteDrop;
11582         break;
11583       case IcsPlayingBlack:
11584       case MachinePlaysWhite:
11585         if (WhiteOnMove(currentMove)) {
11586             DisplayMoveError(_("It is White's turn"));
11587             return;
11588         }
11589         moveType = BlackDrop;
11590         break;
11591       case EditGame:
11592         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11593         break;
11594       default:
11595         return;
11596     }
11597
11598     if (moveType == BlackDrop && selection < BlackPawn) {
11599       selection = (ChessSquare) ((int) selection
11600                                  + (int) BlackPawn - (int) WhitePawn);
11601     }
11602     if (boards[currentMove][y][x] != EmptySquare) {
11603         DisplayMoveError(_("That square is occupied"));
11604         return;
11605     }
11606
11607     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11608 }
11609
11610 void
11611 AcceptEvent()
11612 {
11613     /* Accept a pending offer of any kind from opponent */
11614     
11615     if (appData.icsActive) {
11616         SendToICS(ics_prefix);
11617         SendToICS("accept\n");
11618     } else if (cmailMsgLoaded) {
11619         if (currentMove == cmailOldMove &&
11620             commentList[cmailOldMove] != NULL &&
11621             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11622                    "Black offers a draw" : "White offers a draw")) {
11623             TruncateGame();
11624             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11625             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11626         } else {
11627             DisplayError(_("There is no pending offer on this move"), 0);
11628             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11629         }
11630     } else {
11631         /* Not used for offers from chess program */
11632     }
11633 }
11634
11635 void
11636 DeclineEvent()
11637 {
11638     /* Decline a pending offer of any kind from opponent */
11639     
11640     if (appData.icsActive) {
11641         SendToICS(ics_prefix);
11642         SendToICS("decline\n");
11643     } else if (cmailMsgLoaded) {
11644         if (currentMove == cmailOldMove &&
11645             commentList[cmailOldMove] != NULL &&
11646             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11647                    "Black offers a draw" : "White offers a draw")) {
11648 #ifdef NOTDEF
11649             AppendComment(cmailOldMove, "Draw declined", TRUE);
11650             DisplayComment(cmailOldMove - 1, "Draw declined");
11651 #endif /*NOTDEF*/
11652         } else {
11653             DisplayError(_("There is no pending offer on this move"), 0);
11654         }
11655     } else {
11656         /* Not used for offers from chess program */
11657     }
11658 }
11659
11660 void
11661 RematchEvent()
11662 {
11663     /* Issue ICS rematch command */
11664     if (appData.icsActive) {
11665         SendToICS(ics_prefix);
11666         SendToICS("rematch\n");
11667     }
11668 }
11669
11670 void
11671 CallFlagEvent()
11672 {
11673     /* Call your opponent's flag (claim a win on time) */
11674     if (appData.icsActive) {
11675         SendToICS(ics_prefix);
11676         SendToICS("flag\n");
11677     } else {
11678         switch (gameMode) {
11679           default:
11680             return;
11681           case MachinePlaysWhite:
11682             if (whiteFlag) {
11683                 if (blackFlag)
11684                   GameEnds(GameIsDrawn, "Both players ran out of time",
11685                            GE_PLAYER);
11686                 else
11687                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11688             } else {
11689                 DisplayError(_("Your opponent is not out of time"), 0);
11690             }
11691             break;
11692           case MachinePlaysBlack:
11693             if (blackFlag) {
11694                 if (whiteFlag)
11695                   GameEnds(GameIsDrawn, "Both players ran out of time",
11696                            GE_PLAYER);
11697                 else
11698                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11699             } else {
11700                 DisplayError(_("Your opponent is not out of time"), 0);
11701             }
11702             break;
11703         }
11704     }
11705 }
11706
11707 void
11708 DrawEvent()
11709 {
11710     /* Offer draw or accept pending draw offer from opponent */
11711     
11712     if (appData.icsActive) {
11713         /* Note: tournament rules require draw offers to be
11714            made after you make your move but before you punch
11715            your clock.  Currently ICS doesn't let you do that;
11716            instead, you immediately punch your clock after making
11717            a move, but you can offer a draw at any time. */
11718         
11719         SendToICS(ics_prefix);
11720         SendToICS("draw\n");
11721     } else if (cmailMsgLoaded) {
11722         if (currentMove == cmailOldMove &&
11723             commentList[cmailOldMove] != NULL &&
11724             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11725                    "Black offers a draw" : "White offers a draw")) {
11726             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11727             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11728         } else if (currentMove == cmailOldMove + 1) {
11729             char *offer = WhiteOnMove(cmailOldMove) ?
11730               "White offers a draw" : "Black offers a draw";
11731             AppendComment(currentMove, offer, TRUE);
11732             DisplayComment(currentMove - 1, offer);
11733             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11734         } else {
11735             DisplayError(_("You must make your move before offering a draw"), 0);
11736             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11737         }
11738     } else if (first.offeredDraw) {
11739         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11740     } else {
11741         if (first.sendDrawOffers) {
11742             SendToProgram("draw\n", &first);
11743             userOfferedDraw = TRUE;
11744         }
11745     }
11746 }
11747
11748 void
11749 AdjournEvent()
11750 {
11751     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11752     
11753     if (appData.icsActive) {
11754         SendToICS(ics_prefix);
11755         SendToICS("adjourn\n");
11756     } else {
11757         /* Currently GNU Chess doesn't offer or accept Adjourns */
11758     }
11759 }
11760
11761
11762 void
11763 AbortEvent()
11764 {
11765     /* Offer Abort or accept pending Abort offer from opponent */
11766     
11767     if (appData.icsActive) {
11768         SendToICS(ics_prefix);
11769         SendToICS("abort\n");
11770     } else {
11771         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11772     }
11773 }
11774
11775 void
11776 ResignEvent()
11777 {
11778     /* Resign.  You can do this even if it's not your turn. */
11779     
11780     if (appData.icsActive) {
11781         SendToICS(ics_prefix);
11782         SendToICS("resign\n");
11783     } else {
11784         switch (gameMode) {
11785           case MachinePlaysWhite:
11786             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11787             break;
11788           case MachinePlaysBlack:
11789             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11790             break;
11791           case EditGame:
11792             if (cmailMsgLoaded) {
11793                 TruncateGame();
11794                 if (WhiteOnMove(cmailOldMove)) {
11795                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11796                 } else {
11797                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11798                 }
11799                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11800             }
11801             break;
11802           default:
11803             break;
11804         }
11805     }
11806 }
11807
11808
11809 void
11810 StopObservingEvent()
11811 {
11812     /* Stop observing current games */
11813     SendToICS(ics_prefix);
11814     SendToICS("unobserve\n");
11815 }
11816
11817 void
11818 StopExaminingEvent()
11819 {
11820     /* Stop observing current game */
11821     SendToICS(ics_prefix);
11822     SendToICS("unexamine\n");
11823 }
11824
11825 void
11826 ForwardInner(target)
11827      int target;
11828 {
11829     int limit;
11830
11831     if (appData.debugMode)
11832         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11833                 target, currentMove, forwardMostMove);
11834
11835     if (gameMode == EditPosition)
11836       return;
11837
11838     if (gameMode == PlayFromGameFile && !pausing)
11839       PauseEvent();
11840     
11841     if (gameMode == IcsExamining && pausing)
11842       limit = pauseExamForwardMostMove;
11843     else
11844       limit = forwardMostMove;
11845     
11846     if (target > limit) target = limit;
11847
11848     if (target > 0 && moveList[target - 1][0]) {
11849         int fromX, fromY, toX, toY;
11850         toX = moveList[target - 1][2] - AAA;
11851         toY = moveList[target - 1][3] - ONE;
11852         if (moveList[target - 1][1] == '@') {
11853             if (appData.highlightLastMove) {
11854                 SetHighlights(-1, -1, toX, toY);
11855             }
11856         } else {
11857             fromX = moveList[target - 1][0] - AAA;
11858             fromY = moveList[target - 1][1] - ONE;
11859             if (target == currentMove + 1) {
11860                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11861             }
11862             if (appData.highlightLastMove) {
11863                 SetHighlights(fromX, fromY, toX, toY);
11864             }
11865         }
11866     }
11867     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11868         gameMode == Training || gameMode == PlayFromGameFile || 
11869         gameMode == AnalyzeFile) {
11870         while (currentMove < target) {
11871             SendMoveToProgram(currentMove++, &first);
11872         }
11873     } else {
11874         currentMove = target;
11875     }
11876     
11877     if (gameMode == EditGame || gameMode == EndOfGame) {
11878         whiteTimeRemaining = timeRemaining[0][currentMove];
11879         blackTimeRemaining = timeRemaining[1][currentMove];
11880     }
11881     DisplayBothClocks();
11882     DisplayMove(currentMove - 1);
11883     DrawPosition(FALSE, boards[currentMove]);
11884     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11885     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11886         DisplayComment(currentMove - 1, commentList[currentMove]);
11887     }
11888 }
11889
11890
11891 void
11892 ForwardEvent()
11893 {
11894     if (gameMode == IcsExamining && !pausing) {
11895         SendToICS(ics_prefix);
11896         SendToICS("forward\n");
11897     } else {
11898         ForwardInner(currentMove + 1);
11899     }
11900 }
11901
11902 void
11903 ToEndEvent()
11904 {
11905     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11906         /* to optimze, we temporarily turn off analysis mode while we feed
11907          * the remaining moves to the engine. Otherwise we get analysis output
11908          * after each move.
11909          */ 
11910         if (first.analysisSupport) {
11911           SendToProgram("exit\nforce\n", &first);
11912           first.analyzing = FALSE;
11913         }
11914     }
11915         
11916     if (gameMode == IcsExamining && !pausing) {
11917         SendToICS(ics_prefix);
11918         SendToICS("forward 999999\n");
11919     } else {
11920         ForwardInner(forwardMostMove);
11921     }
11922
11923     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11924         /* we have fed all the moves, so reactivate analysis mode */
11925         SendToProgram("analyze\n", &first);
11926         first.analyzing = TRUE;
11927         /*first.maybeThinking = TRUE;*/
11928         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11929     }
11930 }
11931
11932 void
11933 BackwardInner(target)
11934      int target;
11935 {
11936     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11937
11938     if (appData.debugMode)
11939         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11940                 target, currentMove, forwardMostMove);
11941
11942     if (gameMode == EditPosition) return;
11943     if (currentMove <= backwardMostMove) {
11944         ClearHighlights();
11945         DrawPosition(full_redraw, boards[currentMove]);
11946         return;
11947     }
11948     if (gameMode == PlayFromGameFile && !pausing)
11949       PauseEvent();
11950     
11951     if (moveList[target][0]) {
11952         int fromX, fromY, toX, toY;
11953         toX = moveList[target][2] - AAA;
11954         toY = moveList[target][3] - ONE;
11955         if (moveList[target][1] == '@') {
11956             if (appData.highlightLastMove) {
11957                 SetHighlights(-1, -1, toX, toY);
11958             }
11959         } else {
11960             fromX = moveList[target][0] - AAA;
11961             fromY = moveList[target][1] - ONE;
11962             if (target == currentMove - 1) {
11963                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11964             }
11965             if (appData.highlightLastMove) {
11966                 SetHighlights(fromX, fromY, toX, toY);
11967             }
11968         }
11969     }
11970     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11971         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11972         while (currentMove > target) {
11973             SendToProgram("undo\n", &first);
11974             currentMove--;
11975         }
11976     } else {
11977         currentMove = target;
11978     }
11979     
11980     if (gameMode == EditGame || gameMode == EndOfGame) {
11981         whiteTimeRemaining = timeRemaining[0][currentMove];
11982         blackTimeRemaining = timeRemaining[1][currentMove];
11983     }
11984     DisplayBothClocks();
11985     DisplayMove(currentMove - 1);
11986     DrawPosition(full_redraw, boards[currentMove]);
11987     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11988     // [HGM] PV info: routine tests if comment empty
11989     DisplayComment(currentMove - 1, commentList[currentMove]);
11990 }
11991
11992 void
11993 BackwardEvent()
11994 {
11995     if (gameMode == IcsExamining && !pausing) {
11996         SendToICS(ics_prefix);
11997         SendToICS("backward\n");
11998     } else {
11999         BackwardInner(currentMove - 1);
12000     }
12001 }
12002
12003 void
12004 ToStartEvent()
12005 {
12006     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12007         /* to optimize, we temporarily turn off analysis mode while we undo
12008          * all the moves. Otherwise we get analysis output after each undo.
12009          */ 
12010         if (first.analysisSupport) {
12011           SendToProgram("exit\nforce\n", &first);
12012           first.analyzing = FALSE;
12013         }
12014     }
12015
12016     if (gameMode == IcsExamining && !pausing) {
12017         SendToICS(ics_prefix);
12018         SendToICS("backward 999999\n");
12019     } else {
12020         BackwardInner(backwardMostMove);
12021     }
12022
12023     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12024         /* we have fed all the moves, so reactivate analysis mode */
12025         SendToProgram("analyze\n", &first);
12026         first.analyzing = TRUE;
12027         /*first.maybeThinking = TRUE;*/
12028         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12029     }
12030 }
12031
12032 void
12033 ToNrEvent(int to)
12034 {
12035   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12036   if (to >= forwardMostMove) to = forwardMostMove;
12037   if (to <= backwardMostMove) to = backwardMostMove;
12038   if (to < currentMove) {
12039     BackwardInner(to);
12040   } else {
12041     ForwardInner(to);
12042   }
12043 }
12044
12045 void
12046 RevertEvent()
12047 {
12048     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12049         return;
12050     }
12051     if (gameMode != IcsExamining) {
12052         DisplayError(_("You are not examining a game"), 0);
12053         return;
12054     }
12055     if (pausing) {
12056         DisplayError(_("You can't revert while pausing"), 0);
12057         return;
12058     }
12059     SendToICS(ics_prefix);
12060     SendToICS("revert\n");
12061 }
12062
12063 void
12064 RetractMoveEvent()
12065 {
12066     switch (gameMode) {
12067       case MachinePlaysWhite:
12068       case MachinePlaysBlack:
12069         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12070             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12071             return;
12072         }
12073         if (forwardMostMove < 2) return;
12074         currentMove = forwardMostMove = forwardMostMove - 2;
12075         whiteTimeRemaining = timeRemaining[0][currentMove];
12076         blackTimeRemaining = timeRemaining[1][currentMove];
12077         DisplayBothClocks();
12078         DisplayMove(currentMove - 1);
12079         ClearHighlights();/*!! could figure this out*/
12080         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12081         SendToProgram("remove\n", &first);
12082         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12083         break;
12084
12085       case BeginningOfGame:
12086       default:
12087         break;
12088
12089       case IcsPlayingWhite:
12090       case IcsPlayingBlack:
12091         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12092             SendToICS(ics_prefix);
12093             SendToICS("takeback 2\n");
12094         } else {
12095             SendToICS(ics_prefix);
12096             SendToICS("takeback 1\n");
12097         }
12098         break;
12099     }
12100 }
12101
12102 void
12103 MoveNowEvent()
12104 {
12105     ChessProgramState *cps;
12106
12107     switch (gameMode) {
12108       case MachinePlaysWhite:
12109         if (!WhiteOnMove(forwardMostMove)) {
12110             DisplayError(_("It is your turn"), 0);
12111             return;
12112         }
12113         cps = &first;
12114         break;
12115       case MachinePlaysBlack:
12116         if (WhiteOnMove(forwardMostMove)) {
12117             DisplayError(_("It is your turn"), 0);
12118             return;
12119         }
12120         cps = &first;
12121         break;
12122       case TwoMachinesPlay:
12123         if (WhiteOnMove(forwardMostMove) ==
12124             (first.twoMachinesColor[0] == 'w')) {
12125             cps = &first;
12126         } else {
12127             cps = &second;
12128         }
12129         break;
12130       case BeginningOfGame:
12131       default:
12132         return;
12133     }
12134     SendToProgram("?\n", cps);
12135 }
12136
12137 void
12138 TruncateGameEvent()
12139 {
12140     EditGameEvent();
12141     if (gameMode != EditGame) return;
12142     TruncateGame();
12143 }
12144
12145 void
12146 TruncateGame()
12147 {
12148     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12149     if (forwardMostMove > currentMove) {
12150         if (gameInfo.resultDetails != NULL) {
12151             free(gameInfo.resultDetails);
12152             gameInfo.resultDetails = NULL;
12153             gameInfo.result = GameUnfinished;
12154         }
12155         forwardMostMove = currentMove;
12156         HistorySet(parseList, backwardMostMove, forwardMostMove,
12157                    currentMove-1);
12158     }
12159 }
12160
12161 void
12162 HintEvent()
12163 {
12164     if (appData.noChessProgram) return;
12165     switch (gameMode) {
12166       case MachinePlaysWhite:
12167         if (WhiteOnMove(forwardMostMove)) {
12168             DisplayError(_("Wait until your turn"), 0);
12169             return;
12170         }
12171         break;
12172       case BeginningOfGame:
12173       case MachinePlaysBlack:
12174         if (!WhiteOnMove(forwardMostMove)) {
12175             DisplayError(_("Wait until your turn"), 0);
12176             return;
12177         }
12178         break;
12179       default:
12180         DisplayError(_("No hint available"), 0);
12181         return;
12182     }
12183     SendToProgram("hint\n", &first);
12184     hintRequested = TRUE;
12185 }
12186
12187 void
12188 BookEvent()
12189 {
12190     if (appData.noChessProgram) return;
12191     switch (gameMode) {
12192       case MachinePlaysWhite:
12193         if (WhiteOnMove(forwardMostMove)) {
12194             DisplayError(_("Wait until your turn"), 0);
12195             return;
12196         }
12197         break;
12198       case BeginningOfGame:
12199       case MachinePlaysBlack:
12200         if (!WhiteOnMove(forwardMostMove)) {
12201             DisplayError(_("Wait until your turn"), 0);
12202             return;
12203         }
12204         break;
12205       case EditPosition:
12206         EditPositionDone(TRUE);
12207         break;
12208       case TwoMachinesPlay:
12209         return;
12210       default:
12211         break;
12212     }
12213     SendToProgram("bk\n", &first);
12214     bookOutput[0] = NULLCHAR;
12215     bookRequested = TRUE;
12216 }
12217
12218 void
12219 AboutGameEvent()
12220 {
12221     char *tags = PGNTags(&gameInfo);
12222     TagsPopUp(tags, CmailMsg());
12223     free(tags);
12224 }
12225
12226 /* end button procedures */
12227
12228 void
12229 PrintPosition(fp, move)
12230      FILE *fp;
12231      int move;
12232 {
12233     int i, j;
12234     
12235     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12236         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12237             char c = PieceToChar(boards[move][i][j]);
12238             fputc(c == 'x' ? '.' : c, fp);
12239             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12240         }
12241     }
12242     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12243       fprintf(fp, "white to play\n");
12244     else
12245       fprintf(fp, "black to play\n");
12246 }
12247
12248 void
12249 PrintOpponents(fp)
12250      FILE *fp;
12251 {
12252     if (gameInfo.white != NULL) {
12253         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12254     } else {
12255         fprintf(fp, "\n");
12256     }
12257 }
12258
12259 /* Find last component of program's own name, using some heuristics */
12260 void
12261 TidyProgramName(prog, host, buf)
12262      char *prog, *host, buf[MSG_SIZ];
12263 {
12264     char *p, *q;
12265     int local = (strcmp(host, "localhost") == 0);
12266     while (!local && (p = strchr(prog, ';')) != NULL) {
12267         p++;
12268         while (*p == ' ') p++;
12269         prog = p;
12270     }
12271     if (*prog == '"' || *prog == '\'') {
12272         q = strchr(prog + 1, *prog);
12273     } else {
12274         q = strchr(prog, ' ');
12275     }
12276     if (q == NULL) q = prog + strlen(prog);
12277     p = q;
12278     while (p >= prog && *p != '/' && *p != '\\') p--;
12279     p++;
12280     if(p == prog && *p == '"') p++;
12281     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12282     memcpy(buf, p, q - p);
12283     buf[q - p] = NULLCHAR;
12284     if (!local) {
12285         strcat(buf, "@");
12286         strcat(buf, host);
12287     }
12288 }
12289
12290 char *
12291 TimeControlTagValue()
12292 {
12293     char buf[MSG_SIZ];
12294     if (!appData.clockMode) {
12295         strcpy(buf, "-");
12296     } else if (movesPerSession > 0) {
12297         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12298     } else if (timeIncrement == 0) {
12299         sprintf(buf, "%ld", timeControl/1000);
12300     } else {
12301         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12302     }
12303     return StrSave(buf);
12304 }
12305
12306 void
12307 SetGameInfo()
12308 {
12309     /* This routine is used only for certain modes */
12310     VariantClass v = gameInfo.variant;
12311     ChessMove r = GameUnfinished;
12312     char *p = NULL;
12313
12314     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12315         r = gameInfo.result; 
12316         p = gameInfo.resultDetails; 
12317         gameInfo.resultDetails = NULL;
12318     }
12319     ClearGameInfo(&gameInfo);
12320     gameInfo.variant = v;
12321
12322     switch (gameMode) {
12323       case MachinePlaysWhite:
12324         gameInfo.event = StrSave( appData.pgnEventHeader );
12325         gameInfo.site = StrSave(HostName());
12326         gameInfo.date = PGNDate();
12327         gameInfo.round = StrSave("-");
12328         gameInfo.white = StrSave(first.tidy);
12329         gameInfo.black = StrSave(UserName());
12330         gameInfo.timeControl = TimeControlTagValue();
12331         break;
12332
12333       case MachinePlaysBlack:
12334         gameInfo.event = StrSave( appData.pgnEventHeader );
12335         gameInfo.site = StrSave(HostName());
12336         gameInfo.date = PGNDate();
12337         gameInfo.round = StrSave("-");
12338         gameInfo.white = StrSave(UserName());
12339         gameInfo.black = StrSave(first.tidy);
12340         gameInfo.timeControl = TimeControlTagValue();
12341         break;
12342
12343       case TwoMachinesPlay:
12344         gameInfo.event = StrSave( appData.pgnEventHeader );
12345         gameInfo.site = StrSave(HostName());
12346         gameInfo.date = PGNDate();
12347         if (matchGame > 0) {
12348             char buf[MSG_SIZ];
12349             sprintf(buf, "%d", matchGame);
12350             gameInfo.round = StrSave(buf);
12351         } else {
12352             gameInfo.round = StrSave("-");
12353         }
12354         if (first.twoMachinesColor[0] == 'w') {
12355             gameInfo.white = StrSave(first.tidy);
12356             gameInfo.black = StrSave(second.tidy);
12357         } else {
12358             gameInfo.white = StrSave(second.tidy);
12359             gameInfo.black = StrSave(first.tidy);
12360         }
12361         gameInfo.timeControl = TimeControlTagValue();
12362         break;
12363
12364       case EditGame:
12365         gameInfo.event = StrSave("Edited game");
12366         gameInfo.site = StrSave(HostName());
12367         gameInfo.date = PGNDate();
12368         gameInfo.round = StrSave("-");
12369         gameInfo.white = StrSave("-");
12370         gameInfo.black = StrSave("-");
12371         gameInfo.result = r;
12372         gameInfo.resultDetails = p;
12373         break;
12374
12375       case EditPosition:
12376         gameInfo.event = StrSave("Edited position");
12377         gameInfo.site = StrSave(HostName());
12378         gameInfo.date = PGNDate();
12379         gameInfo.round = StrSave("-");
12380         gameInfo.white = StrSave("-");
12381         gameInfo.black = StrSave("-");
12382         break;
12383
12384       case IcsPlayingWhite:
12385       case IcsPlayingBlack:
12386       case IcsObserving:
12387       case IcsExamining:
12388         break;
12389
12390       case PlayFromGameFile:
12391         gameInfo.event = StrSave("Game from non-PGN file");
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       default:
12400         break;
12401     }
12402 }
12403
12404 void
12405 ReplaceComment(index, text)
12406      int index;
12407      char *text;
12408 {
12409     int len;
12410
12411     while (*text == '\n') text++;
12412     len = strlen(text);
12413     while (len > 0 && text[len - 1] == '\n') len--;
12414
12415     if (commentList[index] != NULL)
12416       free(commentList[index]);
12417
12418     if (len == 0) {
12419         commentList[index] = NULL;
12420         return;
12421     }
12422   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12423       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12424       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12425     commentList[index] = (char *) malloc(len + 2);
12426     strncpy(commentList[index], text, len);
12427     commentList[index][len] = '\n';
12428     commentList[index][len + 1] = NULLCHAR;
12429   } else { 
12430     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12431     char *p;
12432     commentList[index] = (char *) malloc(len + 6);
12433     strcpy(commentList[index], "{\n");
12434     strncpy(commentList[index]+2, text, len);
12435     commentList[index][len+2] = NULLCHAR;
12436     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12437     strcat(commentList[index], "\n}\n");
12438   }
12439 }
12440
12441 void
12442 CrushCRs(text)
12443      char *text;
12444 {
12445   char *p = text;
12446   char *q = text;
12447   char ch;
12448
12449   do {
12450     ch = *p++;
12451     if (ch == '\r') continue;
12452     *q++ = ch;
12453   } while (ch != '\0');
12454 }
12455
12456 void
12457 AppendComment(index, text, addBraces)
12458      int index;
12459      char *text;
12460      Boolean addBraces; // [HGM] braces: tells if we should add {}
12461 {
12462     int oldlen, len;
12463     char *old;
12464
12465 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12466     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12467
12468     CrushCRs(text);
12469     while (*text == '\n') text++;
12470     len = strlen(text);
12471     while (len > 0 && text[len - 1] == '\n') len--;
12472
12473     if (len == 0) return;
12474
12475     if (commentList[index] != NULL) {
12476         old = commentList[index];
12477         oldlen = strlen(old);
12478         while(commentList[index][oldlen-1] ==  '\n')
12479           commentList[index][--oldlen] = NULLCHAR;
12480         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12481         strcpy(commentList[index], old);
12482         free(old);
12483         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12484         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12485           if(addBraces) addBraces = FALSE; else { text++; len--; }
12486           while (*text == '\n') { text++; len--; }
12487           commentList[index][--oldlen] = NULLCHAR;
12488       }
12489         if(addBraces) strcat(commentList[index], "\n{\n");
12490         else          strcat(commentList[index], "\n");
12491         strcat(commentList[index], text);
12492         if(addBraces) strcat(commentList[index], "\n}\n");
12493         else          strcat(commentList[index], "\n");
12494     } else {
12495         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12496         if(addBraces)
12497              strcpy(commentList[index], "{\n");
12498         else commentList[index][0] = NULLCHAR;
12499         strcat(commentList[index], text);
12500         strcat(commentList[index], "\n");
12501         if(addBraces) strcat(commentList[index], "}\n");
12502     }
12503 }
12504
12505 static char * FindStr( char * text, char * sub_text )
12506 {
12507     char * result = strstr( text, sub_text );
12508
12509     if( result != NULL ) {
12510         result += strlen( sub_text );
12511     }
12512
12513     return result;
12514 }
12515
12516 /* [AS] Try to extract PV info from PGN comment */
12517 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12518 char *GetInfoFromComment( int index, char * text )
12519 {
12520     char * sep = text;
12521
12522     if( text != NULL && index > 0 ) {
12523         int score = 0;
12524         int depth = 0;
12525         int time = -1, sec = 0, deci;
12526         char * s_eval = FindStr( text, "[%eval " );
12527         char * s_emt = FindStr( text, "[%emt " );
12528
12529         if( s_eval != NULL || s_emt != NULL ) {
12530             /* New style */
12531             char delim;
12532
12533             if( s_eval != NULL ) {
12534                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12535                     return text;
12536                 }
12537
12538                 if( delim != ']' ) {
12539                     return text;
12540                 }
12541             }
12542
12543             if( s_emt != NULL ) {
12544             }
12545                 return text;
12546         }
12547         else {
12548             /* We expect something like: [+|-]nnn.nn/dd */
12549             int score_lo = 0;
12550
12551             if(*text != '{') return text; // [HGM] braces: must be normal comment
12552
12553             sep = strchr( text, '/' );
12554             if( sep == NULL || sep < (text+4) ) {
12555                 return text;
12556             }
12557
12558             time = -1; sec = -1; deci = -1;
12559             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12560                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12561                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12562                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12563                 return text;
12564             }
12565
12566             if( score_lo < 0 || score_lo >= 100 ) {
12567                 return text;
12568             }
12569
12570             if(sec >= 0) time = 600*time + 10*sec; else
12571             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12572
12573             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12574
12575             /* [HGM] PV time: now locate end of PV info */
12576             while( *++sep >= '0' && *sep <= '9'); // strip depth
12577             if(time >= 0)
12578             while( *++sep >= '0' && *sep <= '9'); // strip time
12579             if(sec >= 0)
12580             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12581             if(deci >= 0)
12582             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12583             while(*sep == ' ') sep++;
12584         }
12585
12586         if( depth <= 0 ) {
12587             return text;
12588         }
12589
12590         if( time < 0 ) {
12591             time = -1;
12592         }
12593
12594         pvInfoList[index-1].depth = depth;
12595         pvInfoList[index-1].score = score;
12596         pvInfoList[index-1].time  = 10*time; // centi-sec
12597         if(*sep == '}') *sep = 0; else *--sep = '{';
12598     }
12599     return sep;
12600 }
12601
12602 void
12603 SendToProgram(message, cps)
12604      char *message;
12605      ChessProgramState *cps;
12606 {
12607     int count, outCount, error;
12608     char buf[MSG_SIZ];
12609
12610     if (cps->pr == NULL) return;
12611     Attention(cps);
12612     
12613     if (appData.debugMode) {
12614         TimeMark now;
12615         GetTimeMark(&now);
12616         fprintf(debugFP, "%ld >%-6s: %s", 
12617                 SubtractTimeMarks(&now, &programStartTime),
12618                 cps->which, message);
12619     }
12620     
12621     count = strlen(message);
12622     outCount = OutputToProcess(cps->pr, message, count, &error);
12623     if (outCount < count && !exiting 
12624                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12625         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12626         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12627             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12628                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12629                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12630             } else {
12631                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12632             }
12633             gameInfo.resultDetails = StrSave(buf);
12634         }
12635         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12636     }
12637 }
12638
12639 void
12640 ReceiveFromProgram(isr, closure, message, count, error)
12641      InputSourceRef isr;
12642      VOIDSTAR closure;
12643      char *message;
12644      int count;
12645      int error;
12646 {
12647     char *end_str;
12648     char buf[MSG_SIZ];
12649     ChessProgramState *cps = (ChessProgramState *)closure;
12650
12651     if (isr != cps->isr) return; /* Killed intentionally */
12652     if (count <= 0) {
12653         if (count == 0) {
12654             sprintf(buf,
12655                     _("Error: %s chess program (%s) exited unexpectedly"),
12656                     cps->which, cps->program);
12657         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12658                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12659                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12660                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12661                 } else {
12662                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12663                 }
12664                 gameInfo.resultDetails = StrSave(buf);
12665             }
12666             RemoveInputSource(cps->isr);
12667             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12668         } else {
12669             sprintf(buf,
12670                     _("Error reading from %s chess program (%s)"),
12671                     cps->which, cps->program);
12672             RemoveInputSource(cps->isr);
12673
12674             /* [AS] Program is misbehaving badly... kill it */
12675             if( count == -2 ) {
12676                 DestroyChildProcess( cps->pr, 9 );
12677                 cps->pr = NoProc;
12678             }
12679
12680             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12681         }
12682         return;
12683     }
12684     
12685     if ((end_str = strchr(message, '\r')) != NULL)
12686       *end_str = NULLCHAR;
12687     if ((end_str = strchr(message, '\n')) != NULL)
12688       *end_str = NULLCHAR;
12689     
12690     if (appData.debugMode) {
12691         TimeMark now; int print = 1;
12692         char *quote = ""; char c; int i;
12693
12694         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12695                 char start = message[0];
12696                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12697                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12698                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12699                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12700                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12701                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12702                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12703                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12704                         { quote = "# "; print = (appData.engineComments == 2); }
12705                 message[0] = start; // restore original message
12706         }
12707         if(print) {
12708                 GetTimeMark(&now);
12709                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12710                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12711                         quote,
12712                         message);
12713         }
12714     }
12715
12716     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12717     if (appData.icsEngineAnalyze) {
12718         if (strstr(message, "whisper") != NULL ||
12719              strstr(message, "kibitz") != NULL || 
12720             strstr(message, "tellics") != NULL) return;
12721     }
12722
12723     HandleMachineMove(message, cps);
12724 }
12725
12726
12727 void
12728 SendTimeControl(cps, mps, tc, inc, sd, st)
12729      ChessProgramState *cps;
12730      int mps, inc, sd, st;
12731      long tc;
12732 {
12733     char buf[MSG_SIZ];
12734     int seconds;
12735
12736     if( timeControl_2 > 0 ) {
12737         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12738             tc = timeControl_2;
12739         }
12740     }
12741     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12742     inc /= cps->timeOdds;
12743     st  /= cps->timeOdds;
12744
12745     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12746
12747     if (st > 0) {
12748       /* Set exact time per move, normally using st command */
12749       if (cps->stKludge) {
12750         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12751         seconds = st % 60;
12752         if (seconds == 0) {
12753           sprintf(buf, "level 1 %d\n", st/60);
12754         } else {
12755           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12756         }
12757       } else {
12758         sprintf(buf, "st %d\n", st);
12759       }
12760     } else {
12761       /* Set conventional or incremental time control, using level command */
12762       if (seconds == 0) {
12763         /* Note old gnuchess bug -- minutes:seconds used to not work.
12764            Fixed in later versions, but still avoid :seconds
12765            when seconds is 0. */
12766         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12767       } else {
12768         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12769                 seconds, inc/1000);
12770       }
12771     }
12772     SendToProgram(buf, cps);
12773
12774     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12775     /* Orthogonally, limit search to given depth */
12776     if (sd > 0) {
12777       if (cps->sdKludge) {
12778         sprintf(buf, "depth\n%d\n", sd);
12779       } else {
12780         sprintf(buf, "sd %d\n", sd);
12781       }
12782       SendToProgram(buf, cps);
12783     }
12784
12785     if(cps->nps > 0) { /* [HGM] nps */
12786         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12787         else {
12788                 sprintf(buf, "nps %d\n", cps->nps);
12789               SendToProgram(buf, cps);
12790         }
12791     }
12792 }
12793
12794 ChessProgramState *WhitePlayer()
12795 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12796 {
12797     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12798        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12799         return &second;
12800     return &first;
12801 }
12802
12803 void
12804 SendTimeRemaining(cps, machineWhite)
12805      ChessProgramState *cps;
12806      int /*boolean*/ machineWhite;
12807 {
12808     char message[MSG_SIZ];
12809     long time, otime;
12810
12811     /* Note: this routine must be called when the clocks are stopped
12812        or when they have *just* been set or switched; otherwise
12813        it will be off by the time since the current tick started.
12814     */
12815     if (machineWhite) {
12816         time = whiteTimeRemaining / 10;
12817         otime = blackTimeRemaining / 10;
12818     } else {
12819         time = blackTimeRemaining / 10;
12820         otime = whiteTimeRemaining / 10;
12821     }
12822     /* [HGM] translate opponent's time by time-odds factor */
12823     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12824     if (appData.debugMode) {
12825         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12826     }
12827
12828     if (time <= 0) time = 1;
12829     if (otime <= 0) otime = 1;
12830     
12831     sprintf(message, "time %ld\n", time);
12832     SendToProgram(message, cps);
12833
12834     sprintf(message, "otim %ld\n", otime);
12835     SendToProgram(message, cps);
12836 }
12837
12838 int
12839 BoolFeature(p, name, loc, cps)
12840      char **p;
12841      char *name;
12842      int *loc;
12843      ChessProgramState *cps;
12844 {
12845   char buf[MSG_SIZ];
12846   int len = strlen(name);
12847   int val;
12848   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12849     (*p) += len + 1;
12850     sscanf(*p, "%d", &val);
12851     *loc = (val != 0);
12852     while (**p && **p != ' ') (*p)++;
12853     sprintf(buf, "accepted %s\n", name);
12854     SendToProgram(buf, cps);
12855     return TRUE;
12856   }
12857   return FALSE;
12858 }
12859
12860 int
12861 IntFeature(p, name, loc, cps)
12862      char **p;
12863      char *name;
12864      int *loc;
12865      ChessProgramState *cps;
12866 {
12867   char buf[MSG_SIZ];
12868   int len = strlen(name);
12869   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12870     (*p) += len + 1;
12871     sscanf(*p, "%d", loc);
12872     while (**p && **p != ' ') (*p)++;
12873     sprintf(buf, "accepted %s\n", name);
12874     SendToProgram(buf, cps);
12875     return TRUE;
12876   }
12877   return FALSE;
12878 }
12879
12880 int
12881 StringFeature(p, name, loc, cps)
12882      char **p;
12883      char *name;
12884      char loc[];
12885      ChessProgramState *cps;
12886 {
12887   char buf[MSG_SIZ];
12888   int len = strlen(name);
12889   if (strncmp((*p), name, len) == 0
12890       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12891     (*p) += len + 2;
12892     sscanf(*p, "%[^\"]", loc);
12893     while (**p && **p != '\"') (*p)++;
12894     if (**p == '\"') (*p)++;
12895     sprintf(buf, "accepted %s\n", name);
12896     SendToProgram(buf, cps);
12897     return TRUE;
12898   }
12899   return FALSE;
12900 }
12901
12902 int 
12903 ParseOption(Option *opt, ChessProgramState *cps)
12904 // [HGM] options: process the string that defines an engine option, and determine
12905 // name, type, default value, and allowed value range
12906 {
12907         char *p, *q, buf[MSG_SIZ];
12908         int n, min = (-1)<<31, max = 1<<31, def;
12909
12910         if(p = strstr(opt->name, " -spin ")) {
12911             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12912             if(max < min) max = min; // enforce consistency
12913             if(def < min) def = min;
12914             if(def > max) def = max;
12915             opt->value = def;
12916             opt->min = min;
12917             opt->max = max;
12918             opt->type = Spin;
12919         } else if((p = strstr(opt->name, " -slider "))) {
12920             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12921             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12922             if(max < min) max = min; // enforce consistency
12923             if(def < min) def = min;
12924             if(def > max) def = max;
12925             opt->value = def;
12926             opt->min = min;
12927             opt->max = max;
12928             opt->type = Spin; // Slider;
12929         } else if((p = strstr(opt->name, " -string "))) {
12930             opt->textValue = p+9;
12931             opt->type = TextBox;
12932         } else if((p = strstr(opt->name, " -file "))) {
12933             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12934             opt->textValue = p+7;
12935             opt->type = TextBox; // FileName;
12936         } else if((p = strstr(opt->name, " -path "))) {
12937             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12938             opt->textValue = p+7;
12939             opt->type = TextBox; // PathName;
12940         } else if(p = strstr(opt->name, " -check ")) {
12941             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12942             opt->value = (def != 0);
12943             opt->type = CheckBox;
12944         } else if(p = strstr(opt->name, " -combo ")) {
12945             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12946             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12947             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12948             opt->value = n = 0;
12949             while(q = StrStr(q, " /// ")) {
12950                 n++; *q = 0;    // count choices, and null-terminate each of them
12951                 q += 5;
12952                 if(*q == '*') { // remember default, which is marked with * prefix
12953                     q++;
12954                     opt->value = n;
12955                 }
12956                 cps->comboList[cps->comboCnt++] = q;
12957             }
12958             cps->comboList[cps->comboCnt++] = NULL;
12959             opt->max = n + 1;
12960             opt->type = ComboBox;
12961         } else if(p = strstr(opt->name, " -button")) {
12962             opt->type = Button;
12963         } else if(p = strstr(opt->name, " -save")) {
12964             opt->type = SaveButton;
12965         } else return FALSE;
12966         *p = 0; // terminate option name
12967         // now look if the command-line options define a setting for this engine option.
12968         if(cps->optionSettings && cps->optionSettings[0])
12969             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12970         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12971                 sprintf(buf, "option %s", p);
12972                 if(p = strstr(buf, ",")) *p = 0;
12973                 strcat(buf, "\n");
12974                 SendToProgram(buf, cps);
12975         }
12976         return TRUE;
12977 }
12978
12979 void
12980 FeatureDone(cps, val)
12981      ChessProgramState* cps;
12982      int val;
12983 {
12984   DelayedEventCallback cb = GetDelayedEvent();
12985   if ((cb == InitBackEnd3 && cps == &first) ||
12986       (cb == TwoMachinesEventIfReady && cps == &second)) {
12987     CancelDelayedEvent();
12988     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12989   }
12990   cps->initDone = val;
12991 }
12992
12993 /* Parse feature command from engine */
12994 void
12995 ParseFeatures(args, cps)
12996      char* args;
12997      ChessProgramState *cps;  
12998 {
12999   char *p = args;
13000   char *q;
13001   int val;
13002   char buf[MSG_SIZ];
13003
13004   for (;;) {
13005     while (*p == ' ') p++;
13006     if (*p == NULLCHAR) return;
13007
13008     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13009     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13010     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13011     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13012     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13013     if (BoolFeature(&p, "reuse", &val, cps)) {
13014       /* Engine can disable reuse, but can't enable it if user said no */
13015       if (!val) cps->reuse = FALSE;
13016       continue;
13017     }
13018     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13019     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13020       if (gameMode == TwoMachinesPlay) {
13021         DisplayTwoMachinesTitle();
13022       } else {
13023         DisplayTitle("");
13024       }
13025       continue;
13026     }
13027     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13028     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13029     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13030     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13031     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13032     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13033     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13034     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13035     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13036     if (IntFeature(&p, "done", &val, cps)) {
13037       FeatureDone(cps, val);
13038       continue;
13039     }
13040     /* Added by Tord: */
13041     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13042     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13043     /* End of additions by Tord */
13044
13045     /* [HGM] added features: */
13046     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13047     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13048     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13049     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13050     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13051     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13052     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13053         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13054             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13055             SendToProgram(buf, cps);
13056             continue;
13057         }
13058         if(cps->nrOptions >= MAX_OPTIONS) {
13059             cps->nrOptions--;
13060             sprintf(buf, "%s engine has too many options\n", cps->which);
13061             DisplayError(buf, 0);
13062         }
13063         continue;
13064     }
13065     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13066     /* End of additions by HGM */
13067
13068     /* unknown feature: complain and skip */
13069     q = p;
13070     while (*q && *q != '=') q++;
13071     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13072     SendToProgram(buf, cps);
13073     p = q;
13074     if (*p == '=') {
13075       p++;
13076       if (*p == '\"') {
13077         p++;
13078         while (*p && *p != '\"') p++;
13079         if (*p == '\"') p++;
13080       } else {
13081         while (*p && *p != ' ') p++;
13082       }
13083     }
13084   }
13085
13086 }
13087
13088 void
13089 PeriodicUpdatesEvent(newState)
13090      int newState;
13091 {
13092     if (newState == appData.periodicUpdates)
13093       return;
13094
13095     appData.periodicUpdates=newState;
13096
13097     /* Display type changes, so update it now */
13098 //    DisplayAnalysis();
13099
13100     /* Get the ball rolling again... */
13101     if (newState) {
13102         AnalysisPeriodicEvent(1);
13103         StartAnalysisClock();
13104     }
13105 }
13106
13107 void
13108 PonderNextMoveEvent(newState)
13109      int newState;
13110 {
13111     if (newState == appData.ponderNextMove) return;
13112     if (gameMode == EditPosition) EditPositionDone(TRUE);
13113     if (newState) {
13114         SendToProgram("hard\n", &first);
13115         if (gameMode == TwoMachinesPlay) {
13116             SendToProgram("hard\n", &second);
13117         }
13118     } else {
13119         SendToProgram("easy\n", &first);
13120         thinkOutput[0] = NULLCHAR;
13121         if (gameMode == TwoMachinesPlay) {
13122             SendToProgram("easy\n", &second);
13123         }
13124     }
13125     appData.ponderNextMove = newState;
13126 }
13127
13128 void
13129 NewSettingEvent(option, command, value)
13130      char *command;
13131      int option, value;
13132 {
13133     char buf[MSG_SIZ];
13134
13135     if (gameMode == EditPosition) EditPositionDone(TRUE);
13136     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13137     SendToProgram(buf, &first);
13138     if (gameMode == TwoMachinesPlay) {
13139         SendToProgram(buf, &second);
13140     }
13141 }
13142
13143 void
13144 ShowThinkingEvent()
13145 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13146 {
13147     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13148     int newState = appData.showThinking
13149         // [HGM] thinking: other features now need thinking output as well
13150         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13151     
13152     if (oldState == newState) return;
13153     oldState = newState;
13154     if (gameMode == EditPosition) EditPositionDone(TRUE);
13155     if (oldState) {
13156         SendToProgram("post\n", &first);
13157         if (gameMode == TwoMachinesPlay) {
13158             SendToProgram("post\n", &second);
13159         }
13160     } else {
13161         SendToProgram("nopost\n", &first);
13162         thinkOutput[0] = NULLCHAR;
13163         if (gameMode == TwoMachinesPlay) {
13164             SendToProgram("nopost\n", &second);
13165         }
13166     }
13167 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13168 }
13169
13170 void
13171 AskQuestionEvent(title, question, replyPrefix, which)
13172      char *title; char *question; char *replyPrefix; char *which;
13173 {
13174   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13175   if (pr == NoProc) return;
13176   AskQuestion(title, question, replyPrefix, pr);
13177 }
13178
13179 void
13180 DisplayMove(moveNumber)
13181      int moveNumber;
13182 {
13183     char message[MSG_SIZ];
13184     char res[MSG_SIZ];
13185     char cpThinkOutput[MSG_SIZ];
13186
13187     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13188     
13189     if (moveNumber == forwardMostMove - 1 || 
13190         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13191
13192         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13193
13194         if (strchr(cpThinkOutput, '\n')) {
13195             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13196         }
13197     } else {
13198         *cpThinkOutput = NULLCHAR;
13199     }
13200
13201     /* [AS] Hide thinking from human user */
13202     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13203         *cpThinkOutput = NULLCHAR;
13204         if( thinkOutput[0] != NULLCHAR ) {
13205             int i;
13206
13207             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13208                 cpThinkOutput[i] = '.';
13209             }
13210             cpThinkOutput[i] = NULLCHAR;
13211             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13212         }
13213     }
13214
13215     if (moveNumber == forwardMostMove - 1 &&
13216         gameInfo.resultDetails != NULL) {
13217         if (gameInfo.resultDetails[0] == NULLCHAR) {
13218             sprintf(res, " %s", PGNResult(gameInfo.result));
13219         } else {
13220             sprintf(res, " {%s} %s",
13221                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13222         }
13223     } else {
13224         res[0] = NULLCHAR;
13225     }
13226
13227     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13228         DisplayMessage(res, cpThinkOutput);
13229     } else {
13230         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13231                 WhiteOnMove(moveNumber) ? " " : ".. ",
13232                 parseList[moveNumber], res);
13233         DisplayMessage(message, cpThinkOutput);
13234     }
13235 }
13236
13237 void
13238 DisplayComment(moveNumber, text)
13239      int moveNumber;
13240      char *text;
13241 {
13242     char title[MSG_SIZ];
13243     char buf[8000]; // comment can be long!
13244     int score, depth;
13245     
13246     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13247       strcpy(title, "Comment");
13248     } else {
13249       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13250               WhiteOnMove(moveNumber) ? " " : ".. ",
13251               parseList[moveNumber]);
13252     }
13253     // [HGM] PV info: display PV info together with (or as) comment
13254     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13255       if(text == NULL) text = "";                                           
13256       score = pvInfoList[moveNumber].score;
13257       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13258               depth, (pvInfoList[moveNumber].time+50)/100, text);
13259       text = buf;
13260     }
13261     if (text != NULL && (appData.autoDisplayComment || commentUp))
13262         CommentPopUp(title, text);
13263 }
13264
13265 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13266  * might be busy thinking or pondering.  It can be omitted if your
13267  * gnuchess is configured to stop thinking immediately on any user
13268  * input.  However, that gnuchess feature depends on the FIONREAD
13269  * ioctl, which does not work properly on some flavors of Unix.
13270  */
13271 void
13272 Attention(cps)
13273      ChessProgramState *cps;
13274 {
13275 #if ATTENTION
13276     if (!cps->useSigint) return;
13277     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13278     switch (gameMode) {
13279       case MachinePlaysWhite:
13280       case MachinePlaysBlack:
13281       case TwoMachinesPlay:
13282       case IcsPlayingWhite:
13283       case IcsPlayingBlack:
13284       case AnalyzeMode:
13285       case AnalyzeFile:
13286         /* Skip if we know it isn't thinking */
13287         if (!cps->maybeThinking) return;
13288         if (appData.debugMode)
13289           fprintf(debugFP, "Interrupting %s\n", cps->which);
13290         InterruptChildProcess(cps->pr);
13291         cps->maybeThinking = FALSE;
13292         break;
13293       default:
13294         break;
13295     }
13296 #endif /*ATTENTION*/
13297 }
13298
13299 int
13300 CheckFlags()
13301 {
13302     if (whiteTimeRemaining <= 0) {
13303         if (!whiteFlag) {
13304             whiteFlag = TRUE;
13305             if (appData.icsActive) {
13306                 if (appData.autoCallFlag &&
13307                     gameMode == IcsPlayingBlack && !blackFlag) {
13308                   SendToICS(ics_prefix);
13309                   SendToICS("flag\n");
13310                 }
13311             } else {
13312                 if (blackFlag) {
13313                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13314                 } else {
13315                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13316                     if (appData.autoCallFlag) {
13317                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13318                         return TRUE;
13319                     }
13320                 }
13321             }
13322         }
13323     }
13324     if (blackTimeRemaining <= 0) {
13325         if (!blackFlag) {
13326             blackFlag = TRUE;
13327             if (appData.icsActive) {
13328                 if (appData.autoCallFlag &&
13329                     gameMode == IcsPlayingWhite && !whiteFlag) {
13330                   SendToICS(ics_prefix);
13331                   SendToICS("flag\n");
13332                 }
13333             } else {
13334                 if (whiteFlag) {
13335                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13336                 } else {
13337                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13338                     if (appData.autoCallFlag) {
13339                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13340                         return TRUE;
13341                     }
13342                 }
13343             }
13344         }
13345     }
13346     return FALSE;
13347 }
13348
13349 void
13350 CheckTimeControl()
13351 {
13352     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13353         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13354
13355     /*
13356      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13357      */
13358     if ( !WhiteOnMove(forwardMostMove) )
13359         /* White made time control */
13360         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13361         /* [HGM] time odds: correct new time quota for time odds! */
13362                                             / WhitePlayer()->timeOdds;
13363       else
13364         /* Black made time control */
13365         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13366                                             / WhitePlayer()->other->timeOdds;
13367 }
13368
13369 void
13370 DisplayBothClocks()
13371 {
13372     int wom = gameMode == EditPosition ?
13373       !blackPlaysFirst : WhiteOnMove(currentMove);
13374     DisplayWhiteClock(whiteTimeRemaining, wom);
13375     DisplayBlackClock(blackTimeRemaining, !wom);
13376 }
13377
13378
13379 /* Timekeeping seems to be a portability nightmare.  I think everyone
13380    has ftime(), but I'm really not sure, so I'm including some ifdefs
13381    to use other calls if you don't.  Clocks will be less accurate if
13382    you have neither ftime nor gettimeofday.
13383 */
13384
13385 /* VS 2008 requires the #include outside of the function */
13386 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13387 #include <sys/timeb.h>
13388 #endif
13389
13390 /* Get the current time as a TimeMark */
13391 void
13392 GetTimeMark(tm)
13393      TimeMark *tm;
13394 {
13395 #if HAVE_GETTIMEOFDAY
13396
13397     struct timeval timeVal;
13398     struct timezone timeZone;
13399
13400     gettimeofday(&timeVal, &timeZone);
13401     tm->sec = (long) timeVal.tv_sec; 
13402     tm->ms = (int) (timeVal.tv_usec / 1000L);
13403
13404 #else /*!HAVE_GETTIMEOFDAY*/
13405 #if HAVE_FTIME
13406
13407 // include <sys/timeb.h> / moved to just above start of function
13408     struct timeb timeB;
13409
13410     ftime(&timeB);
13411     tm->sec = (long) timeB.time;
13412     tm->ms = (int) timeB.millitm;
13413
13414 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13415     tm->sec = (long) time(NULL);
13416     tm->ms = 0;
13417 #endif
13418 #endif
13419 }
13420
13421 /* Return the difference in milliseconds between two
13422    time marks.  We assume the difference will fit in a long!
13423 */
13424 long
13425 SubtractTimeMarks(tm2, tm1)
13426      TimeMark *tm2, *tm1;
13427 {
13428     return 1000L*(tm2->sec - tm1->sec) +
13429            (long) (tm2->ms - tm1->ms);
13430 }
13431
13432
13433 /*
13434  * Code to manage the game clocks.
13435  *
13436  * In tournament play, black starts the clock and then white makes a move.
13437  * We give the human user a slight advantage if he is playing white---the
13438  * clocks don't run until he makes his first move, so it takes zero time.
13439  * Also, we don't account for network lag, so we could get out of sync
13440  * with GNU Chess's clock -- but then, referees are always right.  
13441  */
13442
13443 static TimeMark tickStartTM;
13444 static long intendedTickLength;
13445
13446 long
13447 NextTickLength(timeRemaining)
13448      long timeRemaining;
13449 {
13450     long nominalTickLength, nextTickLength;
13451
13452     if (timeRemaining > 0L && timeRemaining <= 10000L)
13453       nominalTickLength = 100L;
13454     else
13455       nominalTickLength = 1000L;
13456     nextTickLength = timeRemaining % nominalTickLength;
13457     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13458
13459     return nextTickLength;
13460 }
13461
13462 /* Adjust clock one minute up or down */
13463 void
13464 AdjustClock(Boolean which, int dir)
13465 {
13466     if(which) blackTimeRemaining += 60000*dir;
13467     else      whiteTimeRemaining += 60000*dir;
13468     DisplayBothClocks();
13469 }
13470
13471 /* Stop clocks and reset to a fresh time control */
13472 void
13473 ResetClocks() 
13474 {
13475     (void) StopClockTimer();
13476     if (appData.icsActive) {
13477         whiteTimeRemaining = blackTimeRemaining = 0;
13478     } else if (searchTime) {
13479         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13480         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13481     } else { /* [HGM] correct new time quote for time odds */
13482         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13483         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13484     }
13485     if (whiteFlag || blackFlag) {
13486         DisplayTitle("");
13487         whiteFlag = blackFlag = FALSE;
13488     }
13489     DisplayBothClocks();
13490 }
13491
13492 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13493
13494 /* Decrement running clock by amount of time that has passed */
13495 void
13496 DecrementClocks()
13497 {
13498     long timeRemaining;
13499     long lastTickLength, fudge;
13500     TimeMark now;
13501
13502     if (!appData.clockMode) return;
13503     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13504         
13505     GetTimeMark(&now);
13506
13507     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13508
13509     /* Fudge if we woke up a little too soon */
13510     fudge = intendedTickLength - lastTickLength;
13511     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13512
13513     if (WhiteOnMove(forwardMostMove)) {
13514         if(whiteNPS >= 0) lastTickLength = 0;
13515         timeRemaining = whiteTimeRemaining -= lastTickLength;
13516         DisplayWhiteClock(whiteTimeRemaining - fudge,
13517                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13518     } else {
13519         if(blackNPS >= 0) lastTickLength = 0;
13520         timeRemaining = blackTimeRemaining -= lastTickLength;
13521         DisplayBlackClock(blackTimeRemaining - fudge,
13522                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13523     }
13524
13525     if (CheckFlags()) return;
13526         
13527     tickStartTM = now;
13528     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13529     StartClockTimer(intendedTickLength);
13530
13531     /* if the time remaining has fallen below the alarm threshold, sound the
13532      * alarm. if the alarm has sounded and (due to a takeback or time control
13533      * with increment) the time remaining has increased to a level above the
13534      * threshold, reset the alarm so it can sound again. 
13535      */
13536     
13537     if (appData.icsActive && appData.icsAlarm) {
13538
13539         /* make sure we are dealing with the user's clock */
13540         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13541                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13542            )) return;
13543
13544         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13545             alarmSounded = FALSE;
13546         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13547             PlayAlarmSound();
13548             alarmSounded = TRUE;
13549         }
13550     }
13551 }
13552
13553
13554 /* A player has just moved, so stop the previously running
13555    clock and (if in clock mode) start the other one.
13556    We redisplay both clocks in case we're in ICS mode, because
13557    ICS gives us an update to both clocks after every move.
13558    Note that this routine is called *after* forwardMostMove
13559    is updated, so the last fractional tick must be subtracted
13560    from the color that is *not* on move now.
13561 */
13562 void
13563 SwitchClocks()
13564 {
13565     long lastTickLength;
13566     TimeMark now;
13567     int flagged = FALSE;
13568
13569     GetTimeMark(&now);
13570
13571     if (StopClockTimer() && appData.clockMode) {
13572         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13573         if (WhiteOnMove(forwardMostMove)) {
13574             if(blackNPS >= 0) lastTickLength = 0;
13575             blackTimeRemaining -= lastTickLength;
13576            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13577 //         if(pvInfoList[forwardMostMove-1].time == -1)
13578                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13579                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13580         } else {
13581            if(whiteNPS >= 0) lastTickLength = 0;
13582            whiteTimeRemaining -= lastTickLength;
13583            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13584 //         if(pvInfoList[forwardMostMove-1].time == -1)
13585                  pvInfoList[forwardMostMove-1].time = 
13586                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13587         }
13588         flagged = CheckFlags();
13589     }
13590     CheckTimeControl();
13591
13592     if (flagged || !appData.clockMode) return;
13593
13594     switch (gameMode) {
13595       case MachinePlaysBlack:
13596       case MachinePlaysWhite:
13597       case BeginningOfGame:
13598         if (pausing) return;
13599         break;
13600
13601       case EditGame:
13602       case PlayFromGameFile:
13603       case IcsExamining:
13604         return;
13605
13606       default:
13607         break;
13608     }
13609
13610     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13611         if(WhiteOnMove(forwardMostMove))
13612              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13613         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13614     }
13615
13616     tickStartTM = now;
13617     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13618       whiteTimeRemaining : blackTimeRemaining);
13619     StartClockTimer(intendedTickLength);
13620 }
13621         
13622
13623 /* Stop both clocks */
13624 void
13625 StopClocks()
13626 {       
13627     long lastTickLength;
13628     TimeMark now;
13629
13630     if (!StopClockTimer()) return;
13631     if (!appData.clockMode) return;
13632
13633     GetTimeMark(&now);
13634
13635     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13636     if (WhiteOnMove(forwardMostMove)) {
13637         if(whiteNPS >= 0) lastTickLength = 0;
13638         whiteTimeRemaining -= lastTickLength;
13639         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13640     } else {
13641         if(blackNPS >= 0) lastTickLength = 0;
13642         blackTimeRemaining -= lastTickLength;
13643         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13644     }
13645     CheckFlags();
13646 }
13647         
13648 /* Start clock of player on move.  Time may have been reset, so
13649    if clock is already running, stop and restart it. */
13650 void
13651 StartClocks()
13652 {
13653     (void) StopClockTimer(); /* in case it was running already */
13654     DisplayBothClocks();
13655     if (CheckFlags()) return;
13656
13657     if (!appData.clockMode) return;
13658     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13659
13660     GetTimeMark(&tickStartTM);
13661     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13662       whiteTimeRemaining : blackTimeRemaining);
13663
13664    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13665     whiteNPS = blackNPS = -1; 
13666     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13667        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13668         whiteNPS = first.nps;
13669     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13670        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13671         blackNPS = first.nps;
13672     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13673         whiteNPS = second.nps;
13674     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13675         blackNPS = second.nps;
13676     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13677
13678     StartClockTimer(intendedTickLength);
13679 }
13680
13681 char *
13682 TimeString(ms)
13683      long ms;
13684 {
13685     long second, minute, hour, day;
13686     char *sign = "";
13687     static char buf[32];
13688     
13689     if (ms > 0 && ms <= 9900) {
13690       /* convert milliseconds to tenths, rounding up */
13691       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13692
13693       sprintf(buf, " %03.1f ", tenths/10.0);
13694       return buf;
13695     }
13696
13697     /* convert milliseconds to seconds, rounding up */
13698     /* use floating point to avoid strangeness of integer division
13699        with negative dividends on many machines */
13700     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13701
13702     if (second < 0) {
13703         sign = "-";
13704         second = -second;
13705     }
13706     
13707     day = second / (60 * 60 * 24);
13708     second = second % (60 * 60 * 24);
13709     hour = second / (60 * 60);
13710     second = second % (60 * 60);
13711     minute = second / 60;
13712     second = second % 60;
13713     
13714     if (day > 0)
13715       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13716               sign, day, hour, minute, second);
13717     else if (hour > 0)
13718       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13719     else
13720       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13721     
13722     return buf;
13723 }
13724
13725
13726 /*
13727  * This is necessary because some C libraries aren't ANSI C compliant yet.
13728  */
13729 char *
13730 StrStr(string, match)
13731      char *string, *match;
13732 {
13733     int i, length;
13734     
13735     length = strlen(match);
13736     
13737     for (i = strlen(string) - length; i >= 0; i--, string++)
13738       if (!strncmp(match, string, length))
13739         return string;
13740     
13741     return NULL;
13742 }
13743
13744 char *
13745 StrCaseStr(string, match)
13746      char *string, *match;
13747 {
13748     int i, j, length;
13749     
13750     length = strlen(match);
13751     
13752     for (i = strlen(string) - length; i >= 0; i--, string++) {
13753         for (j = 0; j < length; j++) {
13754             if (ToLower(match[j]) != ToLower(string[j]))
13755               break;
13756         }
13757         if (j == length) return string;
13758     }
13759
13760     return NULL;
13761 }
13762
13763 #ifndef _amigados
13764 int
13765 StrCaseCmp(s1, s2)
13766      char *s1, *s2;
13767 {
13768     char c1, c2;
13769     
13770     for (;;) {
13771         c1 = ToLower(*s1++);
13772         c2 = ToLower(*s2++);
13773         if (c1 > c2) return 1;
13774         if (c1 < c2) return -1;
13775         if (c1 == NULLCHAR) return 0;
13776     }
13777 }
13778
13779
13780 int
13781 ToLower(c)
13782      int c;
13783 {
13784     return isupper(c) ? tolower(c) : c;
13785 }
13786
13787
13788 int
13789 ToUpper(c)
13790      int c;
13791 {
13792     return islower(c) ? toupper(c) : c;
13793 }
13794 #endif /* !_amigados    */
13795
13796 char *
13797 StrSave(s)
13798      char *s;
13799 {
13800     char *ret;
13801
13802     if ((ret = (char *) malloc(strlen(s) + 1))) {
13803         strcpy(ret, s);
13804     }
13805     return ret;
13806 }
13807
13808 char *
13809 StrSavePtr(s, savePtr)
13810      char *s, **savePtr;
13811 {
13812     if (*savePtr) {
13813         free(*savePtr);
13814     }
13815     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13816         strcpy(*savePtr, s);
13817     }
13818     return(*savePtr);
13819 }
13820
13821 char *
13822 PGNDate()
13823 {
13824     time_t clock;
13825     struct tm *tm;
13826     char buf[MSG_SIZ];
13827
13828     clock = time((time_t *)NULL);
13829     tm = localtime(&clock);
13830     sprintf(buf, "%04d.%02d.%02d",
13831             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13832     return StrSave(buf);
13833 }
13834
13835
13836 char *
13837 PositionToFEN(move, overrideCastling)
13838      int move;
13839      char *overrideCastling;
13840 {
13841     int i, j, fromX, fromY, toX, toY;
13842     int whiteToPlay;
13843     char buf[128];
13844     char *p, *q;
13845     int emptycount;
13846     ChessSquare piece;
13847
13848     whiteToPlay = (gameMode == EditPosition) ?
13849       !blackPlaysFirst : (move % 2 == 0);
13850     p = buf;
13851
13852     /* Piece placement data */
13853     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13854         emptycount = 0;
13855         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13856             if (boards[move][i][j] == EmptySquare) {
13857                 emptycount++;
13858             } else { ChessSquare piece = boards[move][i][j];
13859                 if (emptycount > 0) {
13860                     if(emptycount<10) /* [HGM] can be >= 10 */
13861                         *p++ = '0' + emptycount;
13862                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13863                     emptycount = 0;
13864                 }
13865                 if(PieceToChar(piece) == '+') {
13866                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13867                     *p++ = '+';
13868                     piece = (ChessSquare)(DEMOTED piece);
13869                 } 
13870                 *p++ = PieceToChar(piece);
13871                 if(p[-1] == '~') {
13872                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13873                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13874                     *p++ = '~';
13875                 }
13876             }
13877         }
13878         if (emptycount > 0) {
13879             if(emptycount<10) /* [HGM] can be >= 10 */
13880                 *p++ = '0' + emptycount;
13881             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13882             emptycount = 0;
13883         }
13884         *p++ = '/';
13885     }
13886     *(p - 1) = ' ';
13887
13888     /* [HGM] print Crazyhouse or Shogi holdings */
13889     if( gameInfo.holdingsWidth ) {
13890         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13891         q = p;
13892         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13893             piece = boards[move][i][BOARD_WIDTH-1];
13894             if( piece != EmptySquare )
13895               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13896                   *p++ = PieceToChar(piece);
13897         }
13898         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13899             piece = boards[move][BOARD_HEIGHT-i-1][0];
13900             if( piece != EmptySquare )
13901               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13902                   *p++ = PieceToChar(piece);
13903         }
13904
13905         if( q == p ) *p++ = '-';
13906         *p++ = ']';
13907         *p++ = ' ';
13908     }
13909
13910     /* Active color */
13911     *p++ = whiteToPlay ? 'w' : 'b';
13912     *p++ = ' ';
13913
13914   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13915     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13916   } else {
13917   if(nrCastlingRights) {
13918      q = p;
13919      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13920        /* [HGM] write directly from rights */
13921            if(boards[move][CASTLING][2] != NoRights &&
13922               boards[move][CASTLING][0] != NoRights   )
13923                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13924            if(boards[move][CASTLING][2] != NoRights &&
13925               boards[move][CASTLING][1] != NoRights   )
13926                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13927            if(boards[move][CASTLING][5] != NoRights &&
13928               boards[move][CASTLING][3] != NoRights   )
13929                 *p++ = boards[move][CASTLING][3] + AAA;
13930            if(boards[move][CASTLING][5] != NoRights &&
13931               boards[move][CASTLING][4] != NoRights   )
13932                 *p++ = boards[move][CASTLING][4] + AAA;
13933      } else {
13934
13935         /* [HGM] write true castling rights */
13936         if( nrCastlingRights == 6 ) {
13937             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13938                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13939             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13940                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13941             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13942                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13943             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13944                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13945         }
13946      }
13947      if (q == p) *p++ = '-'; /* No castling rights */
13948      *p++ = ' ';
13949   }
13950
13951   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13952      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13953     /* En passant target square */
13954     if (move > backwardMostMove) {
13955         fromX = moveList[move - 1][0] - AAA;
13956         fromY = moveList[move - 1][1] - ONE;
13957         toX = moveList[move - 1][2] - AAA;
13958         toY = moveList[move - 1][3] - ONE;
13959         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13960             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13961             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13962             fromX == toX) {
13963             /* 2-square pawn move just happened */
13964             *p++ = toX + AAA;
13965             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13966         } else {
13967             *p++ = '-';
13968         }
13969     } else if(move == backwardMostMove) {
13970         // [HGM] perhaps we should always do it like this, and forget the above?
13971         if((signed char)boards[move][EP_STATUS] >= 0) {
13972             *p++ = boards[move][EP_STATUS] + AAA;
13973             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13974         } else {
13975             *p++ = '-';
13976         }
13977     } else {
13978         *p++ = '-';
13979     }
13980     *p++ = ' ';
13981   }
13982   }
13983
13984     /* [HGM] find reversible plies */
13985     {   int i = 0, j=move;
13986
13987         if (appData.debugMode) { int k;
13988             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13989             for(k=backwardMostMove; k<=forwardMostMove; k++)
13990                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13991
13992         }
13993
13994         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13995         if( j == backwardMostMove ) i += initialRulePlies;
13996         sprintf(p, "%d ", i);
13997         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13998     }
13999     /* Fullmove number */
14000     sprintf(p, "%d", (move / 2) + 1);
14001     
14002     return StrSave(buf);
14003 }
14004
14005 Boolean
14006 ParseFEN(board, blackPlaysFirst, fen)
14007     Board board;
14008      int *blackPlaysFirst;
14009      char *fen;
14010 {
14011     int i, j;
14012     char *p;
14013     int emptycount;
14014     ChessSquare piece;
14015
14016     p = fen;
14017
14018     /* [HGM] by default clear Crazyhouse holdings, if present */
14019     if(gameInfo.holdingsWidth) {
14020        for(i=0; i<BOARD_HEIGHT; i++) {
14021            board[i][0]             = EmptySquare; /* black holdings */
14022            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14023            board[i][1]             = (ChessSquare) 0; /* black counts */
14024            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14025        }
14026     }
14027
14028     /* Piece placement data */
14029     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14030         j = 0;
14031         for (;;) {
14032             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14033                 if (*p == '/') p++;
14034                 emptycount = gameInfo.boardWidth - j;
14035                 while (emptycount--)
14036                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14037                 break;
14038 #if(BOARD_FILES >= 10)
14039             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14040                 p++; emptycount=10;
14041                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14042                 while (emptycount--)
14043                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14044 #endif
14045             } else if (isdigit(*p)) {
14046                 emptycount = *p++ - '0';
14047                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14048                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14049                 while (emptycount--)
14050                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14051             } else if (*p == '+' || isalpha(*p)) {
14052                 if (j >= gameInfo.boardWidth) return FALSE;
14053                 if(*p=='+') {
14054                     piece = CharToPiece(*++p);
14055                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14056                     piece = (ChessSquare) (PROMOTED piece ); p++;
14057                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14058                 } else piece = CharToPiece(*p++);
14059
14060                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14061                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14062                     piece = (ChessSquare) (PROMOTED piece);
14063                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14064                     p++;
14065                 }
14066                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14067             } else {
14068                 return FALSE;
14069             }
14070         }
14071     }
14072     while (*p == '/' || *p == ' ') p++;
14073
14074     /* [HGM] look for Crazyhouse holdings here */
14075     while(*p==' ') p++;
14076     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14077         if(*p == '[') p++;
14078         if(*p == '-' ) *p++; /* empty holdings */ else {
14079             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14080             /* if we would allow FEN reading to set board size, we would   */
14081             /* have to add holdings and shift the board read so far here   */
14082             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14083                 *p++;
14084                 if((int) piece >= (int) BlackPawn ) {
14085                     i = (int)piece - (int)BlackPawn;
14086                     i = PieceToNumber((ChessSquare)i);
14087                     if( i >= gameInfo.holdingsSize ) return FALSE;
14088                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14089                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14090                 } else {
14091                     i = (int)piece - (int)WhitePawn;
14092                     i = PieceToNumber((ChessSquare)i);
14093                     if( i >= gameInfo.holdingsSize ) return FALSE;
14094                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14095                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14096                 }
14097             }
14098         }
14099         if(*p == ']') *p++;
14100     }
14101
14102     while(*p == ' ') p++;
14103
14104     /* Active color */
14105     switch (*p++) {
14106       case 'w':
14107         *blackPlaysFirst = FALSE;
14108         break;
14109       case 'b': 
14110         *blackPlaysFirst = TRUE;
14111         break;
14112       default:
14113         return FALSE;
14114     }
14115
14116     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14117     /* return the extra info in global variiables             */
14118
14119     /* set defaults in case FEN is incomplete */
14120     board[EP_STATUS] = EP_UNKNOWN;
14121     for(i=0; i<nrCastlingRights; i++ ) {
14122         board[CASTLING][i] =
14123             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14124     }   /* assume possible unless obviously impossible */
14125     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14126     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14127     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14128     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14129     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14130     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14131     FENrulePlies = 0;
14132
14133     while(*p==' ') p++;
14134     if(nrCastlingRights) {
14135       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14136           /* castling indicator present, so default becomes no castlings */
14137           for(i=0; i<nrCastlingRights; i++ ) {
14138                  board[CASTLING][i] = NoRights;
14139           }
14140       }
14141       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14142              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14143              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14144              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14145         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14146
14147         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14148             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14149             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14150         }
14151         switch(c) {
14152           case'K':
14153               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14154               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14155               board[CASTLING][2] = whiteKingFile;
14156               break;
14157           case'Q':
14158               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14159               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14160               board[CASTLING][2] = whiteKingFile;
14161               break;
14162           case'k':
14163               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14164               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14165               board[CASTLING][5] = blackKingFile;
14166               break;
14167           case'q':
14168               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14169               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14170               board[CASTLING][5] = blackKingFile;
14171           case '-':
14172               break;
14173           default: /* FRC castlings */
14174               if(c >= 'a') { /* black rights */
14175                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14176                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14177                   if(i == BOARD_RGHT) break;
14178                   board[CASTLING][5] = i;
14179                   c -= AAA;
14180                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14181                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14182                   if(c > i)
14183                       board[CASTLING][3] = c;
14184                   else
14185                       board[CASTLING][4] = c;
14186               } else { /* white rights */
14187                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14188                     if(board[0][i] == WhiteKing) break;
14189                   if(i == BOARD_RGHT) break;
14190                   board[CASTLING][2] = i;
14191                   c -= AAA - 'a' + 'A';
14192                   if(board[0][c] >= WhiteKing) break;
14193                   if(c > i)
14194                       board[CASTLING][0] = c;
14195                   else
14196                       board[CASTLING][1] = c;
14197               }
14198         }
14199       }
14200     if (appData.debugMode) {
14201         fprintf(debugFP, "FEN castling rights:");
14202         for(i=0; i<nrCastlingRights; i++)
14203         fprintf(debugFP, " %d", board[CASTLING][i]);
14204         fprintf(debugFP, "\n");
14205     }
14206
14207       while(*p==' ') p++;
14208     }
14209
14210     /* read e.p. field in games that know e.p. capture */
14211     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14212        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14213       if(*p=='-') {
14214         p++; board[EP_STATUS] = EP_NONE;
14215       } else {
14216          char c = *p++ - AAA;
14217
14218          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14219          if(*p >= '0' && *p <='9') *p++;
14220          board[EP_STATUS] = c;
14221       }
14222     }
14223
14224
14225     if(sscanf(p, "%d", &i) == 1) {
14226         FENrulePlies = i; /* 50-move ply counter */
14227         /* (The move number is still ignored)    */
14228     }
14229
14230     return TRUE;
14231 }
14232       
14233 void
14234 EditPositionPasteFEN(char *fen)
14235 {
14236   if (fen != NULL) {
14237     Board initial_position;
14238
14239     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14240       DisplayError(_("Bad FEN position in clipboard"), 0);
14241       return ;
14242     } else {
14243       int savedBlackPlaysFirst = blackPlaysFirst;
14244       EditPositionEvent();
14245       blackPlaysFirst = savedBlackPlaysFirst;
14246       CopyBoard(boards[0], initial_position);
14247       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14248       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14249       DisplayBothClocks();
14250       DrawPosition(FALSE, boards[currentMove]);
14251     }
14252   }
14253 }
14254
14255 static char cseq[12] = "\\   ";
14256
14257 Boolean set_cont_sequence(char *new_seq)
14258 {
14259     int len;
14260     Boolean ret;
14261
14262     // handle bad attempts to set the sequence
14263         if (!new_seq)
14264                 return 0; // acceptable error - no debug
14265
14266     len = strlen(new_seq);
14267     ret = (len > 0) && (len < sizeof(cseq));
14268     if (ret)
14269         strcpy(cseq, new_seq);
14270     else if (appData.debugMode)
14271         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14272     return ret;
14273 }
14274
14275 /*
14276     reformat a source message so words don't cross the width boundary.  internal
14277     newlines are not removed.  returns the wrapped size (no null character unless
14278     included in source message).  If dest is NULL, only calculate the size required
14279     for the dest buffer.  lp argument indicats line position upon entry, and it's
14280     passed back upon exit.
14281 */
14282 int wrap(char *dest, char *src, int count, int width, int *lp)
14283 {
14284     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14285
14286     cseq_len = strlen(cseq);
14287     old_line = line = *lp;
14288     ansi = len = clen = 0;
14289
14290     for (i=0; i < count; i++)
14291     {
14292         if (src[i] == '\033')
14293             ansi = 1;
14294
14295         // if we hit the width, back up
14296         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14297         {
14298             // store i & len in case the word is too long
14299             old_i = i, old_len = len;
14300
14301             // find the end of the last word
14302             while (i && src[i] != ' ' && src[i] != '\n')
14303             {
14304                 i--;
14305                 len--;
14306             }
14307
14308             // word too long?  restore i & len before splitting it
14309             if ((old_i-i+clen) >= width)
14310             {
14311                 i = old_i;
14312                 len = old_len;
14313             }
14314
14315             // extra space?
14316             if (i && src[i-1] == ' ')
14317                 len--;
14318
14319             if (src[i] != ' ' && src[i] != '\n')
14320             {
14321                 i--;
14322                 if (len)
14323                     len--;
14324             }
14325
14326             // now append the newline and continuation sequence
14327             if (dest)
14328                 dest[len] = '\n';
14329             len++;
14330             if (dest)
14331                 strncpy(dest+len, cseq, cseq_len);
14332             len += cseq_len;
14333             line = cseq_len;
14334             clen = cseq_len;
14335             continue;
14336         }
14337
14338         if (dest)
14339             dest[len] = src[i];
14340         len++;
14341         if (!ansi)
14342             line++;
14343         if (src[i] == '\n')
14344             line = 0;
14345         if (src[i] == 'm')
14346             ansi = 0;
14347     }
14348     if (dest && appData.debugMode)
14349     {
14350         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14351             count, width, line, len, *lp);
14352         show_bytes(debugFP, src, count);
14353         fprintf(debugFP, "\ndest: ");
14354         show_bytes(debugFP, dest, len);
14355         fprintf(debugFP, "\n");
14356     }
14357     *lp = dest ? line : old_line;
14358
14359     return len;
14360 }
14361
14362 // [HGM] vari: routines for shelving variations
14363
14364 void 
14365 PushTail(int firstMove, int lastMove)
14366 {
14367         int i, j, nrMoves = lastMove - firstMove;
14368
14369         if(appData.icsActive) { // only in local mode
14370                 forwardMostMove = currentMove; // mimic old ICS behavior
14371                 return;
14372         }
14373         if(storedGames >= MAX_VARIATIONS-1) return;
14374
14375         // push current tail of game on stack
14376         savedResult[storedGames] = gameInfo.result;
14377         savedDetails[storedGames] = gameInfo.resultDetails;
14378         gameInfo.resultDetails = NULL;
14379         savedFirst[storedGames] = firstMove;
14380         savedLast [storedGames] = lastMove;
14381         savedFramePtr[storedGames] = framePtr;
14382         framePtr -= nrMoves; // reserve space for the boards
14383         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14384             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14385             for(j=0; j<MOVE_LEN; j++)
14386                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14387             for(j=0; j<2*MOVE_LEN; j++)
14388                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14389             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14390             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14391             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14392             pvInfoList[firstMove+i-1].depth = 0;
14393             commentList[framePtr+i] = commentList[firstMove+i];
14394             commentList[firstMove+i] = NULL;
14395         }
14396
14397         storedGames++;
14398         forwardMostMove = currentMove; // truncte game so we can start variation
14399         if(storedGames == 1) GreyRevert(FALSE);
14400 }
14401
14402 Boolean
14403 PopTail(Boolean annotate)
14404 {
14405         int i, j, nrMoves;
14406         char buf[8000], moveBuf[20];
14407
14408         if(appData.icsActive) return FALSE; // only in local mode
14409         if(!storedGames) return FALSE; // sanity
14410
14411         storedGames--;
14412         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14413         nrMoves = savedLast[storedGames] - currentMove;
14414         if(annotate) {
14415                 int cnt = 10;
14416                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14417                 else strcpy(buf, "(");
14418                 for(i=currentMove; i<forwardMostMove; i++) {
14419                         if(WhiteOnMove(i))
14420                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14421                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14422                         strcat(buf, moveBuf);
14423                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14424                 }
14425                 strcat(buf, ")");
14426         }
14427         for(i=1; i<nrMoves; i++) { // copy last variation back
14428             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14429             for(j=0; j<MOVE_LEN; j++)
14430                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14431             for(j=0; j<2*MOVE_LEN; j++)
14432                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14433             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14434             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14435             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14436             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14437             commentList[currentMove+i] = commentList[framePtr+i];
14438             commentList[framePtr+i] = NULL;
14439         }
14440         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14441         framePtr = savedFramePtr[storedGames];
14442         gameInfo.result = savedResult[storedGames];
14443         if(gameInfo.resultDetails != NULL) {
14444             free(gameInfo.resultDetails);
14445       }
14446         gameInfo.resultDetails = savedDetails[storedGames];
14447         forwardMostMove = currentMove + nrMoves;
14448         if(storedGames == 0) GreyRevert(TRUE);
14449         return TRUE;
14450 }
14451
14452 void 
14453 CleanupTail()
14454 {       // remove all shelved variations
14455         int i;
14456         for(i=0; i<storedGames; i++) {
14457             if(savedDetails[i])
14458                 free(savedDetails[i]);
14459             savedDetails[i] = NULL;
14460         }
14461         for(i=framePtr; i<MAX_MOVES; i++) {
14462                 if(commentList[i]) free(commentList[i]);
14463                 commentList[i] = NULL;
14464         }
14465         framePtr = MAX_MOVES-1;
14466         storedGames = 0;
14467 }