fix double start of zippy engine after switch to gothic
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178                            char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180                         int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220
221 #ifdef WIN32
222        extern void ConsoleCreate();
223 #endif
224
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
228
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
235
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
239 int endPV = -1;
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
243 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
251 int chattingPartner;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
253
254 /* States for ics_getting_history */
255 #define H_FALSE 0
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
261
262 /* whosays values for GameEnds */
263 #define GE_ICS 0
264 #define GE_ENGINE 1
265 #define GE_PLAYER 2
266 #define GE_FILE 3
267 #define GE_XBOARD 4
268 #define GE_ENGINE1 5
269 #define GE_ENGINE2 6
270
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
273
274 /* Different types of move when calling RegisterMove */
275 #define CMAIL_MOVE   0
276 #define CMAIL_RESIGN 1
277 #define CMAIL_DRAW   2
278 #define CMAIL_ACCEPT 3
279
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
284
285 /* Telnet protocol constants */
286 #define TN_WILL 0373
287 #define TN_WONT 0374
288 #define TN_DO   0375
289 #define TN_DONT 0376
290 #define TN_IAC  0377
291 #define TN_ECHO 0001
292 #define TN_SGA  0003
293 #define TN_PORT 23
294
295 /* [AS] */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
297 {
298     assert( dst != NULL );
299     assert( src != NULL );
300     assert( count > 0 );
301
302     strncpy( dst, src, count );
303     dst[ count-1 ] = '\0';
304     return dst;
305 }
306
307 /* Some compiler can't cast u64 to double
308  * This function do the job for us:
309
310  * We use the highest bit for cast, this only
311  * works if the highest bit is not
312  * in use (This should not happen)
313  *
314  * We used this for all compiler
315  */
316 double
317 u64ToDouble(u64 value)
318 {
319   double r;
320   u64 tmp = value & u64Const(0x7fffffffffffffff);
321   r = (double)(s64)tmp;
322   if (value & u64Const(0x8000000000000000))
323        r +=  9.2233720368547758080e18; /* 2^63 */
324  return r;
325 }
326
327 /* Fake up flags for now, as we aren't keeping track of castling
328    availability yet. [HGM] Change of logic: the flag now only
329    indicates the type of castlings allowed by the rule of the game.
330    The actual rights themselves are maintained in the array
331    castlingRights, as part of the game history, and are not probed
332    by this function.
333  */
334 int
335 PosFlags(index)
336 {
337   int flags = F_ALL_CASTLE_OK;
338   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339   switch (gameInfo.variant) {
340   case VariantSuicide:
341     flags &= ~F_ALL_CASTLE_OK;
342   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343     flags |= F_IGNORE_CHECK;
344   case VariantLosers:
345     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346     break;
347   case VariantAtomic:
348     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
349     break;
350   case VariantKriegspiel:
351     flags |= F_KRIEGSPIEL_CAPTURE;
352     break;
353   case VariantCapaRandom: 
354   case VariantFischeRandom:
355     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356   case VariantNoCastle:
357   case VariantShatranj:
358   case VariantCourier:
359     flags &= ~F_ALL_CASTLE_OK;
360     break;
361   default:
362     break;
363   }
364   return flags;
365 }
366
367 FILE *gameFileFP, *debugFP;
368
369 /* 
370     [AS] Note: sometimes, the sscanf() function is used to parse the input
371     into a fixed-size buffer. Because of this, we must be prepared to
372     receive strings as long as the size of the input buffer, which is currently
373     set to 4K for Windows and 8K for the rest.
374     So, we must either allocate sufficiently large buffers here, or
375     reduce the size of the input buffer in the input reading part.
376 */
377
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
381
382 ChessProgramState first, second;
383
384 /* premove variables */
385 int premoveToX = 0;
386 int premoveToY = 0;
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
390 int gotPremove = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
393
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
396
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
424 int movesPerSession;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
429 int matchGame = 0;
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
433
434 /* animateTraining preserves the state of appData.animate
435  * when Training mode is activated. This allows the
436  * response to be animated when appData.animate == TRUE and
437  * appData.animateDragging == TRUE.
438  */
439 Boolean animateTraining;
440
441 GameInfo gameInfo;
442
443 AppData appData;
444
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char  initialRights[BOARD_FILES];
449 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int   initialRulePlies, FENrulePlies;
451 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int loadFlag = 0; 
453 int shuffleOpenings;
454 int mute; // mute all sounds
455
456 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
459 int storedGames = 0;
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
465
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
469
470 ChessSquare  FIDEArray[2][BOARD_FILES] = {
471     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474         BlackKing, BlackBishop, BlackKnight, BlackRook }
475 };
476
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481         BlackKing, BlackKing, BlackKnight, BlackRook }
482 };
483
484 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
485     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487     { BlackRook, BlackMan, BlackBishop, BlackQueen,
488         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
489 };
490
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492     { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494     { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495         BlackKing, BlackBishop, BlackKnight, BlackRook }
496 };
497
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 };
504
505
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
512 };
513
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
519 };
520
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
523         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
525         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
526 };
527
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
530         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
532         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
533 };
534
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
537         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
539         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 };
541
542 #ifdef GOTHIC
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
545         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
547         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
548 };
549 #else // !GOTHIC
550 #define GothicArray CapablancaArray
551 #endif // !GOTHIC
552
553 #ifdef FALCON
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
556         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
558         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !FALCON
561 #define FalconArray CapablancaArray
562 #endif // !FALCON
563
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
570
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
577 };
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
581
582
583 Board initialPosition;
584
585
586 /* Convert str to a rating. Checks for special cases of "----",
587
588    "++++", etc. Also strips ()'s */
589 int
590 string_to_rating(str)
591   char *str;
592 {
593   while(*str && !isdigit(*str)) ++str;
594   if (!*str)
595     return 0;   /* One of the special "no rating" cases */
596   else
597     return atoi(str);
598 }
599
600 void
601 ClearProgramStats()
602 {
603     /* Init programStats */
604     programStats.movelist[0] = 0;
605     programStats.depth = 0;
606     programStats.nr_moves = 0;
607     programStats.moves_left = 0;
608     programStats.nodes = 0;
609     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
610     programStats.score = 0;
611     programStats.got_only_move = 0;
612     programStats.got_fail = 0;
613     programStats.line_is_book = 0;
614 }
615
616 void
617 InitBackEnd1()
618 {
619     int matched, min, sec;
620
621     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
622
623     GetTimeMark(&programStartTime);
624     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
625
626     ClearProgramStats();
627     programStats.ok_to_send = 1;
628     programStats.seen_stat = 0;
629
630     /*
631      * Initialize game list
632      */
633     ListNew(&gameList);
634
635
636     /*
637      * Internet chess server status
638      */
639     if (appData.icsActive) {
640         appData.matchMode = FALSE;
641         appData.matchGames = 0;
642 #if ZIPPY       
643         appData.noChessProgram = !appData.zippyPlay;
644 #else
645         appData.zippyPlay = FALSE;
646         appData.zippyTalk = FALSE;
647         appData.noChessProgram = TRUE;
648 #endif
649         if (*appData.icsHelper != NULLCHAR) {
650             appData.useTelnet = TRUE;
651             appData.telnetProgram = appData.icsHelper;
652         }
653     } else {
654         appData.zippyTalk = appData.zippyPlay = FALSE;
655     }
656
657     /* [AS] Initialize pv info list [HGM] and game state */
658     {
659         int i, j;
660
661         for( i=0; i<=framePtr; i++ ) {
662             pvInfoList[i].depth = -1;
663             boards[i][EP_STATUS] = EP_NONE;
664             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
665         }
666     }
667
668     /*
669      * Parse timeControl resource
670      */
671     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672                           appData.movesPerSession)) {
673         char buf[MSG_SIZ];
674         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675         DisplayFatalError(buf, 0, 2);
676     }
677
678     /*
679      * Parse searchTime resource
680      */
681     if (*appData.searchTime != NULLCHAR) {
682         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
683         if (matched == 1) {
684             searchTime = min * 60;
685         } else if (matched == 2) {
686             searchTime = min * 60 + sec;
687         } else {
688             char buf[MSG_SIZ];
689             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690             DisplayFatalError(buf, 0, 2);
691         }
692     }
693
694     /* [AS] Adjudication threshold */
695     adjudicateLossThreshold = appData.adjudicateLossThreshold;
696     
697     first.which = "first";
698     second.which = "second";
699     first.maybeThinking = second.maybeThinking = FALSE;
700     first.pr = second.pr = NoProc;
701     first.isr = second.isr = NULL;
702     first.sendTime = second.sendTime = 2;
703     first.sendDrawOffers = 1;
704     if (appData.firstPlaysBlack) {
705         first.twoMachinesColor = "black\n";
706         second.twoMachinesColor = "white\n";
707     } else {
708         first.twoMachinesColor = "white\n";
709         second.twoMachinesColor = "black\n";
710     }
711     first.program = appData.firstChessProgram;
712     second.program = appData.secondChessProgram;
713     first.host = appData.firstHost;
714     second.host = appData.secondHost;
715     first.dir = appData.firstDirectory;
716     second.dir = appData.secondDirectory;
717     first.other = &second;
718     second.other = &first;
719     first.initString = appData.initString;
720     second.initString = appData.secondInitString;
721     first.computerString = appData.firstComputerString;
722     second.computerString = appData.secondComputerString;
723     first.useSigint = second.useSigint = TRUE;
724     first.useSigterm = second.useSigterm = TRUE;
725     first.reuse = appData.reuseFirst;
726     second.reuse = appData.reuseSecond;
727     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
728     second.nps = appData.secondNPS;
729     first.useSetboard = second.useSetboard = FALSE;
730     first.useSAN = second.useSAN = FALSE;
731     first.usePing = second.usePing = FALSE;
732     first.lastPing = second.lastPing = 0;
733     first.lastPong = second.lastPong = 0;
734     first.usePlayother = second.usePlayother = FALSE;
735     first.useColors = second.useColors = TRUE;
736     first.useUsermove = second.useUsermove = FALSE;
737     first.sendICS = second.sendICS = FALSE;
738     first.sendName = second.sendName = appData.icsActive;
739     first.sdKludge = second.sdKludge = FALSE;
740     first.stKludge = second.stKludge = FALSE;
741     TidyProgramName(first.program, first.host, first.tidy);
742     TidyProgramName(second.program, second.host, second.tidy);
743     first.matchWins = second.matchWins = 0;
744     strcpy(first.variants, appData.variant);
745     strcpy(second.variants, appData.variant);
746     first.analysisSupport = second.analysisSupport = 2; /* detect */
747     first.analyzing = second.analyzing = FALSE;
748     first.initDone = second.initDone = FALSE;
749
750     /* New features added by Tord: */
751     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753     /* End of new features added by Tord. */
754     first.fenOverride  = appData.fenOverride1;
755     second.fenOverride = appData.fenOverride2;
756
757     /* [HGM] time odds: set factor for each machine */
758     first.timeOdds  = appData.firstTimeOdds;
759     second.timeOdds = appData.secondTimeOdds;
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = first.timeOdds;
763             if(norm > second.timeOdds) norm = second.timeOdds;
764         }
765         first.timeOdds /= norm;
766         second.timeOdds /= norm;
767     }
768
769     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770     first.accumulateTC = appData.firstAccumulateTC;
771     second.accumulateTC = appData.secondAccumulateTC;
772     first.maxNrOfSessions = second.maxNrOfSessions = 1;
773
774     /* [HGM] debug */
775     first.debug = second.debug = FALSE;
776     first.supportsNPS = second.supportsNPS = UNKNOWN;
777
778     /* [HGM] options */
779     first.optionSettings  = appData.firstOptions;
780     second.optionSettings = appData.secondOptions;
781
782     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784     first.isUCI = appData.firstIsUCI; /* [AS] */
785     second.isUCI = appData.secondIsUCI; /* [AS] */
786     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
788
789     if (appData.firstProtocolVersion > PROTOVER ||
790         appData.firstProtocolVersion < 1) {
791       char buf[MSG_SIZ];
792       sprintf(buf, _("protocol version %d not supported"),
793               appData.firstProtocolVersion);
794       DisplayFatalError(buf, 0, 2);
795     } else {
796       first.protocolVersion = appData.firstProtocolVersion;
797     }
798
799     if (appData.secondProtocolVersion > PROTOVER ||
800         appData.secondProtocolVersion < 1) {
801       char buf[MSG_SIZ];
802       sprintf(buf, _("protocol version %d not supported"),
803               appData.secondProtocolVersion);
804       DisplayFatalError(buf, 0, 2);
805     } else {
806       second.protocolVersion = appData.secondProtocolVersion;
807     }
808
809     if (appData.icsActive) {
810         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
811 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813         appData.clockMode = FALSE;
814         first.sendTime = second.sendTime = 0;
815     }
816     
817 #if ZIPPY
818     /* Override some settings from environment variables, for backward
819        compatibility.  Unfortunately it's not feasible to have the env
820        vars just set defaults, at least in xboard.  Ugh.
821     */
822     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
823       ZippyInit();
824     }
825 #endif
826     
827     if (appData.noChessProgram) {
828         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829         sprintf(programVersion, "%s", PACKAGE_STRING);
830     } else {
831       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
834     }
835
836     if (!appData.icsActive) {
837       char buf[MSG_SIZ];
838       /* Check for variants that are supported only in ICS mode,
839          or not at all.  Some that are accepted here nevertheless
840          have bugs; see comments below.
841       */
842       VariantClass variant = StringToVariant(appData.variant);
843       switch (variant) {
844       case VariantBughouse:     /* need four players and two boards */
845       case VariantKriegspiel:   /* need to hide pieces and move details */
846       /* case VariantFischeRandom: (Fabien: moved below) */
847         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848         DisplayFatalError(buf, 0, 2);
849         return;
850
851       case VariantUnknown:
852       case VariantLoadable:
853       case Variant29:
854       case Variant30:
855       case Variant31:
856       case Variant32:
857       case Variant33:
858       case Variant34:
859       case Variant35:
860       case Variant36:
861       default:
862         sprintf(buf, _("Unknown variant name %s"), appData.variant);
863         DisplayFatalError(buf, 0, 2);
864         return;
865
866       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
867       case VariantFairy:      /* [HGM] TestLegality definitely off! */
868       case VariantGothic:     /* [HGM] should work */
869       case VariantCapablanca: /* [HGM] should work */
870       case VariantCourier:    /* [HGM] initial forced moves not implemented */
871       case VariantShogi:      /* [HGM] drops not tested for legality */
872       case VariantKnightmate: /* [HGM] should work */
873       case VariantCylinder:   /* [HGM] untested */
874       case VariantFalcon:     /* [HGM] untested */
875       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876                                  offboard interposition not understood */
877       case VariantNormal:     /* definitely works! */
878       case VariantWildCastle: /* pieces not automatically shuffled */
879       case VariantNoCastle:   /* pieces not automatically shuffled */
880       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881       case VariantLosers:     /* should work except for win condition,
882                                  and doesn't know captures are mandatory */
883       case VariantSuicide:    /* should work except for win condition,
884                                  and doesn't know captures are mandatory */
885       case VariantGiveaway:   /* should work except for win condition,
886                                  and doesn't know captures are mandatory */
887       case VariantTwoKings:   /* should work */
888       case VariantAtomic:     /* should work except for win condition */
889       case Variant3Check:     /* should work except for win condition */
890       case VariantShatranj:   /* should work except for all win conditions */
891       case VariantBerolina:   /* might work if TestLegality is off */
892       case VariantCapaRandom: /* should work */
893       case VariantJanus:      /* should work */
894       case VariantSuper:      /* experimental */
895       case VariantGreat:      /* experimental, requires legality testing to be off */
896         break;
897       }
898     }
899
900     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
901     InitEngineUCI( installDir, &second );
902 }
903
904 int NextIntegerFromString( char ** str, long * value )
905 {
906     int result = -1;
907     char * s = *str;
908
909     while( *s == ' ' || *s == '\t' ) {
910         s++;
911     }
912
913     *value = 0;
914
915     if( *s >= '0' && *s <= '9' ) {
916         while( *s >= '0' && *s <= '9' ) {
917             *value = *value * 10 + (*s - '0');
918             s++;
919         }
920
921         result = 0;
922     }
923
924     *str = s;
925
926     return result;
927 }
928
929 int NextTimeControlFromString( char ** str, long * value )
930 {
931     long temp;
932     int result = NextIntegerFromString( str, &temp );
933
934     if( result == 0 ) {
935         *value = temp * 60; /* Minutes */
936         if( **str == ':' ) {
937             (*str)++;
938             result = NextIntegerFromString( str, &temp );
939             *value += temp; /* Seconds */
940         }
941     }
942
943     return result;
944 }
945
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
948     int result = -1; long temp, temp2;
949
950     if(**str != '+') return -1; // old params remain in force!
951     (*str)++;
952     if( NextTimeControlFromString( str, &temp ) ) return -1;
953
954     if(**str != '/') {
955         /* time only: incremental or sudden-death time control */
956         if(**str == '+') { /* increment follows; read it */
957             (*str)++;
958             if(result = NextIntegerFromString( str, &temp2)) return -1;
959             *inc = temp2 * 1000;
960         } else *inc = 0;
961         *moves = 0; *tc = temp * 1000; 
962         return 0;
963     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
964
965     (*str)++; /* classical time control */
966     result = NextTimeControlFromString( str, &temp2);
967     if(result == 0) {
968         *moves = temp/60;
969         *tc    = temp2 * 1000;
970         *inc   = 0;
971     }
972     return result;
973 }
974
975 int GetTimeQuota(int movenr)
976 {   /* [HGM] get time to add from the multi-session time-control string */
977     int moves=1; /* kludge to force reading of first session */
978     long time, increment;
979     char *s = fullTimeControlString;
980
981     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
982     do {
983         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985         if(movenr == -1) return time;    /* last move before new session     */
986         if(!moves) return increment;     /* current session is incremental   */
987         if(movenr >= 0) movenr -= moves; /* we already finished this session */
988     } while(movenr >= -1);               /* try again for next session       */
989
990     return 0; // no new time quota on this move
991 }
992
993 int
994 ParseTimeControl(tc, ti, mps)
995      char *tc;
996      int ti;
997      int mps;
998 {
999   long tc1;
1000   long tc2;
1001   char buf[MSG_SIZ];
1002   
1003   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1004   if(ti > 0) {
1005     if(mps)
1006       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007     else sprintf(buf, "+%s+%d", tc, ti);
1008   } else {
1009     if(mps)
1010              sprintf(buf, "+%d/%s", mps, tc);
1011     else sprintf(buf, "+%s", tc);
1012   }
1013   fullTimeControlString = StrSave(buf);
1014   
1015   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1016     return FALSE;
1017   }
1018   
1019   if( *tc == '/' ) {
1020     /* Parse second time control */
1021     tc++;
1022     
1023     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1024       return FALSE;
1025     }
1026     
1027     if( tc2 == 0 ) {
1028       return FALSE;
1029     }
1030     
1031     timeControl_2 = tc2 * 1000;
1032   }
1033   else {
1034     timeControl_2 = 0;
1035   }
1036   
1037   if( tc1 == 0 ) {
1038     return FALSE;
1039   }
1040   
1041   timeControl = tc1 * 1000;
1042   
1043   if (ti >= 0) {
1044     timeIncrement = ti * 1000;  /* convert to ms */
1045     movesPerSession = 0;
1046   } else {
1047     timeIncrement = 0;
1048     movesPerSession = mps;
1049   }
1050   return TRUE;
1051 }
1052
1053 void
1054 InitBackEnd2()
1055 {
1056     if (appData.debugMode) {
1057         fprintf(debugFP, "%s\n", programVersion);
1058     }
1059
1060     set_cont_sequence(appData.wrapContSeq);
1061     if (appData.matchGames > 0) {
1062         appData.matchMode = TRUE;
1063     } else if (appData.matchMode) {
1064         appData.matchGames = 1;
1065     }
1066     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067         appData.matchGames = appData.sameColorGames;
1068     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1071     }
1072     Reset(TRUE, FALSE);
1073     if (appData.noChessProgram || first.protocolVersion == 1) {
1074       InitBackEnd3();
1075     } else {
1076       /* kludge: allow timeout for initial "feature" commands */
1077       FreezeUI();
1078       DisplayMessage("", _("Starting chess program"));
1079       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1080     }
1081 }
1082
1083 void
1084 InitBackEnd3 P((void))
1085 {
1086     GameMode initialMode;
1087     char buf[MSG_SIZ];
1088     int err;
1089
1090     InitChessProgram(&first, startedFromSetupPosition);
1091
1092
1093     if (appData.icsActive) {
1094 #ifdef WIN32
1095         /* [DM] Make a console window if needed [HGM] merged ifs */
1096         ConsoleCreate(); 
1097 #endif
1098         err = establish();
1099         if (err != 0) {
1100             if (*appData.icsCommPort != NULLCHAR) {
1101                 sprintf(buf, _("Could not open comm port %s"),  
1102                         appData.icsCommPort);
1103             } else {
1104                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1105                         appData.icsHost, appData.icsPort);
1106             }
1107             DisplayFatalError(buf, err, 1);
1108             return;
1109         }
1110         SetICSMode();
1111         telnetISR =
1112           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1113         fromUserISR =
1114           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115     } else if (appData.noChessProgram) {
1116         SetNCPMode();
1117     } else {
1118         SetGNUMode();
1119     }
1120
1121     if (*appData.cmailGameName != NULLCHAR) {
1122         SetCmailMode();
1123         OpenLoopback(&cmailPR);
1124         cmailISR =
1125           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126     }
1127     
1128     ThawUI();
1129     DisplayMessage("", "");
1130     if (StrCaseCmp(appData.initialMode, "") == 0) {
1131       initialMode = BeginningOfGame;
1132     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133       initialMode = TwoMachinesPlay;
1134     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135       initialMode = AnalyzeFile; 
1136     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137       initialMode = AnalyzeMode;
1138     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139       initialMode = MachinePlaysWhite;
1140     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141       initialMode = MachinePlaysBlack;
1142     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143       initialMode = EditGame;
1144     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145       initialMode = EditPosition;
1146     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147       initialMode = Training;
1148     } else {
1149       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150       DisplayFatalError(buf, 0, 2);
1151       return;
1152     }
1153
1154     if (appData.matchMode) {
1155         /* Set up machine vs. machine match */
1156         if (appData.noChessProgram) {
1157             DisplayFatalError(_("Can't have a match with no chess programs"),
1158                               0, 2);
1159             return;
1160         }
1161         matchMode = TRUE;
1162         matchGame = 1;
1163         if (*appData.loadGameFile != NULLCHAR) {
1164             int index = appData.loadGameIndex; // [HGM] autoinc
1165             if(index<0) lastIndex = index = 1;
1166             if (!LoadGameFromFile(appData.loadGameFile,
1167                                   index,
1168                                   appData.loadGameFile, FALSE)) {
1169                 DisplayFatalError(_("Bad game file"), 0, 1);
1170                 return;
1171             }
1172         } else if (*appData.loadPositionFile != NULLCHAR) {
1173             int index = appData.loadPositionIndex; // [HGM] autoinc
1174             if(index<0) lastIndex = index = 1;
1175             if (!LoadPositionFromFile(appData.loadPositionFile,
1176                                       index,
1177                                       appData.loadPositionFile)) {
1178                 DisplayFatalError(_("Bad position file"), 0, 1);
1179                 return;
1180             }
1181         }
1182         TwoMachinesEvent();
1183     } else if (*appData.cmailGameName != NULLCHAR) {
1184         /* Set up cmail mode */
1185         ReloadCmailMsgEvent(TRUE);
1186     } else {
1187         /* Set up other modes */
1188         if (initialMode == AnalyzeFile) {
1189           if (*appData.loadGameFile == NULLCHAR) {
1190             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191             return;
1192           }
1193         }
1194         if (*appData.loadGameFile != NULLCHAR) {
1195             (void) LoadGameFromFile(appData.loadGameFile,
1196                                     appData.loadGameIndex,
1197                                     appData.loadGameFile, TRUE);
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             (void) LoadPositionFromFile(appData.loadPositionFile,
1200                                         appData.loadPositionIndex,
1201                                         appData.loadPositionFile);
1202             /* [HGM] try to make self-starting even after FEN load */
1203             /* to allow automatic setup of fairy variants with wtm */
1204             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205                 gameMode = BeginningOfGame;
1206                 setboardSpoiledMachineBlack = 1;
1207             }
1208             /* [HGM] loadPos: make that every new game uses the setup */
1209             /* from file as long as we do not switch variant          */
1210             if(!blackPlaysFirst) {
1211                 startedFromPositionFile = TRUE;
1212                 CopyBoard(filePosition, boards[0]);
1213             }
1214         }
1215         if (initialMode == AnalyzeMode) {
1216           if (appData.noChessProgram) {
1217             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1218             return;
1219           }
1220           if (appData.icsActive) {
1221             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222             return;
1223           }
1224           AnalyzeModeEvent();
1225         } else if (initialMode == AnalyzeFile) {
1226           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227           ShowThinkingEvent();
1228           AnalyzeFileEvent();
1229           AnalysisPeriodicEvent(1);
1230         } else if (initialMode == MachinePlaysWhite) {
1231           if (appData.noChessProgram) {
1232             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233                               0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238                               0, 2);
1239             return;
1240           }
1241           MachineWhiteEvent();
1242         } else if (initialMode == MachinePlaysBlack) {
1243           if (appData.noChessProgram) {
1244             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245                               0, 2);
1246             return;
1247           }
1248           if (appData.icsActive) {
1249             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250                               0, 2);
1251             return;
1252           }
1253           MachineBlackEvent();
1254         } else if (initialMode == TwoMachinesPlay) {
1255           if (appData.noChessProgram) {
1256             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257                               0, 2);
1258             return;
1259           }
1260           if (appData.icsActive) {
1261             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1262                               0, 2);
1263             return;
1264           }
1265           TwoMachinesEvent();
1266         } else if (initialMode == EditGame) {
1267           EditGameEvent();
1268         } else if (initialMode == EditPosition) {
1269           EditPositionEvent();
1270         } else if (initialMode == Training) {
1271           if (*appData.loadGameFile == NULLCHAR) {
1272             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1273             return;
1274           }
1275           TrainingEvent();
1276         }
1277     }
1278 }
1279
1280 /*
1281  * Establish will establish a contact to a remote host.port.
1282  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283  *  used to talk to the host.
1284  * Returns 0 if okay, error code if not.
1285  */
1286 int
1287 establish()
1288 {
1289     char buf[MSG_SIZ];
1290
1291     if (*appData.icsCommPort != NULLCHAR) {
1292         /* Talk to the host through a serial comm port */
1293         return OpenCommPort(appData.icsCommPort, &icsPR);
1294
1295     } else if (*appData.gateway != NULLCHAR) {
1296         if (*appData.remoteShell == NULLCHAR) {
1297             /* Use the rcmd protocol to run telnet program on a gateway host */
1298             snprintf(buf, sizeof(buf), "%s %s %s",
1299                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1300             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1301
1302         } else {
1303             /* Use the rsh program to run telnet program on a gateway host */
1304             if (*appData.remoteUser == NULLCHAR) {
1305                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306                         appData.gateway, appData.telnetProgram,
1307                         appData.icsHost, appData.icsPort);
1308             } else {
1309                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310                         appData.remoteShell, appData.gateway, 
1311                         appData.remoteUser, appData.telnetProgram,
1312                         appData.icsHost, appData.icsPort);
1313             }
1314             return StartChildProcess(buf, "", &icsPR);
1315
1316         }
1317     } else if (appData.useTelnet) {
1318         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1319
1320     } else {
1321         /* TCP socket interface differs somewhat between
1322            Unix and NT; handle details in the front end.
1323            */
1324         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1325     }
1326 }
1327
1328 void
1329 show_bytes(fp, buf, count)
1330      FILE *fp;
1331      char *buf;
1332      int count;
1333 {
1334     while (count--) {
1335         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336             fprintf(fp, "\\%03o", *buf & 0xff);
1337         } else {
1338             putc(*buf, fp);
1339         }
1340         buf++;
1341     }
1342     fflush(fp);
1343 }
1344
1345 /* Returns an errno value */
1346 int
1347 OutputMaybeTelnet(pr, message, count, outError)
1348      ProcRef pr;
1349      char *message;
1350      int count;
1351      int *outError;
1352 {
1353     char buf[8192], *p, *q, *buflim;
1354     int left, newcount, outcount;
1355
1356     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357         *appData.gateway != NULLCHAR) {
1358         if (appData.debugMode) {
1359             fprintf(debugFP, ">ICS: ");
1360             show_bytes(debugFP, message, count);
1361             fprintf(debugFP, "\n");
1362         }
1363         return OutputToProcess(pr, message, count, outError);
1364     }
1365
1366     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1367     p = message;
1368     q = buf;
1369     left = count;
1370     newcount = 0;
1371     while (left) {
1372         if (q >= buflim) {
1373             if (appData.debugMode) {
1374                 fprintf(debugFP, ">ICS: ");
1375                 show_bytes(debugFP, buf, newcount);
1376                 fprintf(debugFP, "\n");
1377             }
1378             outcount = OutputToProcess(pr, buf, newcount, outError);
1379             if (outcount < newcount) return -1; /* to be sure */
1380             q = buf;
1381             newcount = 0;
1382         }
1383         if (*p == '\n') {
1384             *q++ = '\r';
1385             newcount++;
1386         } else if (((unsigned char) *p) == TN_IAC) {
1387             *q++ = (char) TN_IAC;
1388             newcount ++;
1389         }
1390         *q++ = *p++;
1391         newcount++;
1392         left--;
1393     }
1394     if (appData.debugMode) {
1395         fprintf(debugFP, ">ICS: ");
1396         show_bytes(debugFP, buf, newcount);
1397         fprintf(debugFP, "\n");
1398     }
1399     outcount = OutputToProcess(pr, buf, newcount, outError);
1400     if (outcount < newcount) return -1; /* to be sure */
1401     return count;
1402 }
1403
1404 void
1405 read_from_player(isr, closure, message, count, error)
1406      InputSourceRef isr;
1407      VOIDSTAR closure;
1408      char *message;
1409      int count;
1410      int error;
1411 {
1412     int outError, outCount;
1413     static int gotEof = 0;
1414
1415     /* Pass data read from player on to ICS */
1416     if (count > 0) {
1417         gotEof = 0;
1418         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419         if (outCount < count) {
1420             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1421         }
1422     } else if (count < 0) {
1423         RemoveInputSource(isr);
1424         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425     } else if (gotEof++ > 0) {
1426         RemoveInputSource(isr);
1427         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1428     }
1429 }
1430
1431 void
1432 KeepAlive()
1433 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434     SendToICS("date\n");
1435     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1436 }
1437
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1440 {
1441     char buffer[MSG_SIZ];
1442     va_list args;
1443
1444     va_start(args, format);
1445     vsnprintf(buffer, sizeof(buffer), format, args);
1446     buffer[sizeof(buffer)-1] = '\0';
1447     SendToICS(buffer);
1448     va_end(args);
1449 }
1450
1451 void
1452 SendToICS(s)
1453      char *s;
1454 {
1455     int count, outCount, outError;
1456
1457     if (icsPR == NULL) return;
1458
1459     count = strlen(s);
1460     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461     if (outCount < count) {
1462         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463     }
1464 }
1465
1466 /* This is used for sending logon scripts to the ICS. Sending
1467    without a delay causes problems when using timestamp on ICC
1468    (at least on my machine). */
1469 void
1470 SendToICSDelayed(s,msdelay)
1471      char *s;
1472      long msdelay;
1473 {
1474     int count, outCount, outError;
1475
1476     if (icsPR == NULL) return;
1477
1478     count = strlen(s);
1479     if (appData.debugMode) {
1480         fprintf(debugFP, ">ICS: ");
1481         show_bytes(debugFP, s, count);
1482         fprintf(debugFP, "\n");
1483     }
1484     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1485                                       msdelay);
1486     if (outCount < count) {
1487         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1488     }
1489 }
1490
1491
1492 /* Remove all highlighting escape sequences in s
1493    Also deletes any suffix starting with '(' 
1494    */
1495 char *
1496 StripHighlightAndTitle(s)
1497      char *s;
1498 {
1499     static char retbuf[MSG_SIZ];
1500     char *p = retbuf;
1501
1502     while (*s != NULLCHAR) {
1503         while (*s == '\033') {
1504             while (*s != NULLCHAR && !isalpha(*s)) s++;
1505             if (*s != NULLCHAR) s++;
1506         }
1507         while (*s != NULLCHAR && *s != '\033') {
1508             if (*s == '(' || *s == '[') {
1509                 *p = NULLCHAR;
1510                 return retbuf;
1511             }
1512             *p++ = *s++;
1513         }
1514     }
1515     *p = NULLCHAR;
1516     return retbuf;
1517 }
1518
1519 /* Remove all highlighting escape sequences in s */
1520 char *
1521 StripHighlight(s)
1522      char *s;
1523 {
1524     static char retbuf[MSG_SIZ];
1525     char *p = retbuf;
1526
1527     while (*s != NULLCHAR) {
1528         while (*s == '\033') {
1529             while (*s != NULLCHAR && !isalpha(*s)) s++;
1530             if (*s != NULLCHAR) s++;
1531         }
1532         while (*s != NULLCHAR && *s != '\033') {
1533             *p++ = *s++;
1534         }
1535     }
1536     *p = NULLCHAR;
1537     return retbuf;
1538 }
1539
1540 char *variantNames[] = VARIANT_NAMES;
1541 char *
1542 VariantName(v)
1543      VariantClass v;
1544 {
1545     return variantNames[v];
1546 }
1547
1548
1549 /* Identify a variant from the strings the chess servers use or the
1550    PGN Variant tag names we use. */
1551 VariantClass
1552 StringToVariant(e)
1553      char *e;
1554 {
1555     char *p;
1556     int wnum = -1;
1557     VariantClass v = VariantNormal;
1558     int i, found = FALSE;
1559     char buf[MSG_SIZ];
1560
1561     if (!e) return v;
1562
1563     /* [HGM] skip over optional board-size prefixes */
1564     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566         while( *e++ != '_');
1567     }
1568
1569     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570         v = VariantNormal;
1571         found = TRUE;
1572     } else
1573     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574       if (StrCaseStr(e, variantNames[i])) {
1575         v = (VariantClass) i;
1576         found = TRUE;
1577         break;
1578       }
1579     }
1580
1581     if (!found) {
1582       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583           || StrCaseStr(e, "wild/fr") 
1584           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585         v = VariantFischeRandom;
1586       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587                  (i = 1, p = StrCaseStr(e, "w"))) {
1588         p += i;
1589         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1590         if (isdigit(*p)) {
1591           wnum = atoi(p);
1592         } else {
1593           wnum = -1;
1594         }
1595         switch (wnum) {
1596         case 0: /* FICS only, actually */
1597         case 1:
1598           /* Castling legal even if K starts on d-file */
1599           v = VariantWildCastle;
1600           break;
1601         case 2:
1602         case 3:
1603         case 4:
1604           /* Castling illegal even if K & R happen to start in
1605              normal positions. */
1606           v = VariantNoCastle;
1607           break;
1608         case 5:
1609         case 7:
1610         case 8:
1611         case 10:
1612         case 11:
1613         case 12:
1614         case 13:
1615         case 14:
1616         case 15:
1617         case 18:
1618         case 19:
1619           /* Castling legal iff K & R start in normal positions */
1620           v = VariantNormal;
1621           break;
1622         case 6:
1623         case 20:
1624         case 21:
1625           /* Special wilds for position setup; unclear what to do here */
1626           v = VariantLoadable;
1627           break;
1628         case 9:
1629           /* Bizarre ICC game */
1630           v = VariantTwoKings;
1631           break;
1632         case 16:
1633           v = VariantKriegspiel;
1634           break;
1635         case 17:
1636           v = VariantLosers;
1637           break;
1638         case 22:
1639           v = VariantFischeRandom;
1640           break;
1641         case 23:
1642           v = VariantCrazyhouse;
1643           break;
1644         case 24:
1645           v = VariantBughouse;
1646           break;
1647         case 25:
1648           v = Variant3Check;
1649           break;
1650         case 26:
1651           /* Not quite the same as FICS suicide! */
1652           v = VariantGiveaway;
1653           break;
1654         case 27:
1655           v = VariantAtomic;
1656           break;
1657         case 28:
1658           v = VariantShatranj;
1659           break;
1660
1661         /* Temporary names for future ICC types.  The name *will* change in 
1662            the next xboard/WinBoard release after ICC defines it. */
1663         case 29:
1664           v = Variant29;
1665           break;
1666         case 30:
1667           v = Variant30;
1668           break;
1669         case 31:
1670           v = Variant31;
1671           break;
1672         case 32:
1673           v = Variant32;
1674           break;
1675         case 33:
1676           v = Variant33;
1677           break;
1678         case 34:
1679           v = Variant34;
1680           break;
1681         case 35:
1682           v = Variant35;
1683           break;
1684         case 36:
1685           v = Variant36;
1686           break;
1687         case 37:
1688           v = VariantShogi;
1689           break;
1690         case 38:
1691           v = VariantXiangqi;
1692           break;
1693         case 39:
1694           v = VariantCourier;
1695           break;
1696         case 40:
1697           v = VariantGothic;
1698           break;
1699         case 41:
1700           v = VariantCapablanca;
1701           break;
1702         case 42:
1703           v = VariantKnightmate;
1704           break;
1705         case 43:
1706           v = VariantFairy;
1707           break;
1708         case 44:
1709           v = VariantCylinder;
1710           break;
1711         case 45:
1712           v = VariantFalcon;
1713           break;
1714         case 46:
1715           v = VariantCapaRandom;
1716           break;
1717         case 47:
1718           v = VariantBerolina;
1719           break;
1720         case 48:
1721           v = VariantJanus;
1722           break;
1723         case 49:
1724           v = VariantSuper;
1725           break;
1726         case 50:
1727           v = VariantGreat;
1728           break;
1729         case -1:
1730           /* Found "wild" or "w" in the string but no number;
1731              must assume it's normal chess. */
1732           v = VariantNormal;
1733           break;
1734         default:
1735           sprintf(buf, _("Unknown wild type %d"), wnum);
1736           DisplayError(buf, 0);
1737           v = VariantUnknown;
1738           break;
1739         }
1740       }
1741     }
1742     if (appData.debugMode) {
1743       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744               e, wnum, VariantName(v));
1745     }
1746     return v;
1747 }
1748
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1751
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753    advance *index beyond it, and set leftover_start to the new value of
1754    *index; else return FALSE.  If pattern contains the character '*', it
1755    matches any sequence of characters not containing '\r', '\n', or the
1756    character following the '*' (if any), and the matched sequence(s) are
1757    copied into star_match.
1758    */
1759 int
1760 looking_at(buf, index, pattern)
1761      char *buf;
1762      int *index;
1763      char *pattern;
1764 {
1765     char *bufp = &buf[*index], *patternp = pattern;
1766     int star_count = 0;
1767     char *matchp = star_match[0];
1768     
1769     for (;;) {
1770         if (*patternp == NULLCHAR) {
1771             *index = leftover_start = bufp - buf;
1772             *matchp = NULLCHAR;
1773             return TRUE;
1774         }
1775         if (*bufp == NULLCHAR) return FALSE;
1776         if (*patternp == '*') {
1777             if (*bufp == *(patternp + 1)) {
1778                 *matchp = NULLCHAR;
1779                 matchp = star_match[++star_count];
1780                 patternp += 2;
1781                 bufp++;
1782                 continue;
1783             } else if (*bufp == '\n' || *bufp == '\r') {
1784                 patternp++;
1785                 if (*patternp == NULLCHAR)
1786                   continue;
1787                 else
1788                   return FALSE;
1789             } else {
1790                 *matchp++ = *bufp++;
1791                 continue;
1792             }
1793         }
1794         if (*patternp != *bufp) return FALSE;
1795         patternp++;
1796         bufp++;
1797     }
1798 }
1799
1800 void
1801 SendToPlayer(data, length)
1802      char *data;
1803      int length;
1804 {
1805     int error, outCount;
1806     outCount = OutputToProcess(NoProc, data, length, &error);
1807     if (outCount < length) {
1808         DisplayFatalError(_("Error writing to display"), error, 1);
1809     }
1810 }
1811
1812 void
1813 PackHolding(packed, holding)
1814      char packed[];
1815      char *holding;
1816 {
1817     char *p = holding;
1818     char *q = packed;
1819     int runlength = 0;
1820     int curr = 9999;
1821     do {
1822         if (*p == curr) {
1823             runlength++;
1824         } else {
1825             switch (runlength) {
1826               case 0:
1827                 break;
1828               case 1:
1829                 *q++ = curr;
1830                 break;
1831               case 2:
1832                 *q++ = curr;
1833                 *q++ = curr;
1834                 break;
1835               default:
1836                 sprintf(q, "%d", runlength);
1837                 while (*q) q++;
1838                 *q++ = curr;
1839                 break;
1840             }
1841             runlength = 1;
1842             curr = *p;
1843         }
1844     } while (*p++);
1845     *q = NULLCHAR;
1846 }
1847
1848 /* Telnet protocol requests from the front end */
1849 void
1850 TelnetRequest(ddww, option)
1851      unsigned char ddww, option;
1852 {
1853     unsigned char msg[3];
1854     int outCount, outError;
1855
1856     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857
1858     if (appData.debugMode) {
1859         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1860         switch (ddww) {
1861           case TN_DO:
1862             ddwwStr = "DO";
1863             break;
1864           case TN_DONT:
1865             ddwwStr = "DONT";
1866             break;
1867           case TN_WILL:
1868             ddwwStr = "WILL";
1869             break;
1870           case TN_WONT:
1871             ddwwStr = "WONT";
1872             break;
1873           default:
1874             ddwwStr = buf1;
1875             sprintf(buf1, "%d", ddww);
1876             break;
1877         }
1878         switch (option) {
1879           case TN_ECHO:
1880             optionStr = "ECHO";
1881             break;
1882           default:
1883             optionStr = buf2;
1884             sprintf(buf2, "%d", option);
1885             break;
1886         }
1887         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888     }
1889     msg[0] = TN_IAC;
1890     msg[1] = ddww;
1891     msg[2] = option;
1892     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893     if (outCount < 3) {
1894         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1895     }
1896 }
1897
1898 void
1899 DoEcho()
1900 {
1901     if (!appData.icsActive) return;
1902     TelnetRequest(TN_DO, TN_ECHO);
1903 }
1904
1905 void
1906 DontEcho()
1907 {
1908     if (!appData.icsActive) return;
1909     TelnetRequest(TN_DONT, TN_ECHO);
1910 }
1911
1912 void
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 {
1915     /* put the holdings sent to us by the server on the board holdings area */
1916     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917     char p;
1918     ChessSquare piece;
1919
1920     if(gameInfo.holdingsWidth < 2)  return;
1921     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922         return; // prevent overwriting by pre-board holdings
1923
1924     if( (int)lowestPiece >= BlackPawn ) {
1925         holdingsColumn = 0;
1926         countsColumn = 1;
1927         holdingsStartRow = BOARD_HEIGHT-1;
1928         direction = -1;
1929     } else {
1930         holdingsColumn = BOARD_WIDTH-1;
1931         countsColumn = BOARD_WIDTH-2;
1932         holdingsStartRow = 0;
1933         direction = 1;
1934     }
1935
1936     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937         board[i][holdingsColumn] = EmptySquare;
1938         board[i][countsColumn]   = (ChessSquare) 0;
1939     }
1940     while( (p=*holdings++) != NULLCHAR ) {
1941         piece = CharToPiece( ToUpper(p) );
1942         if(piece == EmptySquare) continue;
1943         /*j = (int) piece - (int) WhitePawn;*/
1944         j = PieceToNumber(piece);
1945         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946         if(j < 0) continue;               /* should not happen */
1947         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949         board[holdingsStartRow+j*direction][countsColumn]++;
1950     }
1951 }
1952
1953
1954 void
1955 VariantSwitch(Board board, VariantClass newVariant)
1956 {
1957    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1958    Board oldBoard;
1959
1960    startedFromPositionFile = FALSE;
1961    if(gameInfo.variant == newVariant) return;
1962
1963    /* [HGM] This routine is called each time an assignment is made to
1964     * gameInfo.variant during a game, to make sure the board sizes
1965     * are set to match the new variant. If that means adding or deleting
1966     * holdings, we shift the playing board accordingly
1967     * This kludge is needed because in ICS observe mode, we get boards
1968     * of an ongoing game without knowing the variant, and learn about the
1969     * latter only later. This can be because of the move list we requested,
1970     * in which case the game history is refilled from the beginning anyway,
1971     * but also when receiving holdings of a crazyhouse game. In the latter
1972     * case we want to add those holdings to the already received position.
1973     */
1974
1975    
1976    if (appData.debugMode) {
1977      fprintf(debugFP, "Switch board from %s to %s\n",
1978              VariantName(gameInfo.variant), VariantName(newVariant));
1979      setbuf(debugFP, NULL);
1980    }
1981    shuffleOpenings = 0;       /* [HGM] shuffle */
1982    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983    switch(newVariant) 
1984      {
1985      case VariantShogi:
1986        newWidth = 9;  newHeight = 9;
1987        gameInfo.holdingsSize = 7;
1988      case VariantBughouse:
1989      case VariantCrazyhouse:
1990        newHoldingsWidth = 2; break;
1991      case VariantGreat:
1992        newWidth = 10;
1993      case VariantSuper:
1994        newHoldingsWidth = 2;
1995        gameInfo.holdingsSize = 8;
1996        break;
1997      case VariantGothic:
1998      case VariantCapablanca:
1999      case VariantCapaRandom:
2000        newWidth = 10;
2001      default:
2002        newHoldingsWidth = gameInfo.holdingsSize = 0;
2003      };
2004    
2005    if(newWidth  != gameInfo.boardWidth  ||
2006       newHeight != gameInfo.boardHeight ||
2007       newHoldingsWidth != gameInfo.holdingsWidth ) {
2008      
2009      /* shift position to new playing area, if needed */
2010      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011        for(i=0; i<BOARD_HEIGHT; i++) 
2012          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014              board[i][j];
2015        for(i=0; i<newHeight; i++) {
2016          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018        }
2019      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020        for(i=0; i<BOARD_HEIGHT; i++)
2021          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2023              board[i][j];
2024      }
2025      gameInfo.boardWidth  = newWidth;
2026      gameInfo.boardHeight = newHeight;
2027      gameInfo.holdingsWidth = newHoldingsWidth;
2028      gameInfo.variant = newVariant;
2029      InitDrawingSizes(-2, 0);
2030    } else gameInfo.variant = newVariant;
2031    CopyBoard(oldBoard, board);   // remember correctly formatted board
2032      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2033    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2034 }
2035
2036 static int loggedOn = FALSE;
2037
2038 /*-- Game start info cache: --*/
2039 int gs_gamenum;
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\   ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2047
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2050
2051 void
2052 read_from_ics(isr, closure, data, count, error)
2053      InputSourceRef isr;
2054      VOIDSTAR closure;
2055      char *data;
2056      int count;
2057      int error;
2058 {
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2068     
2069     static int started = STARTED_NONE;
2070     static char parse[20000];
2071     static int parse_pos = 0;
2072     static char buf[BUF_SIZE + 1];
2073     static int firstTime = TRUE, intfSet = FALSE;
2074     static ColorClass prevColor = ColorNormal;
2075     static int savingComment = FALSE;
2076     static int cmatch = 0; // continuation sequence match
2077     char *bp;
2078     char str[500];
2079     int i, oldi;
2080     int buf_len;
2081     int next_out;
2082     int tkind;
2083     int backup;    /* [DM] For zippy color lines */
2084     char *p;
2085     char talker[MSG_SIZ]; // [HGM] chat
2086     int channel;
2087
2088     if (appData.debugMode) {
2089       if (!error) {
2090         fprintf(debugFP, "<ICS: ");
2091         show_bytes(debugFP, data, count);
2092         fprintf(debugFP, "\n");
2093       }
2094     }
2095
2096     if (appData.debugMode) { int f = forwardMostMove;
2097         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2100     }
2101     if (count > 0) {
2102         /* If last read ended with a partial line that we couldn't parse,
2103            prepend it to the new read and try again. */
2104         if (leftover_len > 0) {
2105             for (i=0; i<leftover_len; i++)
2106               buf[i] = buf[leftover_start + i];
2107         }
2108
2109     /* copy new characters into the buffer */
2110     bp = buf + leftover_len;
2111     buf_len=leftover_len;
2112     for (i=0; i<count; i++)
2113     {
2114         // ignore these
2115         if (data[i] == '\r')
2116             continue;
2117
2118         // join lines split by ICS?
2119         if (!appData.noJoin)
2120         {
2121             /*
2122                 Joining just consists of finding matches against the
2123                 continuation sequence, and discarding that sequence
2124                 if found instead of copying it.  So, until a match
2125                 fails, there's nothing to do since it might be the
2126                 complete sequence, and thus, something we don't want
2127                 copied.
2128             */
2129             if (data[i] == cont_seq[cmatch])
2130             {
2131                 cmatch++;
2132                 if (cmatch == strlen(cont_seq))
2133                 {
2134                     cmatch = 0; // complete match.  just reset the counter
2135
2136                     /*
2137                         it's possible for the ICS to not include the space
2138                         at the end of the last word, making our [correct]
2139                         join operation fuse two separate words.  the server
2140                         does this when the space occurs at the width setting.
2141                     */
2142                     if (!buf_len || buf[buf_len-1] != ' ')
2143                     {
2144                         *bp++ = ' ';
2145                         buf_len++;
2146                     }
2147                 }
2148                 continue;
2149             }
2150             else if (cmatch)
2151             {
2152                 /*
2153                     match failed, so we have to copy what matched before
2154                     falling through and copying this character.  In reality,
2155                     this will only ever be just the newline character, but
2156                     it doesn't hurt to be precise.
2157                 */
2158                 strncpy(bp, cont_seq, cmatch);
2159                 bp += cmatch;
2160                 buf_len += cmatch;
2161                 cmatch = 0;
2162             }
2163         }
2164
2165         // copy this char
2166         *bp++ = data[i];
2167         buf_len++;
2168     }
2169
2170         buf[buf_len] = NULLCHAR;
2171 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2172         next_out = 0;
2173         leftover_start = 0;
2174         
2175         i = 0;
2176         while (i < buf_len) {
2177             /* Deal with part of the TELNET option negotiation
2178                protocol.  We refuse to do anything beyond the
2179                defaults, except that we allow the WILL ECHO option,
2180                which ICS uses to turn off password echoing when we are
2181                directly connected to it.  We reject this option
2182                if localLineEditing mode is on (always on in xboard)
2183                and we are talking to port 23, which might be a real
2184                telnet server that will try to keep WILL ECHO on permanently.
2185              */
2186             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2187                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2188                 unsigned char option;
2189                 oldi = i;
2190                 switch ((unsigned char) buf[++i]) {
2191                   case TN_WILL:
2192                     if (appData.debugMode)
2193                       fprintf(debugFP, "\n<WILL ");
2194                     switch (option = (unsigned char) buf[++i]) {
2195                       case TN_ECHO:
2196                         if (appData.debugMode)
2197                           fprintf(debugFP, "ECHO ");
2198                         /* Reply only if this is a change, according
2199                            to the protocol rules. */
2200                         if (remoteEchoOption) break;
2201                         if (appData.localLineEditing &&
2202                             atoi(appData.icsPort) == TN_PORT) {
2203                             TelnetRequest(TN_DONT, TN_ECHO);
2204                         } else {
2205                             EchoOff();
2206                             TelnetRequest(TN_DO, TN_ECHO);
2207                             remoteEchoOption = TRUE;
2208                         }
2209                         break;
2210                       default:
2211                         if (appData.debugMode)
2212                           fprintf(debugFP, "%d ", option);
2213                         /* Whatever this is, we don't want it. */
2214                         TelnetRequest(TN_DONT, option);
2215                         break;
2216                     }
2217                     break;
2218                   case TN_WONT:
2219                     if (appData.debugMode)
2220                       fprintf(debugFP, "\n<WONT ");
2221                     switch (option = (unsigned char) buf[++i]) {
2222                       case TN_ECHO:
2223                         if (appData.debugMode)
2224                           fprintf(debugFP, "ECHO ");
2225                         /* Reply only if this is a change, according
2226                            to the protocol rules. */
2227                         if (!remoteEchoOption) break;
2228                         EchoOn();
2229                         TelnetRequest(TN_DONT, TN_ECHO);
2230                         remoteEchoOption = FALSE;
2231                         break;
2232                       default:
2233                         if (appData.debugMode)
2234                           fprintf(debugFP, "%d ", (unsigned char) option);
2235                         /* Whatever this is, it must already be turned
2236                            off, because we never agree to turn on
2237                            anything non-default, so according to the
2238                            protocol rules, we don't reply. */
2239                         break;
2240                     }
2241                     break;
2242                   case TN_DO:
2243                     if (appData.debugMode)
2244                       fprintf(debugFP, "\n<DO ");
2245                     switch (option = (unsigned char) buf[++i]) {
2246                       default:
2247                         /* Whatever this is, we refuse to do it. */
2248                         if (appData.debugMode)
2249                           fprintf(debugFP, "%d ", option);
2250                         TelnetRequest(TN_WONT, option);
2251                         break;
2252                     }
2253                     break;
2254                   case TN_DONT:
2255                     if (appData.debugMode)
2256                       fprintf(debugFP, "\n<DONT ");
2257                     switch (option = (unsigned char) buf[++i]) {
2258                       default:
2259                         if (appData.debugMode)
2260                           fprintf(debugFP, "%d ", option);
2261                         /* Whatever this is, we are already not doing
2262                            it, because we never agree to do anything
2263                            non-default, so according to the protocol
2264                            rules, we don't reply. */
2265                         break;
2266                     }
2267                     break;
2268                   case TN_IAC:
2269                     if (appData.debugMode)
2270                       fprintf(debugFP, "\n<IAC ");
2271                     /* Doubled IAC; pass it through */
2272                     i--;
2273                     break;
2274                   default:
2275                     if (appData.debugMode)
2276                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2277                     /* Drop all other telnet commands on the floor */
2278                     break;
2279                 }
2280                 if (oldi > next_out)
2281                   SendToPlayer(&buf[next_out], oldi - next_out);
2282                 if (++i > next_out)
2283                   next_out = i;
2284                 continue;
2285             }
2286                 
2287             /* OK, this at least will *usually* work */
2288             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2289                 loggedOn = TRUE;
2290             }
2291             
2292             if (loggedOn && !intfSet) {
2293                 if (ics_type == ICS_ICC) {
2294                   sprintf(str,
2295                           "/set-quietly interface %s\n/set-quietly style 12\n",
2296                           programVersion);
2297                 } else if (ics_type == ICS_CHESSNET) {
2298                   sprintf(str, "/style 12\n");
2299                 } else {
2300                   strcpy(str, "alias $ @\n$set interface ");
2301                   strcat(str, programVersion);
2302                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2303 #ifdef WIN32
2304                   strcat(str, "$iset nohighlight 1\n");
2305 #endif
2306                   strcat(str, "$iset lock 1\n$style 12\n");
2307                 }
2308                 SendToICS(str);
2309                 NotifyFrontendLogin();
2310                 intfSet = TRUE;
2311             }
2312
2313             if (started == STARTED_COMMENT) {
2314                 /* Accumulate characters in comment */
2315                 parse[parse_pos++] = buf[i];
2316                 if (buf[i] == '\n') {
2317                     parse[parse_pos] = NULLCHAR;
2318                     if(chattingPartner>=0) {
2319                         char mess[MSG_SIZ];
2320                         sprintf(mess, "%s%s", talker, parse);
2321                         OutputChatMessage(chattingPartner, mess);
2322                         chattingPartner = -1;
2323                     } else
2324                     if(!suppressKibitz) // [HGM] kibitz
2325                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2326                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2327                         int nrDigit = 0, nrAlph = 0, j;
2328                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2329                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2330                         parse[parse_pos] = NULLCHAR;
2331                         // try to be smart: if it does not look like search info, it should go to
2332                         // ICS interaction window after all, not to engine-output window.
2333                         for(j=0; j<parse_pos; j++) { // count letters and digits
2334                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2335                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2336                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2337                         }
2338                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2339                             int depth=0; float score;
2340                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2341                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2342                                 pvInfoList[forwardMostMove-1].depth = depth;
2343                                 pvInfoList[forwardMostMove-1].score = 100*score;
2344                             }
2345                             OutputKibitz(suppressKibitz, parse);
2346                             next_out = i+1; // [HGM] suppress printing in ICS window
2347                         } else {
2348                             char tmp[MSG_SIZ];
2349                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2350                             SendToPlayer(tmp, strlen(tmp));
2351                         }
2352                     }
2353                     started = STARTED_NONE;
2354                 } else {
2355                     /* Don't match patterns against characters in comment */
2356                     i++;
2357                     continue;
2358                 }
2359             }
2360             if (started == STARTED_CHATTER) {
2361                 if (buf[i] != '\n') {
2362                     /* Don't match patterns against characters in chatter */
2363                     i++;
2364                     continue;
2365                 }
2366                 started = STARTED_NONE;
2367             }
2368
2369             /* Kludge to deal with rcmd protocol */
2370             if (firstTime && looking_at(buf, &i, "\001*")) {
2371                 DisplayFatalError(&buf[1], 0, 1);
2372                 continue;
2373             } else {
2374                 firstTime = FALSE;
2375             }
2376
2377             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2378                 ics_type = ICS_ICC;
2379                 ics_prefix = "/";
2380                 if (appData.debugMode)
2381                   fprintf(debugFP, "ics_type %d\n", ics_type);
2382                 continue;
2383             }
2384             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2385                 ics_type = ICS_FICS;
2386                 ics_prefix = "$";
2387                 if (appData.debugMode)
2388                   fprintf(debugFP, "ics_type %d\n", ics_type);
2389                 continue;
2390             }
2391             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2392                 ics_type = ICS_CHESSNET;
2393                 ics_prefix = "/";
2394                 if (appData.debugMode)
2395                   fprintf(debugFP, "ics_type %d\n", ics_type);
2396                 continue;
2397             }
2398
2399             if (!loggedOn &&
2400                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2401                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2402                  looking_at(buf, &i, "will be \"*\""))) {
2403               strcpy(ics_handle, star_match[0]);
2404               continue;
2405             }
2406
2407             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2408               char buf[MSG_SIZ];
2409               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2410               DisplayIcsInteractionTitle(buf);
2411               have_set_title = TRUE;
2412             }
2413
2414             /* skip finger notes */
2415             if (started == STARTED_NONE &&
2416                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2417                  (buf[i] == '1' && buf[i+1] == '0')) &&
2418                 buf[i+2] == ':' && buf[i+3] == ' ') {
2419               started = STARTED_CHATTER;
2420               i += 3;
2421               continue;
2422             }
2423
2424             /* skip formula vars */
2425             if (started == STARTED_NONE &&
2426                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2427               started = STARTED_CHATTER;
2428               i += 3;
2429               continue;
2430             }
2431
2432             oldi = i;
2433             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2434             if (appData.autoKibitz && started == STARTED_NONE && 
2435                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2436                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2437                 if(looking_at(buf, &i, "* kibitzes: ") &&
2438                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2439                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2440                         suppressKibitz = TRUE;
2441                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2442                                 && (gameMode == IcsPlayingWhite)) ||
2443                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2444                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2445                             started = STARTED_CHATTER; // own kibitz we simply discard
2446                         else {
2447                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2448                             parse_pos = 0; parse[0] = NULLCHAR;
2449                             savingComment = TRUE;
2450                             suppressKibitz = gameMode != IcsObserving ? 2 :
2451                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2452                         } 
2453                         continue;
2454                 } else
2455                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2456                     // suppress the acknowledgements of our own autoKibitz
2457                     SendToPlayer(star_match[0], strlen(star_match[0]));
2458                     looking_at(buf, &i, "*% "); // eat prompt
2459                     next_out = i;
2460                 }
2461             } // [HGM] kibitz: end of patch
2462
2463 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2464
2465             // [HGM] chat: intercept tells by users for which we have an open chat window
2466             channel = -1;
2467             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2468                                            looking_at(buf, &i, "* whispers:") ||
2469                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2470                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2471                 int p;
2472                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2473                 chattingPartner = -1;
2474
2475                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2476                 for(p=0; p<MAX_CHAT; p++) {
2477                     if(channel == atoi(chatPartner[p])) {
2478                     talker[0] = '['; strcat(talker, "]");
2479                     chattingPartner = p; break;
2480                     }
2481                 } else
2482                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2483                 for(p=0; p<MAX_CHAT; p++) {
2484                     if(!strcmp("WHISPER", chatPartner[p])) {
2485                         talker[0] = '['; strcat(talker, "]");
2486                         chattingPartner = p; break;
2487                     }
2488                 }
2489                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2490                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2491                     talker[0] = 0;
2492                     chattingPartner = p; break;
2493                 }
2494                 if(chattingPartner<0) i = oldi; else {
2495                     started = STARTED_COMMENT;
2496                     parse_pos = 0; parse[0] = NULLCHAR;
2497                     savingComment = TRUE;
2498                     suppressKibitz = TRUE;
2499                 }
2500             } // [HGM] chat: end of patch
2501
2502             if (appData.zippyTalk || appData.zippyPlay) {
2503                 /* [DM] Backup address for color zippy lines */
2504                 backup = i;
2505 #if ZIPPY
2506        #ifdef WIN32
2507                if (loggedOn == TRUE)
2508                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2509                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2510        #else
2511                 if (ZippyControl(buf, &i) ||
2512                     ZippyConverse(buf, &i) ||
2513                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2514                       loggedOn = TRUE;
2515                       if (!appData.colorize) continue;
2516                 }
2517        #endif
2518 #endif
2519             } // [DM] 'else { ' deleted
2520                 if (
2521                     /* Regular tells and says */
2522                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2523                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2524                     looking_at(buf, &i, "* says: ") ||
2525                     /* Don't color "message" or "messages" output */
2526                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2527                     looking_at(buf, &i, "*. * at *:*: ") ||
2528                     looking_at(buf, &i, "--* (*:*): ") ||
2529                     /* Message notifications (same color as tells) */
2530                     looking_at(buf, &i, "* has left a message ") ||
2531                     looking_at(buf, &i, "* just sent you a message:\n") ||
2532                     /* Whispers and kibitzes */
2533                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2534                     looking_at(buf, &i, "* kibitzes: ") ||
2535                     /* Channel tells */
2536                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2537
2538                   if (tkind == 1 && strchr(star_match[0], ':')) {
2539                       /* Avoid "tells you:" spoofs in channels */
2540                      tkind = 3;
2541                   }
2542                   if (star_match[0][0] == NULLCHAR ||
2543                       strchr(star_match[0], ' ') ||
2544                       (tkind == 3 && strchr(star_match[1], ' '))) {
2545                     /* Reject bogus matches */
2546                     i = oldi;
2547                   } else {
2548                     if (appData.colorize) {
2549                       if (oldi > next_out) {
2550                         SendToPlayer(&buf[next_out], oldi - next_out);
2551                         next_out = oldi;
2552                       }
2553                       switch (tkind) {
2554                       case 1:
2555                         Colorize(ColorTell, FALSE);
2556                         curColor = ColorTell;
2557                         break;
2558                       case 2:
2559                         Colorize(ColorKibitz, FALSE);
2560                         curColor = ColorKibitz;
2561                         break;
2562                       case 3:
2563                         p = strrchr(star_match[1], '(');
2564                         if (p == NULL) {
2565                           p = star_match[1];
2566                         } else {
2567                           p++;
2568                         }
2569                         if (atoi(p) == 1) {
2570                           Colorize(ColorChannel1, FALSE);
2571                           curColor = ColorChannel1;
2572                         } else {
2573                           Colorize(ColorChannel, FALSE);
2574                           curColor = ColorChannel;
2575                         }
2576                         break;
2577                       case 5:
2578                         curColor = ColorNormal;
2579                         break;
2580                       }
2581                     }
2582                     if (started == STARTED_NONE && appData.autoComment &&
2583                         (gameMode == IcsObserving ||
2584                          gameMode == IcsPlayingWhite ||
2585                          gameMode == IcsPlayingBlack)) {
2586                       parse_pos = i - oldi;
2587                       memcpy(parse, &buf[oldi], parse_pos);
2588                       parse[parse_pos] = NULLCHAR;
2589                       started = STARTED_COMMENT;
2590                       savingComment = TRUE;
2591                     } else {
2592                       started = STARTED_CHATTER;
2593                       savingComment = FALSE;
2594                     }
2595                     loggedOn = TRUE;
2596                     continue;
2597                   }
2598                 }
2599
2600                 if (looking_at(buf, &i, "* s-shouts: ") ||
2601                     looking_at(buf, &i, "* c-shouts: ")) {
2602                     if (appData.colorize) {
2603                         if (oldi > next_out) {
2604                             SendToPlayer(&buf[next_out], oldi - next_out);
2605                             next_out = oldi;
2606                         }
2607                         Colorize(ColorSShout, FALSE);
2608                         curColor = ColorSShout;
2609                     }
2610                     loggedOn = TRUE;
2611                     started = STARTED_CHATTER;
2612                     continue;
2613                 }
2614
2615                 if (looking_at(buf, &i, "--->")) {
2616                     loggedOn = TRUE;
2617                     continue;
2618                 }
2619
2620                 if (looking_at(buf, &i, "* shouts: ") ||
2621                     looking_at(buf, &i, "--> ")) {
2622                     if (appData.colorize) {
2623                         if (oldi > next_out) {
2624                             SendToPlayer(&buf[next_out], oldi - next_out);
2625                             next_out = oldi;
2626                         }
2627                         Colorize(ColorShout, FALSE);
2628                         curColor = ColorShout;
2629                     }
2630                     loggedOn = TRUE;
2631                     started = STARTED_CHATTER;
2632                     continue;
2633                 }
2634
2635                 if (looking_at( buf, &i, "Challenge:")) {
2636                     if (appData.colorize) {
2637                         if (oldi > next_out) {
2638                             SendToPlayer(&buf[next_out], oldi - next_out);
2639                             next_out = oldi;
2640                         }
2641                         Colorize(ColorChallenge, FALSE);
2642                         curColor = ColorChallenge;
2643                     }
2644                     loggedOn = TRUE;
2645                     continue;
2646                 }
2647
2648                 if (looking_at(buf, &i, "* offers you") ||
2649                     looking_at(buf, &i, "* offers to be") ||
2650                     looking_at(buf, &i, "* would like to") ||
2651                     looking_at(buf, &i, "* requests to") ||
2652                     looking_at(buf, &i, "Your opponent offers") ||
2653                     looking_at(buf, &i, "Your opponent requests")) {
2654
2655                     if (appData.colorize) {
2656                         if (oldi > next_out) {
2657                             SendToPlayer(&buf[next_out], oldi - next_out);
2658                             next_out = oldi;
2659                         }
2660                         Colorize(ColorRequest, FALSE);
2661                         curColor = ColorRequest;
2662                     }
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* (*) seeking")) {
2667                     if (appData.colorize) {
2668                         if (oldi > next_out) {
2669                             SendToPlayer(&buf[next_out], oldi - next_out);
2670                             next_out = oldi;
2671                         }
2672                         Colorize(ColorSeek, FALSE);
2673                         curColor = ColorSeek;
2674                     }
2675                     continue;
2676             }
2677
2678             if (looking_at(buf, &i, "\\   ")) {
2679                 if (prevColor != ColorNormal) {
2680                     if (oldi > next_out) {
2681                         SendToPlayer(&buf[next_out], oldi - next_out);
2682                         next_out = oldi;
2683                     }
2684                     Colorize(prevColor, TRUE);
2685                     curColor = prevColor;
2686                 }
2687                 if (savingComment) {
2688                     parse_pos = i - oldi;
2689                     memcpy(parse, &buf[oldi], parse_pos);
2690                     parse[parse_pos] = NULLCHAR;
2691                     started = STARTED_COMMENT;
2692                 } else {
2693                     started = STARTED_CHATTER;
2694                 }
2695                 continue;
2696             }
2697
2698             if (looking_at(buf, &i, "Black Strength :") ||
2699                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2700                 looking_at(buf, &i, "<10>") ||
2701                 looking_at(buf, &i, "#@#")) {
2702                 /* Wrong board style */
2703                 loggedOn = TRUE;
2704                 SendToICS(ics_prefix);
2705                 SendToICS("set style 12\n");
2706                 SendToICS(ics_prefix);
2707                 SendToICS("refresh\n");
2708                 continue;
2709             }
2710             
2711             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2712                 ICSInitScript();
2713                 have_sent_ICS_logon = 1;
2714                 continue;
2715             }
2716               
2717             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2718                 (looking_at(buf, &i, "\n<12> ") ||
2719                  looking_at(buf, &i, "<12> "))) {
2720                 loggedOn = TRUE;
2721                 if (oldi > next_out) {
2722                     SendToPlayer(&buf[next_out], oldi - next_out);
2723                 }
2724                 next_out = i;
2725                 started = STARTED_BOARD;
2726                 parse_pos = 0;
2727                 continue;
2728             }
2729
2730             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2731                 looking_at(buf, &i, "<b1> ")) {
2732                 if (oldi > next_out) {
2733                     SendToPlayer(&buf[next_out], oldi - next_out);
2734                 }
2735                 next_out = i;
2736                 started = STARTED_HOLDINGS;
2737                 parse_pos = 0;
2738                 continue;
2739             }
2740
2741             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2742                 loggedOn = TRUE;
2743                 /* Header for a move list -- first line */
2744
2745                 switch (ics_getting_history) {
2746                   case H_FALSE:
2747                     switch (gameMode) {
2748                       case IcsIdle:
2749                       case BeginningOfGame:
2750                         /* User typed "moves" or "oldmoves" while we
2751                            were idle.  Pretend we asked for these
2752                            moves and soak them up so user can step
2753                            through them and/or save them.
2754                            */
2755                         Reset(FALSE, TRUE);
2756                         gameMode = IcsObserving;
2757                         ModeHighlight();
2758                         ics_gamenum = -1;
2759                         ics_getting_history = H_GOT_UNREQ_HEADER;
2760                         break;
2761                       case EditGame: /*?*/
2762                       case EditPosition: /*?*/
2763                         /* Should above feature work in these modes too? */
2764                         /* For now it doesn't */
2765                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2766                         break;
2767                       default:
2768                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2769                         break;
2770                     }
2771                     break;
2772                   case H_REQUESTED:
2773                     /* Is this the right one? */
2774                     if (gameInfo.white && gameInfo.black &&
2775                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2776                         strcmp(gameInfo.black, star_match[2]) == 0) {
2777                         /* All is well */
2778                         ics_getting_history = H_GOT_REQ_HEADER;
2779                     }
2780                     break;
2781                   case H_GOT_REQ_HEADER:
2782                   case H_GOT_UNREQ_HEADER:
2783                   case H_GOT_UNWANTED_HEADER:
2784                   case H_GETTING_MOVES:
2785                     /* Should not happen */
2786                     DisplayError(_("Error gathering move list: two headers"), 0);
2787                     ics_getting_history = H_FALSE;
2788                     break;
2789                 }
2790
2791                 /* Save player ratings into gameInfo if needed */
2792                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2793                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2794                     (gameInfo.whiteRating == -1 ||
2795                      gameInfo.blackRating == -1)) {
2796
2797                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2798                     gameInfo.blackRating = string_to_rating(star_match[3]);
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2801                               gameInfo.whiteRating, gameInfo.blackRating);
2802                 }
2803                 continue;
2804             }
2805
2806             if (looking_at(buf, &i,
2807               "* * match, initial time: * minute*, increment: * second")) {
2808                 /* Header for a move list -- second line */
2809                 /* Initial board will follow if this is a wild game */
2810                 if (gameInfo.event != NULL) free(gameInfo.event);
2811                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2812                 gameInfo.event = StrSave(str);
2813                 /* [HGM] we switched variant. Translate boards if needed. */
2814                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2815                 continue;
2816             }
2817
2818             if (looking_at(buf, &i, "Move  ")) {
2819                 /* Beginning of a move list */
2820                 switch (ics_getting_history) {
2821                   case H_FALSE:
2822                     /* Normally should not happen */
2823                     /* Maybe user hit reset while we were parsing */
2824                     break;
2825                   case H_REQUESTED:
2826                     /* Happens if we are ignoring a move list that is not
2827                      * the one we just requested.  Common if the user
2828                      * tries to observe two games without turning off
2829                      * getMoveList */
2830                     break;
2831                   case H_GETTING_MOVES:
2832                     /* Should not happen */
2833                     DisplayError(_("Error gathering move list: nested"), 0);
2834                     ics_getting_history = H_FALSE;
2835                     break;
2836                   case H_GOT_REQ_HEADER:
2837                     ics_getting_history = H_GETTING_MOVES;
2838                     started = STARTED_MOVES;
2839                     parse_pos = 0;
2840                     if (oldi > next_out) {
2841                         SendToPlayer(&buf[next_out], oldi - next_out);
2842                     }
2843                     break;
2844                   case H_GOT_UNREQ_HEADER:
2845                     ics_getting_history = H_GETTING_MOVES;
2846                     started = STARTED_MOVES_NOHIDE;
2847                     parse_pos = 0;
2848                     break;
2849                   case H_GOT_UNWANTED_HEADER:
2850                     ics_getting_history = H_FALSE;
2851                     break;
2852                 }
2853                 continue;
2854             }                           
2855             
2856             if (looking_at(buf, &i, "% ") ||
2857                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2858                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2859                 if(suppressKibitz) next_out = i;
2860                 savingComment = FALSE;
2861                 suppressKibitz = 0;
2862                 switch (started) {
2863                   case STARTED_MOVES:
2864                   case STARTED_MOVES_NOHIDE:
2865                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2866                     parse[parse_pos + i - oldi] = NULLCHAR;
2867                     ParseGameHistory(parse);
2868 #if ZIPPY
2869                     if (appData.zippyPlay && first.initDone) {
2870                         FeedMovesToProgram(&first, forwardMostMove);
2871                         if (gameMode == IcsPlayingWhite) {
2872                             if (WhiteOnMove(forwardMostMove)) {
2873                                 if (first.sendTime) {
2874                                   if (first.useColors) {
2875                                     SendToProgram("black\n", &first); 
2876                                   }
2877                                   SendTimeRemaining(&first, TRUE);
2878                                 }
2879                                 if (first.useColors) {
2880                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2881                                 }
2882                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2883                                 first.maybeThinking = TRUE;
2884                             } else {
2885                                 if (first.usePlayother) {
2886                                   if (first.sendTime) {
2887                                     SendTimeRemaining(&first, TRUE);
2888                                   }
2889                                   SendToProgram("playother\n", &first);
2890                                   firstMove = FALSE;
2891                                 } else {
2892                                   firstMove = TRUE;
2893                                 }
2894                             }
2895                         } else if (gameMode == IcsPlayingBlack) {
2896                             if (!WhiteOnMove(forwardMostMove)) {
2897                                 if (first.sendTime) {
2898                                   if (first.useColors) {
2899                                     SendToProgram("white\n", &first);
2900                                   }
2901                                   SendTimeRemaining(&first, FALSE);
2902                                 }
2903                                 if (first.useColors) {
2904                                   SendToProgram("black\n", &first);
2905                                 }
2906                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2907                                 first.maybeThinking = TRUE;
2908                             } else {
2909                                 if (first.usePlayother) {
2910                                   if (first.sendTime) {
2911                                     SendTimeRemaining(&first, FALSE);
2912                                   }
2913                                   SendToProgram("playother\n", &first);
2914                                   firstMove = FALSE;
2915                                 } else {
2916                                   firstMove = TRUE;
2917                                 }
2918                             }
2919                         }                       
2920                     }
2921 #endif
2922                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2923                         /* Moves came from oldmoves or moves command
2924                            while we weren't doing anything else.
2925                            */
2926                         currentMove = forwardMostMove;
2927                         ClearHighlights();/*!!could figure this out*/
2928                         flipView = appData.flipView;
2929                         DrawPosition(TRUE, boards[currentMove]);
2930                         DisplayBothClocks();
2931                         sprintf(str, "%s vs. %s",
2932                                 gameInfo.white, gameInfo.black);
2933                         DisplayTitle(str);
2934                         gameMode = IcsIdle;
2935                     } else {
2936                         /* Moves were history of an active game */
2937                         if (gameInfo.resultDetails != NULL) {
2938                             free(gameInfo.resultDetails);
2939                             gameInfo.resultDetails = NULL;
2940                         }
2941                     }
2942                     HistorySet(parseList, backwardMostMove,
2943                                forwardMostMove, currentMove-1);
2944                     DisplayMove(currentMove - 1);
2945                     if (started == STARTED_MOVES) next_out = i;
2946                     started = STARTED_NONE;
2947                     ics_getting_history = H_FALSE;
2948                     break;
2949
2950                   case STARTED_OBSERVE:
2951                     started = STARTED_NONE;
2952                     SendToICS(ics_prefix);
2953                     SendToICS("refresh\n");
2954                     break;
2955
2956                   default:
2957                     break;
2958                 }
2959                 if(bookHit) { // [HGM] book: simulate book reply
2960                     static char bookMove[MSG_SIZ]; // a bit generous?
2961
2962                     programStats.nodes = programStats.depth = programStats.time = 
2963                     programStats.score = programStats.got_only_move = 0;
2964                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2965
2966                     strcpy(bookMove, "move ");
2967                     strcat(bookMove, bookHit);
2968                     HandleMachineMove(bookMove, &first);
2969                 }
2970                 continue;
2971             }
2972             
2973             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2974                  started == STARTED_HOLDINGS ||
2975                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2976                 /* Accumulate characters in move list or board */
2977                 parse[parse_pos++] = buf[i];
2978             }
2979             
2980             /* Start of game messages.  Mostly we detect start of game
2981                when the first board image arrives.  On some versions
2982                of the ICS, though, we need to do a "refresh" after starting
2983                to observe in order to get the current board right away. */
2984             if (looking_at(buf, &i, "Adding game * to observation list")) {
2985                 started = STARTED_OBSERVE;
2986                 continue;
2987             }
2988
2989             /* Handle auto-observe */
2990             if (appData.autoObserve &&
2991                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2992                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2993                 char *player;
2994                 /* Choose the player that was highlighted, if any. */
2995                 if (star_match[0][0] == '\033' ||
2996                     star_match[1][0] != '\033') {
2997                     player = star_match[0];
2998                 } else {
2999                     player = star_match[2];
3000                 }
3001                 sprintf(str, "%sobserve %s\n",
3002                         ics_prefix, StripHighlightAndTitle(player));
3003                 SendToICS(str);
3004
3005                 /* Save ratings from notify string */
3006                 strcpy(player1Name, star_match[0]);
3007                 player1Rating = string_to_rating(star_match[1]);
3008                 strcpy(player2Name, star_match[2]);
3009                 player2Rating = string_to_rating(star_match[3]);
3010
3011                 if (appData.debugMode)
3012                   fprintf(debugFP, 
3013                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3014                           player1Name, player1Rating,
3015                           player2Name, player2Rating);
3016
3017                 continue;
3018             }
3019
3020             /* Deal with automatic examine mode after a game,
3021                and with IcsObserving -> IcsExamining transition */
3022             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3023                 looking_at(buf, &i, "has made you an examiner of game *")) {
3024
3025                 int gamenum = atoi(star_match[0]);
3026                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3027                     gamenum == ics_gamenum) {
3028                     /* We were already playing or observing this game;
3029                        no need to refetch history */
3030                     gameMode = IcsExamining;
3031                     if (pausing) {
3032                         pauseExamForwardMostMove = forwardMostMove;
3033                     } else if (currentMove < forwardMostMove) {
3034                         ForwardInner(forwardMostMove);
3035                     }
3036                 } else {
3037                     /* I don't think this case really can happen */
3038                     SendToICS(ics_prefix);
3039                     SendToICS("refresh\n");
3040                 }
3041                 continue;
3042             }    
3043             
3044             /* Error messages */
3045 //          if (ics_user_moved) {
3046             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3047                 if (looking_at(buf, &i, "Illegal move") ||
3048                     looking_at(buf, &i, "Not a legal move") ||
3049                     looking_at(buf, &i, "Your king is in check") ||
3050                     looking_at(buf, &i, "It isn't your turn") ||
3051                     looking_at(buf, &i, "It is not your move")) {
3052                     /* Illegal move */
3053                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3054                         currentMove = --forwardMostMove;
3055                         DisplayMove(currentMove - 1); /* before DMError */
3056                         DrawPosition(FALSE, boards[currentMove]);
3057                         SwitchClocks();
3058                         DisplayBothClocks();
3059                     }
3060                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3061                     ics_user_moved = 0;
3062                     continue;
3063                 }
3064             }
3065
3066             if (looking_at(buf, &i, "still have time") ||
3067                 looking_at(buf, &i, "not out of time") ||
3068                 looking_at(buf, &i, "either player is out of time") ||
3069                 looking_at(buf, &i, "has timeseal; checking")) {
3070                 /* We must have called his flag a little too soon */
3071                 whiteFlag = blackFlag = FALSE;
3072                 continue;
3073             }
3074
3075             if (looking_at(buf, &i, "added * seconds to") ||
3076                 looking_at(buf, &i, "seconds were added to")) {
3077                 /* Update the clocks */
3078                 SendToICS(ics_prefix);
3079                 SendToICS("refresh\n");
3080                 continue;
3081             }
3082
3083             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3084                 ics_clock_paused = TRUE;
3085                 StopClocks();
3086                 continue;
3087             }
3088
3089             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3090                 ics_clock_paused = FALSE;
3091                 StartClocks();
3092                 continue;
3093             }
3094
3095             /* Grab player ratings from the Creating: message.
3096                Note we have to check for the special case when
3097                the ICS inserts things like [white] or [black]. */
3098             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3099                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3100                 /* star_matches:
3101                    0    player 1 name (not necessarily white)
3102                    1    player 1 rating
3103                    2    empty, white, or black (IGNORED)
3104                    3    player 2 name (not necessarily black)
3105                    4    player 2 rating
3106                    
3107                    The names/ratings are sorted out when the game
3108                    actually starts (below).
3109                 */
3110                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3111                 player1Rating = string_to_rating(star_match[1]);
3112                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3113                 player2Rating = string_to_rating(star_match[4]);
3114
3115                 if (appData.debugMode)
3116                   fprintf(debugFP, 
3117                           "Ratings from 'Creating:' %s %d, %s %d\n",
3118                           player1Name, player1Rating,
3119                           player2Name, player2Rating);
3120
3121                 continue;
3122             }
3123             
3124             /* Improved generic start/end-of-game messages */
3125             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3126                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3127                 /* If tkind == 0: */
3128                 /* star_match[0] is the game number */
3129                 /*           [1] is the white player's name */
3130                 /*           [2] is the black player's name */
3131                 /* For end-of-game: */
3132                 /*           [3] is the reason for the game end */
3133                 /*           [4] is a PGN end game-token, preceded by " " */
3134                 /* For start-of-game: */
3135                 /*           [3] begins with "Creating" or "Continuing" */
3136                 /*           [4] is " *" or empty (don't care). */
3137                 int gamenum = atoi(star_match[0]);
3138                 char *whitename, *blackname, *why, *endtoken;
3139                 ChessMove endtype = (ChessMove) 0;
3140
3141                 if (tkind == 0) {
3142                   whitename = star_match[1];
3143                   blackname = star_match[2];
3144                   why = star_match[3];
3145                   endtoken = star_match[4];
3146                 } else {
3147                   whitename = star_match[1];
3148                   blackname = star_match[3];
3149                   why = star_match[5];
3150                   endtoken = star_match[6];
3151                 }
3152
3153                 /* Game start messages */
3154                 if (strncmp(why, "Creating ", 9) == 0 ||
3155                     strncmp(why, "Continuing ", 11) == 0) {
3156                     gs_gamenum = gamenum;
3157                     strcpy(gs_kind, strchr(why, ' ') + 1);
3158                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3159 #if ZIPPY
3160                     if (appData.zippyPlay) {
3161                         ZippyGameStart(whitename, blackname);
3162                     }
3163 #endif /*ZIPPY*/
3164                     continue;
3165                 }
3166
3167                 /* Game end messages */
3168                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3169                     ics_gamenum != gamenum) {
3170                     continue;
3171                 }
3172                 while (endtoken[0] == ' ') endtoken++;
3173                 switch (endtoken[0]) {
3174                   case '*':
3175                   default:
3176                     endtype = GameUnfinished;
3177                     break;
3178                   case '0':
3179                     endtype = BlackWins;
3180                     break;
3181                   case '1':
3182                     if (endtoken[1] == '/')
3183                       endtype = GameIsDrawn;
3184                     else
3185                       endtype = WhiteWins;
3186                     break;
3187                 }
3188                 GameEnds(endtype, why, GE_ICS);
3189 #if ZIPPY
3190                 if (appData.zippyPlay && first.initDone) {
3191                     ZippyGameEnd(endtype, why);
3192                     if (first.pr == NULL) {
3193                       /* Start the next process early so that we'll
3194                          be ready for the next challenge */
3195                       StartChessProgram(&first);
3196                     }
3197                     /* Send "new" early, in case this command takes
3198                        a long time to finish, so that we'll be ready
3199                        for the next challenge. */
3200                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3201                     Reset(TRUE, TRUE);
3202                 }
3203 #endif /*ZIPPY*/
3204                 continue;
3205             }
3206
3207             if (looking_at(buf, &i, "Removing game * from observation") ||
3208                 looking_at(buf, &i, "no longer observing game *") ||
3209                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3210                 if (gameMode == IcsObserving &&
3211                     atoi(star_match[0]) == ics_gamenum)
3212                   {
3213                       /* icsEngineAnalyze */
3214                       if (appData.icsEngineAnalyze) {
3215                             ExitAnalyzeMode();
3216                             ModeHighlight();
3217                       }
3218                       StopClocks();
3219                       gameMode = IcsIdle;
3220                       ics_gamenum = -1;
3221                       ics_user_moved = FALSE;
3222                   }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i, "no longer examining game *")) {
3227                 if (gameMode == IcsExamining &&
3228                     atoi(star_match[0]) == ics_gamenum)
3229                   {
3230                       gameMode = IcsIdle;
3231                       ics_gamenum = -1;
3232                       ics_user_moved = FALSE;
3233                   }
3234                 continue;
3235             }
3236
3237             /* Advance leftover_start past any newlines we find,
3238                so only partial lines can get reparsed */
3239             if (looking_at(buf, &i, "\n")) {
3240                 prevColor = curColor;
3241                 if (curColor != ColorNormal) {
3242                     if (oldi > next_out) {
3243                         SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = oldi;
3245                     }
3246                     Colorize(ColorNormal, FALSE);
3247                     curColor = ColorNormal;
3248                 }
3249                 if (started == STARTED_BOARD) {
3250                     started = STARTED_NONE;
3251                     parse[parse_pos] = NULLCHAR;
3252                     ParseBoard12(parse);
3253                     ics_user_moved = 0;
3254
3255                     /* Send premove here */
3256                     if (appData.premove) {
3257                       char str[MSG_SIZ];
3258                       if (currentMove == 0 &&
3259                           gameMode == IcsPlayingWhite &&
3260                           appData.premoveWhite) {
3261                         sprintf(str, "%s\n", appData.premoveWhiteText);
3262                         if (appData.debugMode)
3263                           fprintf(debugFP, "Sending premove:\n");
3264                         SendToICS(str);
3265                       } else if (currentMove == 1 &&
3266                                  gameMode == IcsPlayingBlack &&
3267                                  appData.premoveBlack) {
3268                         sprintf(str, "%s\n", appData.premoveBlackText);
3269                         if (appData.debugMode)
3270                           fprintf(debugFP, "Sending premove:\n");
3271                         SendToICS(str);
3272                       } else if (gotPremove) {
3273                         gotPremove = 0;
3274                         ClearPremoveHighlights();
3275                         if (appData.debugMode)
3276                           fprintf(debugFP, "Sending premove:\n");
3277                           UserMoveEvent(premoveFromX, premoveFromY, 
3278                                         premoveToX, premoveToY, 
3279                                         premovePromoChar);
3280                       }
3281                     }
3282
3283                     /* Usually suppress following prompt */
3284                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3285                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3286                         if (looking_at(buf, &i, "*% ")) {
3287                             savingComment = FALSE;
3288                             suppressKibitz = 0;
3289                         }
3290                     }
3291                     next_out = i;
3292                 } else if (started == STARTED_HOLDINGS) {
3293                     int gamenum;
3294                     char new_piece[MSG_SIZ];
3295                     started = STARTED_NONE;
3296                     parse[parse_pos] = NULLCHAR;
3297                     if (appData.debugMode)
3298                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3299                                                         parse, currentMove);
3300                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3301                         gamenum == ics_gamenum) {
3302                         if (gameInfo.variant == VariantNormal) {
3303                           /* [HGM] We seem to switch variant during a game!
3304                            * Presumably no holdings were displayed, so we have
3305                            * to move the position two files to the right to
3306                            * create room for them!
3307                            */
3308                           VariantClass newVariant;
3309                           switch(gameInfo.boardWidth) { // base guess on board width
3310                                 case 9:  newVariant = VariantShogi; break;
3311                                 case 10: newVariant = VariantGreat; break;
3312                                 default: newVariant = VariantCrazyhouse; break;
3313                           }
3314                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3315                           /* Get a move list just to see the header, which
3316                              will tell us whether this is really bug or zh */
3317                           if (ics_getting_history == H_FALSE) {
3318                             ics_getting_history = H_REQUESTED;
3319                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3320                             SendToICS(str);
3321                           }
3322                         }
3323                         new_piece[0] = NULLCHAR;
3324                         sscanf(parse, "game %d white [%s black [%s <- %s",
3325                                &gamenum, white_holding, black_holding,
3326                                new_piece);
3327                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3328                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3329                         /* [HGM] copy holdings to board holdings area */
3330                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3331                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3332                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3333 #if ZIPPY
3334                         if (appData.zippyPlay && first.initDone) {
3335                             ZippyHoldings(white_holding, black_holding,
3336                                           new_piece);
3337                         }
3338 #endif /*ZIPPY*/
3339                         if (tinyLayout || smallLayout) {
3340                             char wh[16], bh[16];
3341                             PackHolding(wh, white_holding);
3342                             PackHolding(bh, black_holding);
3343                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3344                                     gameInfo.white, gameInfo.black);
3345                         } else {
3346                             sprintf(str, "%s [%s] vs. %s [%s]",
3347                                     gameInfo.white, white_holding,
3348                                     gameInfo.black, black_holding);
3349                         }
3350
3351                         DrawPosition(FALSE, boards[currentMove]);
3352                         DisplayTitle(str);
3353                     }
3354                     /* Suppress following prompt */
3355                     if (looking_at(buf, &i, "*% ")) {
3356                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3357                         savingComment = FALSE;
3358                         suppressKibitz = 0;
3359                     }
3360                     next_out = i;
3361                 }
3362                 continue;
3363             }
3364
3365             i++;                /* skip unparsed character and loop back */
3366         }
3367         
3368         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3369 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3370 //          SendToPlayer(&buf[next_out], i - next_out);
3371             started != STARTED_HOLDINGS && leftover_start > next_out) {
3372             SendToPlayer(&buf[next_out], leftover_start - next_out);
3373             next_out = i;
3374         }
3375         
3376         leftover_len = buf_len - leftover_start;
3377         /* if buffer ends with something we couldn't parse,
3378            reparse it after appending the next read */
3379         
3380     } else if (count == 0) {
3381         RemoveInputSource(isr);
3382         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3383     } else {
3384         DisplayFatalError(_("Error reading from ICS"), error, 1);
3385     }
3386 }
3387
3388
3389 /* Board style 12 looks like this:
3390    
3391    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3392    
3393  * The "<12> " is stripped before it gets to this routine.  The two
3394  * trailing 0's (flip state and clock ticking) are later addition, and
3395  * some chess servers may not have them, or may have only the first.
3396  * Additional trailing fields may be added in the future.  
3397  */
3398
3399 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3400
3401 #define RELATION_OBSERVING_PLAYED    0
3402 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3403 #define RELATION_PLAYING_MYMOVE      1
3404 #define RELATION_PLAYING_NOTMYMOVE  -1
3405 #define RELATION_EXAMINING           2
3406 #define RELATION_ISOLATED_BOARD     -3
3407 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3408
3409 void
3410 ParseBoard12(string)
3411      char *string;
3412
3413     GameMode newGameMode;
3414     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3415     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3416     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3417     char to_play, board_chars[200];
3418     char move_str[500], str[500], elapsed_time[500];
3419     char black[32], white[32];
3420     Board board;
3421     int prevMove = currentMove;
3422     int ticking = 2;
3423     ChessMove moveType;
3424     int fromX, fromY, toX, toY;
3425     char promoChar;
3426     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3427     char *bookHit = NULL; // [HGM] book
3428     Boolean weird = FALSE, reqFlag = FALSE;
3429
3430     fromX = fromY = toX = toY = -1;
3431     
3432     newGame = FALSE;
3433
3434     if (appData.debugMode)
3435       fprintf(debugFP, _("Parsing board: %s\n"), string);
3436
3437     move_str[0] = NULLCHAR;
3438     elapsed_time[0] = NULLCHAR;
3439     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3440         int  i = 0, j;
3441         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3442             if(string[i] == ' ') { ranks++; files = 0; }
3443             else files++;
3444             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3445             i++;
3446         }
3447         for(j = 0; j <i; j++) board_chars[j] = string[j];
3448         board_chars[i] = '\0';
3449         string += i + 1;
3450     }
3451     n = sscanf(string, PATTERN, &to_play, &double_push,
3452                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3453                &gamenum, white, black, &relation, &basetime, &increment,
3454                &white_stren, &black_stren, &white_time, &black_time,
3455                &moveNum, str, elapsed_time, move_str, &ics_flip,
3456                &ticking);
3457
3458     if (n < 21) {
3459         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3460         DisplayError(str, 0);
3461         return;
3462     }
3463
3464     /* Convert the move number to internal form */
3465     moveNum = (moveNum - 1) * 2;
3466     if (to_play == 'B') moveNum++;
3467     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3468       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3469                         0, 1);
3470       return;
3471     }
3472     
3473     switch (relation) {
3474       case RELATION_OBSERVING_PLAYED:
3475       case RELATION_OBSERVING_STATIC:
3476         if (gamenum == -1) {
3477             /* Old ICC buglet */
3478             relation = RELATION_OBSERVING_STATIC;
3479         }
3480         newGameMode = IcsObserving;
3481         break;
3482       case RELATION_PLAYING_MYMOVE:
3483       case RELATION_PLAYING_NOTMYMOVE:
3484         newGameMode =
3485           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3486             IcsPlayingWhite : IcsPlayingBlack;
3487         break;
3488       case RELATION_EXAMINING:
3489         newGameMode = IcsExamining;
3490         break;
3491       case RELATION_ISOLATED_BOARD:
3492       default:
3493         /* Just display this board.  If user was doing something else,
3494            we will forget about it until the next board comes. */ 
3495         newGameMode = IcsIdle;
3496         break;
3497       case RELATION_STARTING_POSITION:
3498         newGameMode = gameMode;
3499         break;
3500     }
3501     
3502     /* Modify behavior for initial board display on move listing
3503        of wild games.
3504        */
3505     switch (ics_getting_history) {
3506       case H_FALSE:
3507       case H_REQUESTED:
3508         break;
3509       case H_GOT_REQ_HEADER:
3510       case H_GOT_UNREQ_HEADER:
3511         /* This is the initial position of the current game */
3512         gamenum = ics_gamenum;
3513         moveNum = 0;            /* old ICS bug workaround */
3514         if (to_play == 'B') {
3515           startedFromSetupPosition = TRUE;
3516           blackPlaysFirst = TRUE;
3517           moveNum = 1;
3518           if (forwardMostMove == 0) forwardMostMove = 1;
3519           if (backwardMostMove == 0) backwardMostMove = 1;
3520           if (currentMove == 0) currentMove = 1;
3521         }
3522         newGameMode = gameMode;
3523         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3524         break;
3525       case H_GOT_UNWANTED_HEADER:
3526         /* This is an initial board that we don't want */
3527         return;
3528       case H_GETTING_MOVES:
3529         /* Should not happen */
3530         DisplayError(_("Error gathering move list: extra board"), 0);
3531         ics_getting_history = H_FALSE;
3532         return;
3533     }
3534
3535    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3536                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3537      /* [HGM] We seem to have switched variant unexpectedly
3538       * Try to guess new variant from board size
3539       */
3540           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3541           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3542           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3543           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3544           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3545           if(!weird) newVariant = VariantNormal;
3546           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3547           /* Get a move list just to see the header, which
3548              will tell us whether this is really bug or zh */
3549           if (ics_getting_history == H_FALSE) {
3550             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3551             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3552             SendToICS(str);
3553           }
3554     }
3555     
3556     /* Take action if this is the first board of a new game, or of a
3557        different game than is currently being displayed.  */
3558     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3559         relation == RELATION_ISOLATED_BOARD) {
3560         
3561         /* Forget the old game and get the history (if any) of the new one */
3562         if (gameMode != BeginningOfGame) {
3563           Reset(TRUE, TRUE);
3564         }
3565         newGame = TRUE;
3566         if (appData.autoRaiseBoard) BoardToTop();
3567         prevMove = -3;
3568         if (gamenum == -1) {
3569             newGameMode = IcsIdle;
3570         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3571                    appData.getMoveList && !reqFlag) {
3572             /* Need to get game history */
3573             ics_getting_history = H_REQUESTED;
3574             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3575             SendToICS(str);
3576         }
3577         
3578         /* Initially flip the board to have black on the bottom if playing
3579            black or if the ICS flip flag is set, but let the user change
3580            it with the Flip View button. */
3581         flipView = appData.autoFlipView ? 
3582           (newGameMode == IcsPlayingBlack) || ics_flip :
3583           appData.flipView;
3584         
3585         /* Done with values from previous mode; copy in new ones */
3586         gameMode = newGameMode;
3587         ModeHighlight();
3588         ics_gamenum = gamenum;
3589         if (gamenum == gs_gamenum) {
3590             int klen = strlen(gs_kind);
3591             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3592             sprintf(str, "ICS %s", gs_kind);
3593             gameInfo.event = StrSave(str);
3594         } else {
3595             gameInfo.event = StrSave("ICS game");
3596         }
3597         gameInfo.site = StrSave(appData.icsHost);
3598         gameInfo.date = PGNDate();
3599         gameInfo.round = StrSave("-");
3600         gameInfo.white = StrSave(white);
3601         gameInfo.black = StrSave(black);
3602         timeControl = basetime * 60 * 1000;
3603         timeControl_2 = 0;
3604         timeIncrement = increment * 1000;
3605         movesPerSession = 0;
3606         gameInfo.timeControl = TimeControlTagValue();
3607         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3608   if (appData.debugMode) {
3609     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3610     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3611     setbuf(debugFP, NULL);
3612   }
3613
3614         gameInfo.outOfBook = NULL;
3615         
3616         /* Do we have the ratings? */
3617         if (strcmp(player1Name, white) == 0 &&
3618             strcmp(player2Name, black) == 0) {
3619             if (appData.debugMode)
3620               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3621                       player1Rating, player2Rating);
3622             gameInfo.whiteRating = player1Rating;
3623             gameInfo.blackRating = player2Rating;
3624         } else if (strcmp(player2Name, white) == 0 &&
3625                    strcmp(player1Name, black) == 0) {
3626             if (appData.debugMode)
3627               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3628                       player2Rating, player1Rating);
3629             gameInfo.whiteRating = player2Rating;
3630             gameInfo.blackRating = player1Rating;
3631         }
3632         player1Name[0] = player2Name[0] = NULLCHAR;
3633
3634         /* Silence shouts if requested */
3635         if (appData.quietPlay &&
3636             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3637             SendToICS(ics_prefix);
3638             SendToICS("set shout 0\n");
3639         }
3640     }
3641     
3642     /* Deal with midgame name changes */
3643     if (!newGame) {
3644         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3645             if (gameInfo.white) free(gameInfo.white);
3646             gameInfo.white = StrSave(white);
3647         }
3648         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3649             if (gameInfo.black) free(gameInfo.black);
3650             gameInfo.black = StrSave(black);
3651         }
3652     }
3653     
3654     /* Throw away game result if anything actually changes in examine mode */
3655     if (gameMode == IcsExamining && !newGame) {
3656         gameInfo.result = GameUnfinished;
3657         if (gameInfo.resultDetails != NULL) {
3658             free(gameInfo.resultDetails);
3659             gameInfo.resultDetails = NULL;
3660         }
3661     }
3662     
3663     /* In pausing && IcsExamining mode, we ignore boards coming
3664        in if they are in a different variation than we are. */
3665     if (pauseExamInvalid) return;
3666     if (pausing && gameMode == IcsExamining) {
3667         if (moveNum <= pauseExamForwardMostMove) {
3668             pauseExamInvalid = TRUE;
3669             forwardMostMove = pauseExamForwardMostMove;
3670             return;
3671         }
3672     }
3673     
3674   if (appData.debugMode) {
3675     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3676   }
3677     /* Parse the board */
3678     for (k = 0; k < ranks; k++) {
3679       for (j = 0; j < files; j++)
3680         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3681       if(gameInfo.holdingsWidth > 1) {
3682            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3683            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3684       }
3685     }
3686     CopyBoard(boards[moveNum], board);
3687     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3688     if (moveNum == 0) {
3689         startedFromSetupPosition =
3690           !CompareBoards(board, initialPosition);
3691         if(startedFromSetupPosition)
3692             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3693     }
3694
3695     /* [HGM] Set castling rights. Take the outermost Rooks,
3696        to make it also work for FRC opening positions. Note that board12
3697        is really defective for later FRC positions, as it has no way to
3698        indicate which Rook can castle if they are on the same side of King.
3699        For the initial position we grant rights to the outermost Rooks,
3700        and remember thos rights, and we then copy them on positions
3701        later in an FRC game. This means WB might not recognize castlings with
3702        Rooks that have moved back to their original position as illegal,
3703        but in ICS mode that is not its job anyway.
3704     */
3705     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3706     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3707
3708         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3709             if(board[0][i] == WhiteRook) j = i;
3710         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3711         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3712             if(board[0][i] == WhiteRook) j = i;
3713         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3714         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3715             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3716         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3717         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3718             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3719         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3720
3721         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3722         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3723             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3724         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3725             if(board[BOARD_HEIGHT-1][k] == bKing)
3726                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3727     } else { int r;
3728         r = boards[moveNum][CASTLING][0] = initialRights[0];
3729         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3730         r = boards[moveNum][CASTLING][1] = initialRights[1];
3731         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3732         r = boards[moveNum][CASTLING][3] = initialRights[3];
3733         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3734         r = boards[moveNum][CASTLING][4] = initialRights[4];
3735         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3736         /* wildcastle kludge: always assume King has rights */
3737         r = boards[moveNum][CASTLING][2] = initialRights[2];
3738         r = boards[moveNum][CASTLING][5] = initialRights[5];
3739     }
3740     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3741     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3742
3743     
3744     if (ics_getting_history == H_GOT_REQ_HEADER ||
3745         ics_getting_history == H_GOT_UNREQ_HEADER) {
3746         /* This was an initial position from a move list, not
3747            the current position */
3748         return;
3749     }
3750     
3751     /* Update currentMove and known move number limits */
3752     newMove = newGame || moveNum > forwardMostMove;
3753
3754     if (newGame) {
3755         forwardMostMove = backwardMostMove = currentMove = moveNum;
3756         if (gameMode == IcsExamining && moveNum == 0) {
3757           /* Workaround for ICS limitation: we are not told the wild
3758              type when starting to examine a game.  But if we ask for
3759              the move list, the move list header will tell us */
3760             ics_getting_history = H_REQUESTED;
3761             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3762             SendToICS(str);
3763         }
3764     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3765                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3766 #if ZIPPY
3767         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3768         /* [HGM] applied this also to an engine that is silently watching        */
3769         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3770             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3771             gameInfo.variant == currentlyInitializedVariant) {
3772           takeback = forwardMostMove - moveNum;
3773           for (i = 0; i < takeback; i++) {
3774             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3775             SendToProgram("undo\n", &first);
3776           }
3777         }
3778 #endif
3779
3780         forwardMostMove = moveNum;
3781         if (!pausing || currentMove > forwardMostMove)
3782           currentMove = forwardMostMove;
3783     } else {
3784         /* New part of history that is not contiguous with old part */ 
3785         if (pausing && gameMode == IcsExamining) {
3786             pauseExamInvalid = TRUE;
3787             forwardMostMove = pauseExamForwardMostMove;
3788             return;
3789         }
3790         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3791 #if ZIPPY
3792             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3793                 // [HGM] when we will receive the move list we now request, it will be
3794                 // fed to the engine from the first move on. So if the engine is not
3795                 // in the initial position now, bring it there.
3796                 InitChessProgram(&first, 0);
3797             }
3798 #endif
3799             ics_getting_history = H_REQUESTED;
3800             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3801             SendToICS(str);
3802         }
3803         forwardMostMove = backwardMostMove = currentMove = moveNum;
3804     }
3805     
3806     /* Update the clocks */
3807     if (strchr(elapsed_time, '.')) {
3808       /* Time is in ms */
3809       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3810       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3811     } else {
3812       /* Time is in seconds */
3813       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3814       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3815     }
3816       
3817
3818 #if ZIPPY
3819     if (appData.zippyPlay && newGame &&
3820         gameMode != IcsObserving && gameMode != IcsIdle &&
3821         gameMode != IcsExamining)
3822       ZippyFirstBoard(moveNum, basetime, increment);
3823 #endif
3824     
3825     /* Put the move on the move list, first converting
3826        to canonical algebraic form. */
3827     if (moveNum > 0) {
3828   if (appData.debugMode) {
3829     if (appData.debugMode) { int f = forwardMostMove;
3830         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3831                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3832                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3833     }
3834     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3835     fprintf(debugFP, "moveNum = %d\n", moveNum);
3836     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3837     setbuf(debugFP, NULL);
3838   }
3839         if (moveNum <= backwardMostMove) {
3840             /* We don't know what the board looked like before
3841                this move.  Punt. */
3842             strcpy(parseList[moveNum - 1], move_str);
3843             strcat(parseList[moveNum - 1], " ");
3844             strcat(parseList[moveNum - 1], elapsed_time);
3845             moveList[moveNum - 1][0] = NULLCHAR;
3846         } else if (strcmp(move_str, "none") == 0) {
3847             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3848             /* Again, we don't know what the board looked like;
3849                this is really the start of the game. */
3850             parseList[moveNum - 1][0] = NULLCHAR;
3851             moveList[moveNum - 1][0] = NULLCHAR;
3852             backwardMostMove = moveNum;
3853             startedFromSetupPosition = TRUE;
3854             fromX = fromY = toX = toY = -1;
3855         } else {
3856           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3857           //                 So we parse the long-algebraic move string in stead of the SAN move
3858           int valid; char buf[MSG_SIZ], *prom;
3859
3860           // str looks something like "Q/a1-a2"; kill the slash
3861           if(str[1] == '/') 
3862                 sprintf(buf, "%c%s", str[0], str+2);
3863           else  strcpy(buf, str); // might be castling
3864           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3865                 strcat(buf, prom); // long move lacks promo specification!
3866           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3867                 if(appData.debugMode) 
3868                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3869                 strcpy(move_str, buf);
3870           }
3871           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3872                                 &fromX, &fromY, &toX, &toY, &promoChar)
3873                || ParseOneMove(buf, moveNum - 1, &moveType,
3874                                 &fromX, &fromY, &toX, &toY, &promoChar);
3875           // end of long SAN patch
3876           if (valid) {
3877             (void) CoordsToAlgebraic(boards[moveNum - 1],
3878                                      PosFlags(moveNum - 1),
3879                                      fromY, fromX, toY, toX, promoChar,
3880                                      parseList[moveNum-1]);
3881             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3882               case MT_NONE:
3883               case MT_STALEMATE:
3884               default:
3885                 break;
3886               case MT_CHECK:
3887                 if(gameInfo.variant != VariantShogi)
3888                     strcat(parseList[moveNum - 1], "+");
3889                 break;
3890               case MT_CHECKMATE:
3891               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3892                 strcat(parseList[moveNum - 1], "#");
3893                 break;
3894             }
3895             strcat(parseList[moveNum - 1], " ");
3896             strcat(parseList[moveNum - 1], elapsed_time);
3897             /* currentMoveString is set as a side-effect of ParseOneMove */
3898             strcpy(moveList[moveNum - 1], currentMoveString);
3899             strcat(moveList[moveNum - 1], "\n");
3900           } else {
3901             /* Move from ICS was illegal!?  Punt. */
3902   if (appData.debugMode) {
3903     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3904     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3905   }
3906             strcpy(parseList[moveNum - 1], move_str);
3907             strcat(parseList[moveNum - 1], " ");
3908             strcat(parseList[moveNum - 1], elapsed_time);
3909             moveList[moveNum - 1][0] = NULLCHAR;
3910             fromX = fromY = toX = toY = -1;
3911           }
3912         }
3913   if (appData.debugMode) {
3914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3915     setbuf(debugFP, NULL);
3916   }
3917
3918 #if ZIPPY
3919         /* Send move to chess program (BEFORE animating it). */
3920         if (appData.zippyPlay && !newGame && newMove && 
3921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3922
3923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3926                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3927                             move_str);
3928                     DisplayError(str, 0);
3929                 } else {
3930                     if (first.sendTime) {
3931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3932                     }
3933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3934                     if (firstMove && !bookHit) {
3935                         firstMove = FALSE;
3936                         if (first.useColors) {
3937                           SendToProgram(gameMode == IcsPlayingWhite ?
3938                                         "white\ngo\n" :
3939                                         "black\ngo\n", &first);
3940                         } else {
3941                           SendToProgram("go\n", &first);
3942                         }
3943                         first.maybeThinking = TRUE;
3944                     }
3945                 }
3946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3947               if (moveList[moveNum - 1][0] == NULLCHAR) {
3948                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3949                 DisplayError(str, 0);
3950               } else {
3951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3952                 SendMoveToProgram(moveNum - 1, &first);
3953               }
3954             }
3955         }
3956 #endif
3957     }
3958
3959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3960         /* If move comes from a remote source, animate it.  If it
3961            isn't remote, it will have already been animated. */
3962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3964         }
3965         if (!pausing && appData.highlightLastMove) {
3966             SetHighlights(fromX, fromY, toX, toY);
3967         }
3968     }
3969     
3970     /* Start the clocks */
3971     whiteFlag = blackFlag = FALSE;
3972     appData.clockMode = !(basetime == 0 && increment == 0);
3973     if (ticking == 0) {
3974       ics_clock_paused = TRUE;
3975       StopClocks();
3976     } else if (ticking == 1) {
3977       ics_clock_paused = FALSE;
3978     }
3979     if (gameMode == IcsIdle ||
3980         relation == RELATION_OBSERVING_STATIC ||
3981         relation == RELATION_EXAMINING ||
3982         ics_clock_paused)
3983       DisplayBothClocks();
3984     else
3985       StartClocks();
3986     
3987     /* Display opponents and material strengths */
3988     if (gameInfo.variant != VariantBughouse &&
3989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3990         if (tinyLayout || smallLayout) {
3991             if(gameInfo.variant == VariantNormal)
3992                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3994                     basetime, increment);
3995             else
3996                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3998                     basetime, increment, (int) gameInfo.variant);
3999         } else {
4000             if(gameInfo.variant == VariantNormal)
4001                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4002                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4003                     basetime, increment);
4004             else
4005                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4006                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4007                     basetime, increment, VariantName(gameInfo.variant));
4008         }
4009         DisplayTitle(str);
4010   if (appData.debugMode) {
4011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4012   }
4013     }
4014
4015    
4016     /* Display the board */
4017     if (!pausing && !appData.noGUI) {
4018       
4019       if (appData.premove)
4020           if (!gotPremove || 
4021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4023               ClearPremoveHighlights();
4024
4025       DrawPosition(FALSE, boards[currentMove]);
4026       DisplayMove(moveNum - 1);
4027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4031       }
4032     }
4033
4034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4035 #if ZIPPY
4036     if(bookHit) { // [HGM] book: simulate book reply
4037         static char bookMove[MSG_SIZ]; // a bit generous?
4038
4039         programStats.nodes = programStats.depth = programStats.time = 
4040         programStats.score = programStats.got_only_move = 0;
4041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4042
4043         strcpy(bookMove, "move ");
4044         strcat(bookMove, bookHit);
4045         HandleMachineMove(bookMove, &first);
4046     }
4047 #endif
4048 }
4049
4050 void
4051 GetMoveListEvent()
4052 {
4053     char buf[MSG_SIZ];
4054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4055         ics_getting_history = H_REQUESTED;
4056         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4057         SendToICS(buf);
4058     }
4059 }
4060
4061 void
4062 AnalysisPeriodicEvent(force)
4063      int force;
4064 {
4065     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4066          && !force) || !appData.periodicUpdates)
4067       return;
4068
4069     /* Send . command to Crafty to collect stats */
4070     SendToProgram(".\n", &first);
4071
4072     /* Don't send another until we get a response (this makes
4073        us stop sending to old Crafty's which don't understand
4074        the "." command (sending illegal cmds resets node count & time,
4075        which looks bad)) */
4076     programStats.ok_to_send = 0;
4077 }
4078
4079 void ics_update_width(new_width)
4080         int new_width;
4081 {
4082         ics_printf("set width %d\n", new_width);
4083 }
4084
4085 void
4086 SendMoveToProgram(moveNum, cps)
4087      int moveNum;
4088      ChessProgramState *cps;
4089 {
4090     char buf[MSG_SIZ];
4091
4092     if (cps->useUsermove) {
4093       SendToProgram("usermove ", cps);
4094     }
4095     if (cps->useSAN) {
4096       char *space;
4097       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4098         int len = space - parseList[moveNum];
4099         memcpy(buf, parseList[moveNum], len);
4100         buf[len++] = '\n';
4101         buf[len] = NULLCHAR;
4102       } else {
4103         sprintf(buf, "%s\n", parseList[moveNum]);
4104       }
4105       SendToProgram(buf, cps);
4106     } else {
4107       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4108         AlphaRank(moveList[moveNum], 4);
4109         SendToProgram(moveList[moveNum], cps);
4110         AlphaRank(moveList[moveNum], 4); // and back
4111       } else
4112       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4113        * the engine. It would be nice to have a better way to identify castle 
4114        * moves here. */
4115       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4116                                                                          && cps->useOOCastle) {
4117         int fromX = moveList[moveNum][0] - AAA; 
4118         int fromY = moveList[moveNum][1] - ONE;
4119         int toX = moveList[moveNum][2] - AAA; 
4120         int toY = moveList[moveNum][3] - ONE;
4121         if((boards[moveNum][fromY][fromX] == WhiteKing 
4122             && boards[moveNum][toY][toX] == WhiteRook)
4123            || (boards[moveNum][fromY][fromX] == BlackKing 
4124                && boards[moveNum][toY][toX] == BlackRook)) {
4125           if(toX > fromX) SendToProgram("O-O\n", cps);
4126           else SendToProgram("O-O-O\n", cps);
4127         }
4128         else SendToProgram(moveList[moveNum], cps);
4129       }
4130       else SendToProgram(moveList[moveNum], cps);
4131       /* End of additions by Tord */
4132     }
4133
4134     /* [HGM] setting up the opening has brought engine in force mode! */
4135     /*       Send 'go' if we are in a mode where machine should play. */
4136     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4137         (gameMode == TwoMachinesPlay   ||
4138 #ifdef ZIPPY
4139          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4140 #endif
4141          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4142         SendToProgram("go\n", cps);
4143   if (appData.debugMode) {
4144     fprintf(debugFP, "(extra)\n");
4145   }
4146     }
4147     setboardSpoiledMachineBlack = 0;
4148 }
4149
4150 void
4151 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4152      ChessMove moveType;
4153      int fromX, fromY, toX, toY;
4154 {
4155     char user_move[MSG_SIZ];
4156
4157     switch (moveType) {
4158       default:
4159         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4160                 (int)moveType, fromX, fromY, toX, toY);
4161         DisplayError(user_move + strlen("say "), 0);
4162         break;
4163       case WhiteKingSideCastle:
4164       case BlackKingSideCastle:
4165       case WhiteQueenSideCastleWild:
4166       case BlackQueenSideCastleWild:
4167       /* PUSH Fabien */
4168       case WhiteHSideCastleFR:
4169       case BlackHSideCastleFR:
4170       /* POP Fabien */
4171         sprintf(user_move, "o-o\n");
4172         break;
4173       case WhiteQueenSideCastle:
4174       case BlackQueenSideCastle:
4175       case WhiteKingSideCastleWild:
4176       case BlackKingSideCastleWild:
4177       /* PUSH Fabien */
4178       case WhiteASideCastleFR:
4179       case BlackASideCastleFR:
4180       /* POP Fabien */
4181         sprintf(user_move, "o-o-o\n");
4182         break;
4183       case WhitePromotionQueen:
4184       case BlackPromotionQueen:
4185       case WhitePromotionRook:
4186       case BlackPromotionRook:
4187       case WhitePromotionBishop:
4188       case BlackPromotionBishop:
4189       case WhitePromotionKnight:
4190       case BlackPromotionKnight:
4191       case WhitePromotionKing:
4192       case BlackPromotionKing:
4193       case WhitePromotionChancellor:
4194       case BlackPromotionChancellor:
4195       case WhitePromotionArchbishop:
4196       case BlackPromotionArchbishop:
4197         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4198             sprintf(user_move, "%c%c%c%c=%c\n",
4199                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4200                 PieceToChar(WhiteFerz));
4201         else if(gameInfo.variant == VariantGreat)
4202             sprintf(user_move, "%c%c%c%c=%c\n",
4203                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4204                 PieceToChar(WhiteMan));
4205         else
4206             sprintf(user_move, "%c%c%c%c=%c\n",
4207                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4208                 PieceToChar(PromoPiece(moveType)));
4209         break;
4210       case WhiteDrop:
4211       case BlackDrop:
4212         sprintf(user_move, "%c@%c%c\n",
4213                 ToUpper(PieceToChar((ChessSquare) fromX)),
4214                 AAA + toX, ONE + toY);
4215         break;
4216       case NormalMove:
4217       case WhiteCapturesEnPassant:
4218       case BlackCapturesEnPassant:
4219       case IllegalMove:  /* could be a variant we don't quite understand */
4220         sprintf(user_move, "%c%c%c%c\n",
4221                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4222         break;
4223     }
4224     SendToICS(user_move);
4225     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4226         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4227 }
4228
4229 void
4230 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4231      int rf, ff, rt, ft;
4232      char promoChar;
4233      char move[7];
4234 {
4235     if (rf == DROP_RANK) {
4236         sprintf(move, "%c@%c%c\n",
4237                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4238     } else {
4239         if (promoChar == 'x' || promoChar == NULLCHAR) {
4240             sprintf(move, "%c%c%c%c\n",
4241                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4242         } else {
4243             sprintf(move, "%c%c%c%c%c\n",
4244                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4245         }
4246     }
4247 }
4248
4249 void
4250 ProcessICSInitScript(f)
4251      FILE *f;
4252 {
4253     char buf[MSG_SIZ];
4254
4255     while (fgets(buf, MSG_SIZ, f)) {
4256         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4257     }
4258
4259     fclose(f);
4260 }
4261
4262
4263 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4264 void
4265 AlphaRank(char *move, int n)
4266 {
4267 //    char *p = move, c; int x, y;
4268
4269     if (appData.debugMode) {
4270         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4271     }
4272
4273     if(move[1]=='*' && 
4274        move[2]>='0' && move[2]<='9' &&
4275        move[3]>='a' && move[3]<='x'    ) {
4276         move[1] = '@';
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[0]>='0' && move[0]<='9' &&
4281        move[1]>='a' && move[1]<='x' &&
4282        move[2]>='0' && move[2]<='9' &&
4283        move[3]>='a' && move[3]<='x'    ) {
4284         /* input move, Shogi -> normal */
4285         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4286         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4287         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4288         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4289     } else
4290     if(move[1]=='@' &&
4291        move[3]>='0' && move[3]<='9' &&
4292        move[2]>='a' && move[2]<='x'    ) {
4293         move[1] = '*';
4294         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4295         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4296     } else
4297     if(
4298        move[0]>='a' && move[0]<='x' &&
4299        move[3]>='0' && move[3]<='9' &&
4300        move[2]>='a' && move[2]<='x'    ) {
4301          /* output move, normal -> Shogi */
4302         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4303         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4304         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4305         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4306         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4307     }
4308     if (appData.debugMode) {
4309         fprintf(debugFP, "   out = '%s'\n", move);
4310     }
4311 }
4312
4313 /* Parser for moves from gnuchess, ICS, or user typein box */
4314 Boolean
4315 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4316      char *move;
4317      int moveNum;
4318      ChessMove *moveType;
4319      int *fromX, *fromY, *toX, *toY;
4320      char *promoChar;
4321 {       
4322     if (appData.debugMode) {
4323         fprintf(debugFP, "move to parse: %s\n", move);
4324     }
4325     *moveType = yylexstr(moveNum, move);
4326
4327     switch (*moveType) {
4328       case WhitePromotionChancellor:
4329       case BlackPromotionChancellor:
4330       case WhitePromotionArchbishop:
4331       case BlackPromotionArchbishop:
4332       case WhitePromotionQueen:
4333       case BlackPromotionQueen:
4334       case WhitePromotionRook:
4335       case BlackPromotionRook:
4336       case WhitePromotionBishop:
4337       case BlackPromotionBishop:
4338       case WhitePromotionKnight:
4339       case BlackPromotionKnight:
4340       case WhitePromotionKing:
4341       case BlackPromotionKing:
4342       case NormalMove:
4343       case WhiteCapturesEnPassant:
4344       case BlackCapturesEnPassant:
4345       case WhiteKingSideCastle:
4346       case WhiteQueenSideCastle:
4347       case BlackKingSideCastle:
4348       case BlackQueenSideCastle:
4349       case WhiteKingSideCastleWild:
4350       case WhiteQueenSideCastleWild:
4351       case BlackKingSideCastleWild:
4352       case BlackQueenSideCastleWild:
4353       /* Code added by Tord: */
4354       case WhiteHSideCastleFR:
4355       case WhiteASideCastleFR:
4356       case BlackHSideCastleFR:
4357       case BlackASideCastleFR:
4358       /* End of code added by Tord */
4359       case IllegalMove:         /* bug or odd chess variant */
4360         *fromX = currentMoveString[0] - AAA;
4361         *fromY = currentMoveString[1] - ONE;
4362         *toX = currentMoveString[2] - AAA;
4363         *toY = currentMoveString[3] - ONE;
4364         *promoChar = currentMoveString[4];
4365         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4366             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4367     if (appData.debugMode) {
4368         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4369     }
4370             *fromX = *fromY = *toX = *toY = 0;
4371             return FALSE;
4372         }
4373         if (appData.testLegality) {
4374           return (*moveType != IllegalMove);
4375         } else {
4376           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4377                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4378         }
4379
4380       case WhiteDrop:
4381       case BlackDrop:
4382         *fromX = *moveType == WhiteDrop ?
4383           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4384           (int) CharToPiece(ToLower(currentMoveString[0]));
4385         *fromY = DROP_RANK;
4386         *toX = currentMoveString[2] - AAA;
4387         *toY = currentMoveString[3] - ONE;
4388         *promoChar = NULLCHAR;
4389         return TRUE;
4390
4391       case AmbiguousMove:
4392       case ImpossibleMove:
4393       case (ChessMove) 0:       /* end of file */
4394       case ElapsedTime:
4395       case Comment:
4396       case PGNTag:
4397       case NAG:
4398       case WhiteWins:
4399       case BlackWins:
4400       case GameIsDrawn:
4401       default:
4402     if (appData.debugMode) {
4403         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4404     }
4405         /* bug? */
4406         *fromX = *fromY = *toX = *toY = 0;
4407         *promoChar = NULLCHAR;
4408         return FALSE;
4409     }
4410 }
4411
4412
4413 void
4414 ParsePV(char *pv)
4415 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4416   int fromX, fromY, toX, toY; char promoChar;
4417   ChessMove moveType;
4418   Boolean valid;
4419   int nr = 0;
4420
4421   endPV = forwardMostMove;
4422   do {
4423     while(*pv == ' ') pv++;
4424     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4425     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4426 if(appData.debugMode){
4427 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4428 }
4429     if(!valid && nr == 0 &&
4430        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4431         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4432     }
4433     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4434     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4435     nr++;
4436     if(endPV+1 > framePtr) break; // no space, truncate
4437     if(!valid) break;
4438     endPV++;
4439     CopyBoard(boards[endPV], boards[endPV-1]);
4440     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4441     moveList[endPV-1][0] = fromX + AAA;
4442     moveList[endPV-1][1] = fromY + ONE;
4443     moveList[endPV-1][2] = toX + AAA;
4444     moveList[endPV-1][3] = toY + ONE;
4445     parseList[endPV-1][0] = NULLCHAR;
4446   } while(valid);
4447   currentMove = endPV;
4448   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4449   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4450                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4451   DrawPosition(TRUE, boards[currentMove]);
4452 }
4453
4454 static int lastX, lastY;
4455
4456 Boolean
4457 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4458 {
4459         int startPV;
4460
4461         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4462         lastX = x; lastY = y;
4463         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4464         startPV = index;
4465       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4466       index = startPV;
4467         while(buf[index] && buf[index] != '\n') index++;
4468         buf[index] = 0;
4469         ParsePV(buf+startPV);
4470         *start = startPV; *end = index-1;
4471         return TRUE;
4472 }
4473
4474 Boolean
4475 LoadPV(int x, int y)
4476 { // called on right mouse click to load PV
4477   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4478   lastX = x; lastY = y;
4479   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4480   return TRUE;
4481 }
4482
4483 void
4484 UnLoadPV()
4485 {
4486   if(endPV < 0) return;
4487   endPV = -1;
4488   currentMove = forwardMostMove;
4489   ClearPremoveHighlights();
4490   DrawPosition(TRUE, boards[currentMove]);
4491 }
4492
4493 void
4494 MovePV(int x, int y, int h)
4495 { // step through PV based on mouse coordinates (called on mouse move)
4496   int margin = h>>3, step = 0;
4497
4498   if(endPV < 0) return;
4499   // we must somehow check if right button is still down (might be released off board!)
4500   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4501   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4502   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4503   if(!step) return;
4504   lastX = x; lastY = y;
4505   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4506   currentMove += step;
4507   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4508   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4509                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4510   DrawPosition(FALSE, boards[currentMove]);
4511 }
4512
4513
4514 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4515 // All positions will have equal probability, but the current method will not provide a unique
4516 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4517 #define DARK 1
4518 #define LITE 2
4519 #define ANY 3
4520
4521 int squaresLeft[4];
4522 int piecesLeft[(int)BlackPawn];
4523 int seed, nrOfShuffles;
4524
4525 void GetPositionNumber()
4526 {       // sets global variable seed
4527         int i;
4528
4529         seed = appData.defaultFrcPosition;
4530         if(seed < 0) { // randomize based on time for negative FRC position numbers
4531                 for(i=0; i<50; i++) seed += random();
4532                 seed = random() ^ random() >> 8 ^ random() << 8;
4533                 if(seed<0) seed = -seed;
4534         }
4535 }
4536
4537 int put(Board board, int pieceType, int rank, int n, int shade)
4538 // put the piece on the (n-1)-th empty squares of the given shade
4539 {
4540         int i;
4541
4542         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4543                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4544                         board[rank][i] = (ChessSquare) pieceType;
4545                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4546                         squaresLeft[ANY]--;
4547                         piecesLeft[pieceType]--; 
4548                         return i;
4549                 }
4550         }
4551         return -1;
4552 }
4553
4554
4555 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4556 // calculate where the next piece goes, (any empty square), and put it there
4557 {
4558         int i;
4559
4560         i = seed % squaresLeft[shade];
4561         nrOfShuffles *= squaresLeft[shade];
4562         seed /= squaresLeft[shade];
4563         put(board, pieceType, rank, i, shade);
4564 }
4565
4566 void AddTwoPieces(Board board, int pieceType, int rank)
4567 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4568 {
4569         int i, n=squaresLeft[ANY], j=n-1, k;
4570
4571         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4572         i = seed % k;  // pick one
4573         nrOfShuffles *= k;
4574         seed /= k;
4575         while(i >= j) i -= j--;
4576         j = n - 1 - j; i += j;
4577         put(board, pieceType, rank, j, ANY);
4578         put(board, pieceType, rank, i, ANY);
4579 }
4580
4581 void SetUpShuffle(Board board, int number)
4582 {
4583         int i, p, first=1;
4584
4585         GetPositionNumber(); nrOfShuffles = 1;
4586
4587         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4588         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4589         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4590
4591         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4592
4593         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4594             p = (int) board[0][i];
4595             if(p < (int) BlackPawn) piecesLeft[p] ++;
4596             board[0][i] = EmptySquare;
4597         }
4598
4599         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4600             // shuffles restricted to allow normal castling put KRR first
4601             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4602                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4603             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4604                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4605             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4606                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4607             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4608                 put(board, WhiteRook, 0, 0, ANY);
4609             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4610         }
4611
4612         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4613             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4614             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4615                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4616                 while(piecesLeft[p] >= 2) {
4617                     AddOnePiece(board, p, 0, LITE);
4618                     AddOnePiece(board, p, 0, DARK);
4619                 }
4620                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4621             }
4622
4623         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4624             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4625             // but we leave King and Rooks for last, to possibly obey FRC restriction
4626             if(p == (int)WhiteRook) continue;
4627             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4628             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4629         }
4630
4631         // now everything is placed, except perhaps King (Unicorn) and Rooks
4632
4633         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4634             // Last King gets castling rights
4635             while(piecesLeft[(int)WhiteUnicorn]) {
4636                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4637                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4638             }
4639
4640             while(piecesLeft[(int)WhiteKing]) {
4641                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4642                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4643             }
4644
4645
4646         } else {
4647             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4648             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4649         }
4650
4651         // Only Rooks can be left; simply place them all
4652         while(piecesLeft[(int)WhiteRook]) {
4653                 i = put(board, WhiteRook, 0, 0, ANY);
4654                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4655                         if(first) {
4656                                 first=0;
4657                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4658                         }
4659                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4660                 }
4661         }
4662         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4663             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4664         }
4665
4666         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4667 }
4668
4669 int SetCharTable( char *table, const char * map )
4670 /* [HGM] moved here from winboard.c because of its general usefulness */
4671 /*       Basically a safe strcpy that uses the last character as King */
4672 {
4673     int result = FALSE; int NrPieces;
4674
4675     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4676                     && NrPieces >= 12 && !(NrPieces&1)) {
4677         int i; /* [HGM] Accept even length from 12 to 34 */
4678
4679         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4680         for( i=0; i<NrPieces/2-1; i++ ) {
4681             table[i] = map[i];
4682             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4683         }
4684         table[(int) WhiteKing]  = map[NrPieces/2-1];
4685         table[(int) BlackKing]  = map[NrPieces-1];
4686
4687         result = TRUE;
4688     }
4689
4690     return result;
4691 }
4692
4693 void Prelude(Board board)
4694 {       // [HGM] superchess: random selection of exo-pieces
4695         int i, j, k; ChessSquare p; 
4696         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4697
4698         GetPositionNumber(); // use FRC position number
4699
4700         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4701             SetCharTable(pieceToChar, appData.pieceToCharTable);
4702             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4703                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4704         }
4705
4706         j = seed%4;                 seed /= 4; 
4707         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4708         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4709         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4710         j = seed%3 + (seed%3 >= j); seed /= 3; 
4711         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4712         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4713         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4714         j = seed%3;                 seed /= 3; 
4715         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4716         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4717         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4718         j = seed%2 + (seed%2 >= j); seed /= 2; 
4719         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4720         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4721         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4722         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4723         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4724         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4725         put(board, exoPieces[0],    0, 0, ANY);
4726         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4727 }
4728
4729 void
4730 InitPosition(redraw)
4731      int redraw;
4732 {
4733     ChessSquare (* pieces)[BOARD_FILES];
4734     int i, j, pawnRow, overrule,
4735     oldx = gameInfo.boardWidth,
4736     oldy = gameInfo.boardHeight,
4737     oldh = gameInfo.holdingsWidth,
4738     oldv = gameInfo.variant;
4739
4740     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4741
4742     /* [AS] Initialize pv info list [HGM] and game status */
4743     {
4744         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4745             pvInfoList[i].depth = 0;
4746             boards[i][EP_STATUS] = EP_NONE;
4747             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4748         }
4749
4750         initialRulePlies = 0; /* 50-move counter start */
4751
4752         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4753         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4754     }
4755
4756     
4757     /* [HGM] logic here is completely changed. In stead of full positions */
4758     /* the initialized data only consist of the two backranks. The switch */
4759     /* selects which one we will use, which is than copied to the Board   */
4760     /* initialPosition, which for the rest is initialized by Pawns and    */
4761     /* empty squares. This initial position is then copied to boards[0],  */
4762     /* possibly after shuffling, so that it remains available.            */
4763
4764     gameInfo.holdingsWidth = 0; /* default board sizes */
4765     gameInfo.boardWidth    = 8;
4766     gameInfo.boardHeight   = 8;
4767     gameInfo.holdingsSize  = 0;
4768     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4769     for(i=0; i<BOARD_FILES-2; i++)
4770       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4771     initialPosition[EP_STATUS] = EP_NONE;
4772     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4773
4774     switch (gameInfo.variant) {
4775     case VariantFischeRandom:
4776       shuffleOpenings = TRUE;
4777     default:
4778       pieces = FIDEArray;
4779       break;
4780     case VariantShatranj:
4781       pieces = ShatranjArray;
4782       nrCastlingRights = 0;
4783       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4784       break;
4785     case VariantTwoKings:
4786       pieces = twoKingsArray;
4787       break;
4788     case VariantCapaRandom:
4789       shuffleOpenings = TRUE;
4790     case VariantCapablanca:
4791       pieces = CapablancaArray;
4792       gameInfo.boardWidth = 10;
4793       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4794       break;
4795     case VariantGothic:
4796       pieces = GothicArray;
4797       gameInfo.boardWidth = 10;
4798       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4799       break;
4800     case VariantJanus:
4801       pieces = JanusArray;
4802       gameInfo.boardWidth = 10;
4803       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4804       nrCastlingRights = 6;
4805         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4806         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4807         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4808         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4809         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4810         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4811       break;
4812     case VariantFalcon:
4813       pieces = FalconArray;
4814       gameInfo.boardWidth = 10;
4815       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4816       break;
4817     case VariantXiangqi:
4818       pieces = XiangqiArray;
4819       gameInfo.boardWidth  = 9;
4820       gameInfo.boardHeight = 10;
4821       nrCastlingRights = 0;
4822       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4823       break;
4824     case VariantShogi:
4825       pieces = ShogiArray;
4826       gameInfo.boardWidth  = 9;
4827       gameInfo.boardHeight = 9;
4828       gameInfo.holdingsSize = 7;
4829       nrCastlingRights = 0;
4830       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4831       break;
4832     case VariantCourier:
4833       pieces = CourierArray;
4834       gameInfo.boardWidth  = 12;
4835       nrCastlingRights = 0;
4836       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4837       break;
4838     case VariantKnightmate:
4839       pieces = KnightmateArray;
4840       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4841       break;
4842     case VariantFairy:
4843       pieces = fairyArray;
4844       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4845       break;
4846     case VariantGreat:
4847       pieces = GreatArray;
4848       gameInfo.boardWidth = 10;
4849       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4850       gameInfo.holdingsSize = 8;
4851       break;
4852     case VariantSuper:
4853       pieces = FIDEArray;
4854       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4855       gameInfo.holdingsSize = 8;
4856       startedFromSetupPosition = TRUE;
4857       break;
4858     case VariantCrazyhouse:
4859     case VariantBughouse:
4860       pieces = FIDEArray;
4861       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4862       gameInfo.holdingsSize = 5;
4863       break;
4864     case VariantWildCastle:
4865       pieces = FIDEArray;
4866       /* !!?shuffle with kings guaranteed to be on d or e file */
4867       shuffleOpenings = 1;
4868       break;
4869     case VariantNoCastle:
4870       pieces = FIDEArray;
4871       nrCastlingRights = 0;
4872       /* !!?unconstrained back-rank shuffle */
4873       shuffleOpenings = 1;
4874       break;
4875     }
4876
4877     overrule = 0;
4878     if(appData.NrFiles >= 0) {
4879         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4880         gameInfo.boardWidth = appData.NrFiles;
4881     }
4882     if(appData.NrRanks >= 0) {
4883         gameInfo.boardHeight = appData.NrRanks;
4884     }
4885     if(appData.holdingsSize >= 0) {
4886         i = appData.holdingsSize;
4887         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4888         gameInfo.holdingsSize = i;
4889     }
4890     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4891     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4892         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4893
4894     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4895     if(pawnRow < 1) pawnRow = 1;
4896
4897     /* User pieceToChar list overrules defaults */
4898     if(appData.pieceToCharTable != NULL)
4899         SetCharTable(pieceToChar, appData.pieceToCharTable);
4900
4901     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4902
4903         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4904             s = (ChessSquare) 0; /* account holding counts in guard band */
4905         for( i=0; i<BOARD_HEIGHT; i++ )
4906             initialPosition[i][j] = s;
4907
4908         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4909         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4910         initialPosition[pawnRow][j] = WhitePawn;
4911         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4912         if(gameInfo.variant == VariantXiangqi) {
4913             if(j&1) {
4914                 initialPosition[pawnRow][j] = 
4915                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4916                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4917                    initialPosition[2][j] = WhiteCannon;
4918                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4919                 }
4920             }
4921         }
4922         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4923     }
4924     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4925
4926             j=BOARD_LEFT+1;
4927             initialPosition[1][j] = WhiteBishop;
4928             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4929             j=BOARD_RGHT-2;
4930             initialPosition[1][j] = WhiteRook;
4931             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4932     }
4933
4934     if( nrCastlingRights == -1) {
4935         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4936         /*       This sets default castling rights from none to normal corners   */
4937         /* Variants with other castling rights must set them themselves above    */
4938         nrCastlingRights = 6;
4939        
4940         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4941         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4942         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4943         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4944         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4945         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4946      }
4947
4948      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4949      if(gameInfo.variant == VariantGreat) { // promotion commoners
4950         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4951         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4952         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4953         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4954      }
4955   if (appData.debugMode) {
4956     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4957   }
4958     if(shuffleOpenings) {
4959         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4960         startedFromSetupPosition = TRUE;
4961     }
4962     if(startedFromPositionFile) {
4963       /* [HGM] loadPos: use PositionFile for every new game */
4964       CopyBoard(initialPosition, filePosition);
4965       for(i=0; i<nrCastlingRights; i++)
4966           initialRights[i] = filePosition[CASTLING][i];
4967       startedFromSetupPosition = TRUE;
4968     }
4969
4970     CopyBoard(boards[0], initialPosition);
4971
4972     if(oldx != gameInfo.boardWidth ||
4973        oldy != gameInfo.boardHeight ||
4974        oldh != gameInfo.holdingsWidth
4975 #ifdef GOTHIC
4976        || oldv == VariantGothic ||        // For licensing popups
4977        gameInfo.variant == VariantGothic
4978 #endif
4979 #ifdef FALCON
4980        || oldv == VariantFalcon ||
4981        gameInfo.variant == VariantFalcon
4982 #endif
4983                                          )
4984             InitDrawingSizes(-2 ,0);
4985
4986     if (redraw)
4987       DrawPosition(TRUE, boards[currentMove]);
4988 }
4989
4990 void
4991 SendBoard(cps, moveNum)
4992      ChessProgramState *cps;
4993      int moveNum;
4994 {
4995     char message[MSG_SIZ];
4996     
4997     if (cps->useSetboard) {
4998       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4999       sprintf(message, "setboard %s\n", fen);
5000       SendToProgram(message, cps);
5001       free(fen);
5002
5003     } else {
5004       ChessSquare *bp;
5005       int i, j;
5006       /* Kludge to set black to move, avoiding the troublesome and now
5007        * deprecated "black" command.
5008        */
5009       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5010
5011       SendToProgram("edit\n", cps);
5012       SendToProgram("#\n", cps);
5013       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5014         bp = &boards[moveNum][i][BOARD_LEFT];
5015         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5016           if ((int) *bp < (int) BlackPawn) {
5017             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5018                     AAA + j, ONE + i);
5019             if(message[0] == '+' || message[0] == '~') {
5020                 sprintf(message, "%c%c%c+\n",
5021                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5022                         AAA + j, ONE + i);
5023             }
5024             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5025                 message[1] = BOARD_RGHT   - 1 - j + '1';
5026                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5027             }
5028             SendToProgram(message, cps);
5029           }
5030         }
5031       }
5032     
5033       SendToProgram("c\n", cps);
5034       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5035         bp = &boards[moveNum][i][BOARD_LEFT];
5036         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5037           if (((int) *bp != (int) EmptySquare)
5038               && ((int) *bp >= (int) BlackPawn)) {
5039             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5040                     AAA + j, ONE + i);
5041             if(message[0] == '+' || message[0] == '~') {
5042                 sprintf(message, "%c%c%c+\n",
5043                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5044                         AAA + j, ONE + i);
5045             }
5046             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5047                 message[1] = BOARD_RGHT   - 1 - j + '1';
5048                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5049             }
5050             SendToProgram(message, cps);
5051           }
5052         }
5053       }
5054     
5055       SendToProgram(".\n", cps);
5056     }
5057     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5058 }
5059
5060 int
5061 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5062 {
5063     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5064     /* [HGM] add Shogi promotions */
5065     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5066     ChessSquare piece;
5067     ChessMove moveType;
5068     Boolean premove;
5069
5070     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5071     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5072
5073     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5074       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5075         return FALSE;
5076
5077     piece = boards[currentMove][fromY][fromX];
5078     if(gameInfo.variant == VariantShogi) {
5079         promotionZoneSize = 3;
5080         highestPromotingPiece = (int)WhiteFerz;
5081     }
5082
5083     // next weed out all moves that do not touch the promotion zone at all
5084     if((int)piece >= BlackPawn) {
5085         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5086              return FALSE;
5087         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5088     } else {
5089         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5090            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5091     }
5092
5093     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5094
5095     // weed out mandatory Shogi promotions
5096     if(gameInfo.variant == VariantShogi) {
5097         if(piece >= BlackPawn) {
5098             if(toY == 0 && piece == BlackPawn ||
5099                toY == 0 && piece == BlackQueen ||
5100                toY <= 1 && piece == BlackKnight) {
5101                 *promoChoice = '+';
5102                 return FALSE;
5103             }
5104         } else {
5105             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5106                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5107                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5108                 *promoChoice = '+';
5109                 return FALSE;
5110             }
5111         }
5112     }
5113
5114     // weed out obviously illegal Pawn moves
5115     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5116         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5117         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5118         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5119         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5120         // note we are not allowed to test for valid (non-)capture, due to premove
5121     }
5122
5123     // we either have a choice what to promote to, or (in Shogi) whether to promote
5124     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5125         *promoChoice = PieceToChar(BlackFerz);  // no choice
5126         return FALSE;
5127     }
5128     if(appData.alwaysPromoteToQueen) { // predetermined
5129         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5130              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5131         else *promoChoice = PieceToChar(BlackQueen);
5132         return FALSE;
5133     }
5134
5135     // suppress promotion popup on illegal moves that are not premoves
5136     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5137               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5138     if(appData.testLegality && !premove) {
5139         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5140                         fromY, fromX, toY, toX, NULLCHAR);
5141         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5142            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5143             return FALSE;
5144     }
5145
5146     return TRUE;
5147 }
5148
5149 int
5150 InPalace(row, column)
5151      int row, column;
5152 {   /* [HGM] for Xiangqi */
5153     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5154          column < (BOARD_WIDTH + 4)/2 &&
5155          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5156     return FALSE;
5157 }
5158
5159 int
5160 PieceForSquare (x, y)
5161      int x;
5162      int y;
5163 {
5164   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5165      return -1;
5166   else
5167      return boards[currentMove][y][x];
5168 }
5169
5170 int
5171 OKToStartUserMove(x, y)
5172      int x, y;
5173 {
5174     ChessSquare from_piece;
5175     int white_piece;
5176
5177     if (matchMode) return FALSE;
5178     if (gameMode == EditPosition) return TRUE;
5179
5180     if (x >= 0 && y >= 0)
5181       from_piece = boards[currentMove][y][x];
5182     else
5183       from_piece = EmptySquare;
5184
5185     if (from_piece == EmptySquare) return FALSE;
5186
5187     white_piece = (int)from_piece >= (int)WhitePawn &&
5188       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5189
5190     switch (gameMode) {
5191       case PlayFromGameFile:
5192       case AnalyzeFile:
5193       case TwoMachinesPlay:
5194       case EndOfGame:
5195         return FALSE;
5196
5197       case IcsObserving:
5198       case IcsIdle:
5199         return FALSE;
5200
5201       case MachinePlaysWhite:
5202       case IcsPlayingBlack:
5203         if (appData.zippyPlay) return FALSE;
5204         if (white_piece) {
5205             DisplayMoveError(_("You are playing Black"));
5206             return FALSE;
5207         }
5208         break;
5209
5210       case MachinePlaysBlack:
5211       case IcsPlayingWhite:
5212         if (appData.zippyPlay) return FALSE;
5213         if (!white_piece) {
5214             DisplayMoveError(_("You are playing White"));
5215             return FALSE;
5216         }
5217         break;
5218
5219       case EditGame:
5220         if (!white_piece && WhiteOnMove(currentMove)) {
5221             DisplayMoveError(_("It is White's turn"));
5222             return FALSE;
5223         }           
5224         if (white_piece && !WhiteOnMove(currentMove)) {
5225             DisplayMoveError(_("It is Black's turn"));
5226             return FALSE;
5227         }           
5228         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5229             /* Editing correspondence game history */
5230             /* Could disallow this or prompt for confirmation */
5231             cmailOldMove = -1;
5232         }
5233         break;
5234
5235       case BeginningOfGame:
5236         if (appData.icsActive) return FALSE;
5237         if (!appData.noChessProgram) {
5238             if (!white_piece) {
5239                 DisplayMoveError(_("You are playing White"));
5240                 return FALSE;
5241             }
5242         }
5243         break;
5244         
5245       case Training:
5246         if (!white_piece && WhiteOnMove(currentMove)) {
5247             DisplayMoveError(_("It is White's turn"));
5248             return FALSE;
5249         }           
5250         if (white_piece && !WhiteOnMove(currentMove)) {
5251             DisplayMoveError(_("It is Black's turn"));
5252             return FALSE;
5253         }           
5254         break;
5255
5256       default:
5257       case IcsExamining:
5258         break;
5259     }
5260     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5261         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5262         && gameMode != AnalyzeFile && gameMode != Training) {
5263         DisplayMoveError(_("Displayed position is not current"));
5264         return FALSE;
5265     }
5266     return TRUE;
5267 }
5268
5269 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5270 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5271 int lastLoadGameUseList = FALSE;
5272 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5273 ChessMove lastLoadGameStart = (ChessMove) 0;
5274
5275 ChessMove
5276 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5277      int fromX, fromY, toX, toY;
5278      int promoChar;
5279      Boolean captureOwn;
5280 {
5281     ChessMove moveType;
5282     ChessSquare pdown, pup;
5283
5284     /* Check if the user is playing in turn.  This is complicated because we
5285        let the user "pick up" a piece before it is his turn.  So the piece he
5286        tried to pick up may have been captured by the time he puts it down!
5287        Therefore we use the color the user is supposed to be playing in this
5288        test, not the color of the piece that is currently on the starting
5289        square---except in EditGame mode, where the user is playing both
5290        sides; fortunately there the capture race can't happen.  (It can
5291        now happen in IcsExamining mode, but that's just too bad.  The user
5292        will get a somewhat confusing message in that case.)
5293        */
5294
5295     switch (gameMode) {
5296       case PlayFromGameFile:
5297       case AnalyzeFile:
5298       case TwoMachinesPlay:
5299       case EndOfGame:
5300       case IcsObserving:
5301       case IcsIdle:
5302         /* We switched into a game mode where moves are not accepted,
5303            perhaps while the mouse button was down. */
5304         return ImpossibleMove;
5305
5306       case MachinePlaysWhite:
5307         /* User is moving for Black */
5308         if (WhiteOnMove(currentMove)) {
5309             DisplayMoveError(_("It is White's turn"));
5310             return ImpossibleMove;
5311         }
5312         break;
5313
5314       case MachinePlaysBlack:
5315         /* User is moving for White */
5316         if (!WhiteOnMove(currentMove)) {
5317             DisplayMoveError(_("It is Black's turn"));
5318             return ImpossibleMove;
5319         }
5320         break;
5321
5322       case EditGame:
5323       case IcsExamining:
5324       case BeginningOfGame:
5325       case AnalyzeMode:
5326       case Training:
5327         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5328             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5329             /* User is moving for Black */
5330             if (WhiteOnMove(currentMove)) {
5331                 DisplayMoveError(_("It is White's turn"));
5332                 return ImpossibleMove;
5333             }
5334         } else {
5335             /* User is moving for White */
5336             if (!WhiteOnMove(currentMove)) {
5337                 DisplayMoveError(_("It is Black's turn"));
5338                 return ImpossibleMove;
5339             }
5340         }
5341         break;
5342
5343       case IcsPlayingBlack:
5344         /* User is moving for Black */
5345         if (WhiteOnMove(currentMove)) {
5346             if (!appData.premove) {
5347                 DisplayMoveError(_("It is White's turn"));
5348             } else if (toX >= 0 && toY >= 0) {
5349                 premoveToX = toX;
5350                 premoveToY = toY;
5351                 premoveFromX = fromX;
5352                 premoveFromY = fromY;
5353                 premovePromoChar = promoChar;
5354                 gotPremove = 1;
5355                 if (appData.debugMode) 
5356                     fprintf(debugFP, "Got premove: fromX %d,"
5357                             "fromY %d, toX %d, toY %d\n",
5358                             fromX, fromY, toX, toY);
5359             }
5360             return ImpossibleMove;
5361         }
5362         break;
5363
5364       case IcsPlayingWhite:
5365         /* User is moving for White */
5366         if (!WhiteOnMove(currentMove)) {
5367             if (!appData.premove) {
5368                 DisplayMoveError(_("It is Black's turn"));
5369             } else if (toX >= 0 && toY >= 0) {
5370                 premoveToX = toX;
5371                 premoveToY = toY;
5372                 premoveFromX = fromX;
5373                 premoveFromY = fromY;
5374                 premovePromoChar = promoChar;
5375                 gotPremove = 1;
5376                 if (appData.debugMode) 
5377                     fprintf(debugFP, "Got premove: fromX %d,"
5378                             "fromY %d, toX %d, toY %d\n",
5379                             fromX, fromY, toX, toY);
5380             }
5381             return ImpossibleMove;
5382         }
5383         break;
5384
5385       default:
5386         break;
5387
5388       case EditPosition:
5389         /* EditPosition, empty square, or different color piece;
5390            click-click move is possible */
5391         if (toX == -2 || toY == -2) {
5392             boards[0][fromY][fromX] = EmptySquare;
5393             return AmbiguousMove;
5394         } else if (toX >= 0 && toY >= 0) {
5395             boards[0][toY][toX] = boards[0][fromY][fromX];
5396             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5397                 if(boards[0][fromY][0] != EmptySquare) {
5398                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5399                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5400                 }
5401             } else
5402             if(fromX == BOARD_RGHT+1) {
5403                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5404                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5405                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5406                 }
5407             } else
5408             boards[0][fromY][fromX] = EmptySquare;
5409             return AmbiguousMove;
5410         }
5411         return ImpossibleMove;
5412     }
5413
5414     if(toX < 0 || toY < 0) return ImpossibleMove;
5415     pdown = boards[currentMove][fromY][fromX];
5416     pup = boards[currentMove][toY][toX];
5417
5418     /* [HGM] If move started in holdings, it means a drop */
5419     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5420          if( pup != EmptySquare ) return ImpossibleMove;
5421          if(appData.testLegality) {
5422              /* it would be more logical if LegalityTest() also figured out
5423               * which drops are legal. For now we forbid pawns on back rank.
5424               * Shogi is on its own here...
5425               */
5426              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5427                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5428                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5429          }
5430          return WhiteDrop; /* Not needed to specify white or black yet */
5431     }
5432
5433     userOfferedDraw = FALSE;
5434         
5435     /* [HGM] always test for legality, to get promotion info */
5436     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5437                                          fromY, fromX, toY, toX, promoChar);
5438     /* [HGM] but possibly ignore an IllegalMove result */
5439     if (appData.testLegality) {
5440         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5441             DisplayMoveError(_("Illegal move"));
5442             return ImpossibleMove;
5443         }
5444     }
5445
5446     return moveType;
5447     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5448        function is made into one that returns an OK move type if FinishMove
5449        should be called. This to give the calling driver routine the
5450        opportunity to finish the userMove input with a promotion popup,
5451        without bothering the user with this for invalid or illegal moves */
5452
5453 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5454 }
5455
5456 /* Common tail of UserMoveEvent and DropMenuEvent */
5457 int
5458 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5459      ChessMove moveType;
5460      int fromX, fromY, toX, toY;
5461      /*char*/int promoChar;
5462 {
5463     char *bookHit = 0;
5464
5465     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5466         // [HGM] superchess: suppress promotions to non-available piece
5467         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5468         if(WhiteOnMove(currentMove)) {
5469             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5470         } else {
5471             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5472         }
5473     }
5474
5475     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5476        move type in caller when we know the move is a legal promotion */
5477     if(moveType == NormalMove && promoChar)
5478         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5479
5480     /* [HGM] convert drag-and-drop piece drops to standard form */
5481     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5482          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5483            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5484                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5485            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5486            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5487            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5488            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5489          fromY = DROP_RANK;
5490     }
5491
5492     /* [HGM] <popupFix> The following if has been moved here from
5493        UserMoveEvent(). Because it seemed to belong here (why not allow
5494        piece drops in training games?), and because it can only be
5495        performed after it is known to what we promote. */
5496     if (gameMode == Training) {
5497       /* compare the move played on the board to the next move in the
5498        * game. If they match, display the move and the opponent's response. 
5499        * If they don't match, display an error message.
5500        */
5501       int saveAnimate;
5502       Board testBoard;
5503       CopyBoard(testBoard, boards[currentMove]);
5504       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5505
5506       if (CompareBoards(testBoard, boards[currentMove+1])) {
5507         ForwardInner(currentMove+1);
5508
5509         /* Autoplay the opponent's response.
5510          * if appData.animate was TRUE when Training mode was entered,
5511          * the response will be animated.
5512          */
5513         saveAnimate = appData.animate;
5514         appData.animate = animateTraining;
5515         ForwardInner(currentMove+1);
5516         appData.animate = saveAnimate;
5517
5518         /* check for the end of the game */
5519         if (currentMove >= forwardMostMove) {
5520           gameMode = PlayFromGameFile;
5521           ModeHighlight();
5522           SetTrainingModeOff();
5523           DisplayInformation(_("End of game"));
5524         }
5525       } else {
5526         DisplayError(_("Incorrect move"), 0);
5527       }
5528       return 1;
5529     }
5530
5531   /* Ok, now we know that the move is good, so we can kill
5532      the previous line in Analysis Mode */
5533   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5534                                 && currentMove < forwardMostMove) {
5535     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5536   }
5537
5538   /* If we need the chess program but it's dead, restart it */
5539   ResurrectChessProgram();
5540
5541   /* A user move restarts a paused game*/
5542   if (pausing)
5543     PauseEvent();
5544
5545   thinkOutput[0] = NULLCHAR;
5546
5547   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5548
5549   if (gameMode == BeginningOfGame) {
5550     if (appData.noChessProgram) {
5551       gameMode = EditGame;
5552       SetGameInfo();
5553     } else {
5554       char buf[MSG_SIZ];
5555       gameMode = MachinePlaysBlack;
5556       StartClocks();
5557       SetGameInfo();
5558       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5559       DisplayTitle(buf);
5560       if (first.sendName) {
5561         sprintf(buf, "name %s\n", gameInfo.white);
5562         SendToProgram(buf, &first);
5563       }
5564       StartClocks();
5565     }
5566     ModeHighlight();
5567   }
5568
5569   /* Relay move to ICS or chess engine */
5570   if (appData.icsActive) {
5571     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5572         gameMode == IcsExamining) {
5573       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5574       ics_user_moved = 1;
5575     }
5576   } else {
5577     if (first.sendTime && (gameMode == BeginningOfGame ||
5578                            gameMode == MachinePlaysWhite ||
5579                            gameMode == MachinePlaysBlack)) {
5580       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5581     }
5582     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5583          // [HGM] book: if program might be playing, let it use book
5584         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5585         first.maybeThinking = TRUE;
5586     } else SendMoveToProgram(forwardMostMove-1, &first);
5587     if (currentMove == cmailOldMove + 1) {
5588       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5589     }
5590   }
5591
5592   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5593
5594   switch (gameMode) {
5595   case EditGame:
5596     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5597     case MT_NONE:
5598     case MT_CHECK:
5599       break;
5600     case MT_CHECKMATE:
5601     case MT_STAINMATE:
5602       if (WhiteOnMove(currentMove)) {
5603         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5604       } else {
5605         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5606       }
5607       break;
5608     case MT_STALEMATE:
5609       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5610       break;
5611     }
5612     break;
5613     
5614   case MachinePlaysBlack:
5615   case MachinePlaysWhite:
5616     /* disable certain menu options while machine is thinking */
5617     SetMachineThinkingEnables();
5618     break;
5619
5620   default:
5621     break;
5622   }
5623
5624   if(bookHit) { // [HGM] book: simulate book reply
5625         static char bookMove[MSG_SIZ]; // a bit generous?
5626
5627         programStats.nodes = programStats.depth = programStats.time = 
5628         programStats.score = programStats.got_only_move = 0;
5629         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5630
5631         strcpy(bookMove, "move ");
5632         strcat(bookMove, bookHit);
5633         HandleMachineMove(bookMove, &first);
5634   }
5635   return 1;
5636 }
5637
5638 void
5639 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5640      int fromX, fromY, toX, toY;
5641      int promoChar;
5642 {
5643     /* [HGM] This routine was added to allow calling of its two logical
5644        parts from other modules in the old way. Before, UserMoveEvent()
5645        automatically called FinishMove() if the move was OK, and returned
5646        otherwise. I separated the two, in order to make it possible to
5647        slip a promotion popup in between. But that it always needs two
5648        calls, to the first part, (now called UserMoveTest() ), and to
5649        FinishMove if the first part succeeded. Calls that do not need
5650        to do anything in between, can call this routine the old way. 
5651     */
5652     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5653 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5654     if(moveType == AmbiguousMove)
5655         DrawPosition(FALSE, boards[currentMove]);
5656     else if(moveType != ImpossibleMove && moveType != Comment)
5657         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5658 }
5659
5660 void
5661 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5662      Board board;
5663      int flags;
5664      ChessMove kind;
5665      int rf, ff, rt, ft;
5666      VOIDSTAR closure;
5667 {
5668     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5669     Markers *m = (Markers *) closure;
5670     if(rf == fromY && ff == fromX)
5671         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5672                          || kind == WhiteCapturesEnPassant
5673                          || kind == BlackCapturesEnPassant);
5674     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5675 }
5676
5677 void
5678 MarkTargetSquares(int clear)
5679 {
5680   int x, y;
5681   if(!appData.markers || !appData.highlightDragging || 
5682      !appData.testLegality || gameMode == EditPosition) return;
5683   if(clear) {
5684     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5685   } else {
5686     int capt = 0;
5687     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5688     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5689       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5690       if(capt)
5691       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5692     }
5693   }
5694   DrawPosition(TRUE, NULL);
5695 }
5696
5697 void LeftClick(ClickType clickType, int xPix, int yPix)
5698 {
5699     int x, y;
5700     Boolean saveAnimate;
5701     static int second = 0, promotionChoice = 0;
5702     char promoChoice = NULLCHAR;
5703
5704     if (clickType == Press) ErrorPopDown();
5705     MarkTargetSquares(1);
5706
5707     x = EventToSquare(xPix, BOARD_WIDTH);
5708     y = EventToSquare(yPix, BOARD_HEIGHT);
5709     if (!flipView && y >= 0) {
5710         y = BOARD_HEIGHT - 1 - y;
5711     }
5712     if (flipView && x >= 0) {
5713         x = BOARD_WIDTH - 1 - x;
5714     }
5715
5716     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5717         if(clickType == Release) return; // ignore upclick of click-click destination
5718         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5719         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5720         if(gameInfo.holdingsWidth && 
5721                 (WhiteOnMove(currentMove) 
5722                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5723                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5724             // click in right holdings, for determining promotion piece
5725             ChessSquare p = boards[currentMove][y][x];
5726             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5727             if(p != EmptySquare) {
5728                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5729                 fromX = fromY = -1;
5730                 return;
5731             }
5732         }
5733         DrawPosition(FALSE, boards[currentMove]);
5734         return;
5735     }
5736
5737     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5738     if(clickType == Press
5739             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5740               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5741               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5742         return;
5743
5744     if (fromX == -1) {
5745         if (clickType == Press) {
5746             /* First square */
5747             if (OKToStartUserMove(x, y)) {
5748                 fromX = x;
5749                 fromY = y;
5750                 second = 0;
5751                 MarkTargetSquares(0);
5752                 DragPieceBegin(xPix, yPix);
5753                 if (appData.highlightDragging) {
5754                     SetHighlights(x, y, -1, -1);
5755                 }
5756             }
5757         }
5758         return;
5759     }
5760
5761     /* fromX != -1 */
5762     if (clickType == Press && gameMode != EditPosition) {
5763         ChessSquare fromP;
5764         ChessSquare toP;
5765         int frc;
5766
5767         // ignore off-board to clicks
5768         if(y < 0 || x < 0) return;
5769
5770         /* Check if clicking again on the same color piece */
5771         fromP = boards[currentMove][fromY][fromX];
5772         toP = boards[currentMove][y][x];
5773         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5774         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5775              WhitePawn <= toP && toP <= WhiteKing &&
5776              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5777              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5778             (BlackPawn <= fromP && fromP <= BlackKing && 
5779              BlackPawn <= toP && toP <= BlackKing &&
5780              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5781              !(fromP == BlackKing && toP == BlackRook && frc))) {
5782             /* Clicked again on same color piece -- changed his mind */
5783             second = (x == fromX && y == fromY);
5784             if (appData.highlightDragging) {
5785                 SetHighlights(x, y, -1, -1);
5786             } else {
5787                 ClearHighlights();
5788             }
5789             if (OKToStartUserMove(x, y)) {
5790                 fromX = x;
5791                 fromY = y;
5792                 MarkTargetSquares(0);
5793                 DragPieceBegin(xPix, yPix);
5794             }
5795             return;
5796         }
5797         // ignore clicks on holdings
5798         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5799     }
5800
5801     if (clickType == Release && x == fromX && y == fromY) {
5802         DragPieceEnd(xPix, yPix);
5803         if (appData.animateDragging) {
5804             /* Undo animation damage if any */
5805             DrawPosition(FALSE, NULL);
5806         }
5807         if (second) {
5808             /* Second up/down in same square; just abort move */
5809             second = 0;
5810             fromX = fromY = -1;
5811             ClearHighlights();
5812             gotPremove = 0;
5813             ClearPremoveHighlights();
5814         } else {
5815             /* First upclick in same square; start click-click mode */
5816             SetHighlights(x, y, -1, -1);
5817         }
5818         return;
5819     }
5820
5821     /* we now have a different from- and (possibly off-board) to-square */
5822     /* Completed move */
5823     toX = x;
5824     toY = y;
5825     saveAnimate = appData.animate;
5826     if (clickType == Press) {
5827         /* Finish clickclick move */
5828         if (appData.animate || appData.highlightLastMove) {
5829             SetHighlights(fromX, fromY, toX, toY);
5830         } else {
5831             ClearHighlights();
5832         }
5833     } else {
5834         /* Finish drag move */
5835         if (appData.highlightLastMove) {
5836             SetHighlights(fromX, fromY, toX, toY);
5837         } else {
5838             ClearHighlights();
5839         }
5840         DragPieceEnd(xPix, yPix);
5841         /* Don't animate move and drag both */
5842         appData.animate = FALSE;
5843     }
5844
5845     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5846     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5847         ChessSquare piece = boards[currentMove][fromY][fromX];
5848         if(gameMode == EditPosition && piece != EmptySquare &&
5849            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5850             int n;
5851              
5852             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5853                 n = PieceToNumber(piece - (int)BlackPawn);
5854                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5855                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5856                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5857             } else
5858             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5859                 n = PieceToNumber(piece);
5860                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5861                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5862                 boards[currentMove][n][BOARD_WIDTH-2]++;
5863             }
5864             boards[currentMove][fromY][fromX] = EmptySquare;
5865         }
5866         ClearHighlights();
5867         fromX = fromY = -1;
5868         DrawPosition(TRUE, boards[currentMove]);
5869         return;
5870     }
5871
5872     // off-board moves should not be highlighted
5873     if(x < 0 || x < 0) ClearHighlights();
5874
5875     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5876         SetHighlights(fromX, fromY, toX, toY);
5877         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5878             // [HGM] super: promotion to captured piece selected from holdings
5879             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5880             promotionChoice = TRUE;
5881             // kludge follows to temporarily execute move on display, without promoting yet
5882             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5883             boards[currentMove][toY][toX] = p;
5884             DrawPosition(FALSE, boards[currentMove]);
5885             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5886             boards[currentMove][toY][toX] = q;
5887             DisplayMessage("Click in holdings to choose piece", "");
5888             return;
5889         }
5890         PromotionPopUp();
5891     } else {
5892         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5893         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5894         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5895         fromX = fromY = -1;
5896     }
5897     appData.animate = saveAnimate;
5898     if (appData.animate || appData.animateDragging) {
5899         /* Undo animation damage if needed */
5900         DrawPosition(FALSE, NULL);
5901     }
5902 }
5903
5904 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5905 {
5906 //    char * hint = lastHint;
5907     FrontEndProgramStats stats;
5908
5909     stats.which = cps == &first ? 0 : 1;
5910     stats.depth = cpstats->depth;
5911     stats.nodes = cpstats->nodes;
5912     stats.score = cpstats->score;
5913     stats.time = cpstats->time;
5914     stats.pv = cpstats->movelist;
5915     stats.hint = lastHint;
5916     stats.an_move_index = 0;
5917     stats.an_move_count = 0;
5918
5919     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5920         stats.hint = cpstats->move_name;
5921         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5922         stats.an_move_count = cpstats->nr_moves;
5923     }
5924
5925     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5926
5927     SetProgramStats( &stats );
5928 }
5929
5930 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5931 {   // [HGM] book: this routine intercepts moves to simulate book replies
5932     char *bookHit = NULL;
5933
5934     //first determine if the incoming move brings opponent into his book
5935     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5936         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5937     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5938     if(bookHit != NULL && !cps->bookSuspend) {
5939         // make sure opponent is not going to reply after receiving move to book position
5940         SendToProgram("force\n", cps);
5941         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5942     }
5943     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5944     // now arrange restart after book miss
5945     if(bookHit) {
5946         // after a book hit we never send 'go', and the code after the call to this routine
5947         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5948         char buf[MSG_SIZ];
5949         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5950         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5951         SendToProgram(buf, cps);
5952         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5953     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5954         SendToProgram("go\n", cps);
5955         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5956     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5957         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5958             SendToProgram("go\n", cps); 
5959         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5960     }
5961     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5962 }
5963
5964 char *savedMessage;
5965 ChessProgramState *savedState;
5966 void DeferredBookMove(void)
5967 {
5968         if(savedState->lastPing != savedState->lastPong)
5969                     ScheduleDelayedEvent(DeferredBookMove, 10);
5970         else
5971         HandleMachineMove(savedMessage, savedState);
5972 }
5973
5974 void
5975 HandleMachineMove(message, cps)
5976      char *message;
5977      ChessProgramState *cps;
5978 {
5979     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5980     char realname[MSG_SIZ];
5981     int fromX, fromY, toX, toY;
5982     ChessMove moveType;
5983     char promoChar;
5984     char *p;
5985     int machineWhite;
5986     char *bookHit;
5987
5988     cps->userError = 0;
5989
5990 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5991     /*
5992      * Kludge to ignore BEL characters
5993      */
5994     while (*message == '\007') message++;
5995
5996     /*
5997      * [HGM] engine debug message: ignore lines starting with '#' character
5998      */
5999     if(cps->debug && *message == '#') return;
6000
6001     /*
6002      * Look for book output
6003      */
6004     if (cps == &first && bookRequested) {
6005         if (message[0] == '\t' || message[0] == ' ') {
6006             /* Part of the book output is here; append it */
6007             strcat(bookOutput, message);
6008             strcat(bookOutput, "  \n");
6009             return;
6010         } else if (bookOutput[0] != NULLCHAR) {
6011             /* All of book output has arrived; display it */
6012             char *p = bookOutput;
6013             while (*p != NULLCHAR) {
6014                 if (*p == '\t') *p = ' ';
6015                 p++;
6016             }
6017             DisplayInformation(bookOutput);
6018             bookRequested = FALSE;
6019             /* Fall through to parse the current output */
6020         }
6021     }
6022
6023     /*
6024      * Look for machine move.
6025      */
6026     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6027         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6028     {
6029         /* This method is only useful on engines that support ping */
6030         if (cps->lastPing != cps->lastPong) {
6031           if (gameMode == BeginningOfGame) {
6032             /* Extra move from before last new; ignore */
6033             if (appData.debugMode) {
6034                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6035             }
6036           } else {
6037             if (appData.debugMode) {
6038                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6039                         cps->which, gameMode);
6040             }
6041
6042             SendToProgram("undo\n", cps);
6043           }
6044           return;
6045         }
6046
6047         switch (gameMode) {
6048           case BeginningOfGame:
6049             /* Extra move from before last reset; ignore */
6050             if (appData.debugMode) {
6051                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6052             }
6053             return;
6054
6055           case EndOfGame:
6056           case IcsIdle:
6057           default:
6058             /* Extra move after we tried to stop.  The mode test is
6059                not a reliable way of detecting this problem, but it's
6060                the best we can do on engines that don't support ping.
6061             */
6062             if (appData.debugMode) {
6063                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6064                         cps->which, gameMode);
6065             }
6066             SendToProgram("undo\n", cps);
6067             return;
6068
6069           case MachinePlaysWhite:
6070           case IcsPlayingWhite:
6071             machineWhite = TRUE;
6072             break;
6073
6074           case MachinePlaysBlack:
6075           case IcsPlayingBlack:
6076             machineWhite = FALSE;
6077             break;
6078
6079           case TwoMachinesPlay:
6080             machineWhite = (cps->twoMachinesColor[0] == 'w');
6081             break;
6082         }
6083         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6084             if (appData.debugMode) {
6085                 fprintf(debugFP,
6086                         "Ignoring move out of turn by %s, gameMode %d"
6087                         ", forwardMost %d\n",
6088                         cps->which, gameMode, forwardMostMove);
6089             }
6090             return;
6091         }
6092
6093     if (appData.debugMode) { int f = forwardMostMove;
6094         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6095                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6096                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6097     }
6098         if(cps->alphaRank) AlphaRank(machineMove, 4);
6099         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6100                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6101             /* Machine move could not be parsed; ignore it. */
6102             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6103                     machineMove, cps->which);
6104             DisplayError(buf1, 0);
6105             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6106                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6107             if (gameMode == TwoMachinesPlay) {
6108               GameEnds(machineWhite ? BlackWins : WhiteWins,
6109                        buf1, GE_XBOARD);
6110             }
6111             return;
6112         }
6113
6114         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6115         /* So we have to redo legality test with true e.p. status here,  */
6116         /* to make sure an illegal e.p. capture does not slip through,   */
6117         /* to cause a forfeit on a justified illegal-move complaint      */
6118         /* of the opponent.                                              */
6119         if( gameMode==TwoMachinesPlay && appData.testLegality
6120             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6121                                                               ) {
6122            ChessMove moveType;
6123            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6124                              fromY, fromX, toY, toX, promoChar);
6125             if (appData.debugMode) {
6126                 int i;
6127                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6128                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6129                 fprintf(debugFP, "castling rights\n");
6130             }
6131             if(moveType == IllegalMove) {
6132                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6133                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6134                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6135                            buf1, GE_XBOARD);
6136                 return;
6137            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6138            /* [HGM] Kludge to handle engines that send FRC-style castling
6139               when they shouldn't (like TSCP-Gothic) */
6140            switch(moveType) {
6141              case WhiteASideCastleFR:
6142              case BlackASideCastleFR:
6143                toX+=2;
6144                currentMoveString[2]++;
6145                break;
6146              case WhiteHSideCastleFR:
6147              case BlackHSideCastleFR:
6148                toX--;
6149                currentMoveString[2]--;
6150                break;
6151              default: ; // nothing to do, but suppresses warning of pedantic compilers
6152            }
6153         }
6154         hintRequested = FALSE;
6155         lastHint[0] = NULLCHAR;
6156         bookRequested = FALSE;
6157         /* Program may be pondering now */
6158         cps->maybeThinking = TRUE;
6159         if (cps->sendTime == 2) cps->sendTime = 1;
6160         if (cps->offeredDraw) cps->offeredDraw--;
6161
6162 #if ZIPPY
6163         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6164             first.initDone) {
6165           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6166           ics_user_moved = 1;
6167           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6168                 char buf[3*MSG_SIZ];
6169
6170                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6171                         programStats.score / 100.,
6172                         programStats.depth,
6173                         programStats.time / 100.,
6174                         (unsigned int)programStats.nodes,
6175                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6176                         programStats.movelist);
6177                 SendToICS(buf);
6178 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6179           }
6180         }
6181 #endif
6182         /* currentMoveString is set as a side-effect of ParseOneMove */
6183         strcpy(machineMove, currentMoveString);
6184         strcat(machineMove, "\n");
6185         strcpy(moveList[forwardMostMove], machineMove);
6186
6187         /* [AS] Save move info and clear stats for next move */
6188         pvInfoList[ forwardMostMove ].score = programStats.score;
6189         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6190         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6191         ClearProgramStats();
6192         thinkOutput[0] = NULLCHAR;
6193         hiddenThinkOutputState = 0;
6194
6195         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6196
6197         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6198         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6199             int count = 0;
6200
6201             while( count < adjudicateLossPlies ) {
6202                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6203
6204                 if( count & 1 ) {
6205                     score = -score; /* Flip score for winning side */
6206                 }
6207
6208                 if( score > adjudicateLossThreshold ) {
6209                     break;
6210                 }
6211
6212                 count++;
6213             }
6214
6215             if( count >= adjudicateLossPlies ) {
6216                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217
6218                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6219                     "Xboard adjudication", 
6220                     GE_XBOARD );
6221
6222                 return;
6223             }
6224         }
6225
6226         if( gameMode == TwoMachinesPlay ) {
6227           // [HGM] some adjudications useful with buggy engines
6228             int k, count = 0; static int bare = 1;
6229           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6230
6231
6232             if( appData.testLegality )
6233             {   /* [HGM] Some more adjudications for obstinate engines */
6234                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6235                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6236                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6237                 static int moveCount = 6;
6238                 ChessMove result;
6239                 char *reason = NULL;
6240
6241                 /* Count what is on board. */
6242                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6243                 {   ChessSquare p = boards[forwardMostMove][i][j];
6244                     int m=i;
6245
6246                     switch((int) p)
6247                     {   /* count B,N,R and other of each side */
6248                         case WhiteKing:
6249                         case BlackKing:
6250                              NrK++; break; // [HGM] atomic: count Kings
6251                         case WhiteKnight:
6252                              NrWN++; break;
6253                         case WhiteBishop:
6254                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6255                              bishopsColor |= 1 << ((i^j)&1);
6256                              NrWB++; break;
6257                         case BlackKnight:
6258                              NrBN++; break;
6259                         case BlackBishop:
6260                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6261                              bishopsColor |= 1 << ((i^j)&1);
6262                              NrBB++; break;
6263                         case WhiteRook:
6264                              NrWR++; break;
6265                         case BlackRook:
6266                              NrBR++; break;
6267                         case WhiteQueen:
6268                              NrWQ++; break;
6269                         case BlackQueen:
6270                              NrBQ++; break;
6271                         case EmptySquare: 
6272                              break;
6273                         case BlackPawn:
6274                              m = 7-i;
6275                         case WhitePawn:
6276                              PawnAdvance += m; NrPawns++;
6277                     }
6278                     NrPieces += (p != EmptySquare);
6279                     NrW += ((int)p < (int)BlackPawn);
6280                     if(gameInfo.variant == VariantXiangqi && 
6281                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6282                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6283                         NrW -= ((int)p < (int)BlackPawn);
6284                     }
6285                 }
6286
6287                 /* Some material-based adjudications that have to be made before stalemate test */
6288                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6289                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6290                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6291                      if(appData.checkMates) {
6292                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6293                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6294                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6295                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6296                          return;
6297                      }
6298                 }
6299
6300                 /* Bare King in Shatranj (loses) or Losers (wins) */
6301                 if( NrW == 1 || NrPieces - NrW == 1) {
6302                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6303                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6304                      if(appData.checkMates) {
6305                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6306                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6307                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6308                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6309                          return;
6310                      }
6311                   } else
6312                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6313                   {    /* bare King */
6314                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6315                         if(appData.checkMates) {
6316                             /* but only adjudicate if adjudication enabled */
6317                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6318                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6319                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6320                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6321                             return;
6322                         }
6323                   }
6324                 } else bare = 1;
6325
6326
6327             // don't wait for engine to announce game end if we can judge ourselves
6328             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6329               case MT_CHECK:
6330                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6331                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6332                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6333                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6334                             checkCnt++;
6335                         if(checkCnt >= 2) {
6336                             reason = "Xboard adjudication: 3rd check";
6337                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6338                             break;
6339                         }
6340                     }
6341                 }
6342               case MT_NONE:
6343               default:
6344                 break;
6345               case MT_STALEMATE:
6346               case MT_STAINMATE:
6347                 reason = "Xboard adjudication: Stalemate";
6348                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6349                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6350                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6351                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6352                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6353                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6354                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6355                                                                         EP_CHECKMATE : EP_WINS);
6356                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6357                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6358                 }
6359                 break;
6360               case MT_CHECKMATE:
6361                 reason = "Xboard adjudication: Checkmate";
6362                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6363                 break;
6364             }
6365
6366                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6367                     case EP_STALEMATE:
6368                         result = GameIsDrawn; break;
6369                     case EP_CHECKMATE:
6370                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6371                     case EP_WINS:
6372                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6373                     default:
6374                         result = (ChessMove) 0;
6375                 }
6376                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6377                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6378                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6379                     GameEnds( result, reason, GE_XBOARD );
6380                     return;
6381                 }
6382
6383                 /* Next absolutely insufficient mating material. */
6384                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6385                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6386                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6387                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6388                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6389
6390                      /* always flag draws, for judging claims */
6391                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6392
6393                      if(appData.materialDraws) {
6394                          /* but only adjudicate them if adjudication enabled */
6395                          SendToProgram("force\n", cps->other); // suppress reply
6396                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6397                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6398                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6399                          return;
6400                      }
6401                 }
6402
6403                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6404                 if(NrPieces == 4 && 
6405                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6406                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6407                    || NrWN==2 || NrBN==2     /* KNNK */
6408                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6409                   ) ) {
6410                      if(--moveCount < 0 && appData.trivialDraws)
6411                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6412                           SendToProgram("force\n", cps->other); // suppress reply
6413                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6414                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6415                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6416                           return;
6417                      }
6418                 } else moveCount = 6;
6419             }
6420           }
6421           
6422           if (appData.debugMode) { int i;
6423             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6424                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6425                     appData.drawRepeats);
6426             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6427               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6428             
6429           }
6430
6431                 /* Check for rep-draws */
6432                 count = 0;
6433                 for(k = forwardMostMove-2;
6434                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6435                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6436                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6437                     k-=2)
6438                 {   int rights=0;
6439                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6440                         /* compare castling rights */
6441                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6442                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6443                                 rights++; /* King lost rights, while rook still had them */
6444                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6445                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6446                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6447                                    rights++; /* but at least one rook lost them */
6448                         }
6449                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6450                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6451                                 rights++; 
6452                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6453                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6454                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6455                                    rights++;
6456                         }
6457                         if( rights == 0 && ++count > appData.drawRepeats-2
6458                             && appData.drawRepeats > 1) {
6459                              /* adjudicate after user-specified nr of repeats */
6460                              SendToProgram("force\n", cps->other); // suppress reply
6461                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6462                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6463                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6464                                 // [HGM] xiangqi: check for forbidden perpetuals
6465                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6466                                 for(m=forwardMostMove; m>k; m-=2) {
6467                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6468                                         ourPerpetual = 0; // the current mover did not always check
6469                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6470                                         hisPerpetual = 0; // the opponent did not always check
6471                                 }
6472                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6473                                                                         ourPerpetual, hisPerpetual);
6474                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6475                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6476                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6477                                     return;
6478                                 }
6479                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6480                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6481                                 // Now check for perpetual chases
6482                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6483                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6484                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6485                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6486                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6487                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6488                                         return;
6489                                     }
6490                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6491                                         break; // Abort repetition-checking loop.
6492                                 }
6493                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6494                              }
6495                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6496                              return;
6497                         }
6498                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6499                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6500                     }
6501                 }
6502
6503                 /* Now we test for 50-move draws. Determine ply count */
6504                 count = forwardMostMove;
6505                 /* look for last irreversble move */
6506                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6507                     count--;
6508                 /* if we hit starting position, add initial plies */
6509                 if( count == backwardMostMove )
6510                     count -= initialRulePlies;
6511                 count = forwardMostMove - count; 
6512                 if( count >= 100)
6513                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6514                          /* this is used to judge if draw claims are legal */
6515                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6516                          SendToProgram("force\n", cps->other); // suppress reply
6517                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6518                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6519                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6520                          return;
6521                 }
6522
6523                 /* if draw offer is pending, treat it as a draw claim
6524                  * when draw condition present, to allow engines a way to
6525                  * claim draws before making their move to avoid a race
6526                  * condition occurring after their move
6527                  */
6528                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6529                          char *p = NULL;
6530                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6531                              p = "Draw claim: 50-move rule";
6532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6533                              p = "Draw claim: 3-fold repetition";
6534                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6535                              p = "Draw claim: insufficient mating material";
6536                          if( p != NULL ) {
6537                              SendToProgram("force\n", cps->other); // suppress reply
6538                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6539                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6540                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6541                              return;
6542                          }
6543                 }
6544
6545
6546                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6547                     SendToProgram("force\n", cps->other); // suppress reply
6548                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6549                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6550
6551                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6552
6553                     return;
6554                 }
6555         }
6556
6557         bookHit = NULL;
6558         if (gameMode == TwoMachinesPlay) {
6559             /* [HGM] relaying draw offers moved to after reception of move */
6560             /* and interpreting offer as claim if it brings draw condition */
6561             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6562                 SendToProgram("draw\n", cps->other);
6563             }
6564             if (cps->other->sendTime) {
6565                 SendTimeRemaining(cps->other,
6566                                   cps->other->twoMachinesColor[0] == 'w');
6567             }
6568             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6569             if (firstMove && !bookHit) {
6570                 firstMove = FALSE;
6571                 if (cps->other->useColors) {
6572                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6573                 }
6574                 SendToProgram("go\n", cps->other);
6575             }
6576             cps->other->maybeThinking = TRUE;
6577         }
6578
6579         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6580         
6581         if (!pausing && appData.ringBellAfterMoves) {
6582             RingBell();
6583         }
6584
6585         /* 
6586          * Reenable menu items that were disabled while
6587          * machine was thinking
6588          */
6589         if (gameMode != TwoMachinesPlay)
6590             SetUserThinkingEnables();
6591
6592         // [HGM] book: after book hit opponent has received move and is now in force mode
6593         // force the book reply into it, and then fake that it outputted this move by jumping
6594         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6595         if(bookHit) {
6596                 static char bookMove[MSG_SIZ]; // a bit generous?
6597
6598                 strcpy(bookMove, "move ");
6599                 strcat(bookMove, bookHit);
6600                 message = bookMove;
6601                 cps = cps->other;
6602                 programStats.nodes = programStats.depth = programStats.time = 
6603                 programStats.score = programStats.got_only_move = 0;
6604                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6605
6606                 if(cps->lastPing != cps->lastPong) {
6607                     savedMessage = message; // args for deferred call
6608                     savedState = cps;
6609                     ScheduleDelayedEvent(DeferredBookMove, 10);
6610                     return;
6611                 }
6612                 goto FakeBookMove;
6613         }
6614
6615         return;
6616     }
6617
6618     /* Set special modes for chess engines.  Later something general
6619      *  could be added here; for now there is just one kludge feature,
6620      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6621      *  when "xboard" is given as an interactive command.
6622      */
6623     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6624         cps->useSigint = FALSE;
6625         cps->useSigterm = FALSE;
6626     }
6627     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6628       ParseFeatures(message+8, cps);
6629       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6630     }
6631
6632     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6633      * want this, I was asked to put it in, and obliged.
6634      */
6635     if (!strncmp(message, "setboard ", 9)) {
6636         Board initial_position;
6637
6638         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6639
6640         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6641             DisplayError(_("Bad FEN received from engine"), 0);
6642             return ;
6643         } else {
6644            Reset(TRUE, FALSE);
6645            CopyBoard(boards[0], initial_position);
6646            initialRulePlies = FENrulePlies;
6647            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6648            else gameMode = MachinePlaysBlack;                 
6649            DrawPosition(FALSE, boards[currentMove]);
6650         }
6651         return;
6652     }
6653
6654     /*
6655      * Look for communication commands
6656      */
6657     if (!strncmp(message, "telluser ", 9)) {
6658         DisplayNote(message + 9);
6659         return;
6660     }
6661     if (!strncmp(message, "tellusererror ", 14)) {
6662         cps->userError = 1;
6663         DisplayError(message + 14, 0);
6664         return;
6665     }
6666     if (!strncmp(message, "tellopponent ", 13)) {
6667       if (appData.icsActive) {
6668         if (loggedOn) {
6669           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6670           SendToICS(buf1);
6671         }
6672       } else {
6673         DisplayNote(message + 13);
6674       }
6675       return;
6676     }
6677     if (!strncmp(message, "tellothers ", 11)) {
6678       if (appData.icsActive) {
6679         if (loggedOn) {
6680           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6681           SendToICS(buf1);
6682         }
6683       }
6684       return;
6685     }
6686     if (!strncmp(message, "tellall ", 8)) {
6687       if (appData.icsActive) {
6688         if (loggedOn) {
6689           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6690           SendToICS(buf1);
6691         }
6692       } else {
6693         DisplayNote(message + 8);
6694       }
6695       return;
6696     }
6697     if (strncmp(message, "warning", 7) == 0) {
6698         /* Undocumented feature, use tellusererror in new code */
6699         DisplayError(message, 0);
6700         return;
6701     }
6702     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6703         strcpy(realname, cps->tidy);
6704         strcat(realname, " query");
6705         AskQuestion(realname, buf2, buf1, cps->pr);
6706         return;
6707     }
6708     /* Commands from the engine directly to ICS.  We don't allow these to be 
6709      *  sent until we are logged on. Crafty kibitzes have been known to 
6710      *  interfere with the login process.
6711      */
6712     if (loggedOn) {
6713         if (!strncmp(message, "tellics ", 8)) {
6714             SendToICS(message + 8);
6715             SendToICS("\n");
6716             return;
6717         }
6718         if (!strncmp(message, "tellicsnoalias ", 15)) {
6719             SendToICS(ics_prefix);
6720             SendToICS(message + 15);
6721             SendToICS("\n");
6722             return;
6723         }
6724         /* The following are for backward compatibility only */
6725         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6726             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6727             SendToICS(ics_prefix);
6728             SendToICS(message);
6729             SendToICS("\n");
6730             return;
6731         }
6732     }
6733     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6734         return;
6735     }
6736     /*
6737      * If the move is illegal, cancel it and redraw the board.
6738      * Also deal with other error cases.  Matching is rather loose
6739      * here to accommodate engines written before the spec.
6740      */
6741     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6742         strncmp(message, "Error", 5) == 0) {
6743         if (StrStr(message, "name") || 
6744             StrStr(message, "rating") || StrStr(message, "?") ||
6745             StrStr(message, "result") || StrStr(message, "board") ||
6746             StrStr(message, "bk") || StrStr(message, "computer") ||
6747             StrStr(message, "variant") || StrStr(message, "hint") ||
6748             StrStr(message, "random") || StrStr(message, "depth") ||
6749             StrStr(message, "accepted")) {
6750             return;
6751         }
6752         if (StrStr(message, "protover")) {
6753           /* Program is responding to input, so it's apparently done
6754              initializing, and this error message indicates it is
6755              protocol version 1.  So we don't need to wait any longer
6756              for it to initialize and send feature commands. */
6757           FeatureDone(cps, 1);
6758           cps->protocolVersion = 1;
6759           return;
6760         }
6761         cps->maybeThinking = FALSE;
6762
6763         if (StrStr(message, "draw")) {
6764             /* Program doesn't have "draw" command */
6765             cps->sendDrawOffers = 0;
6766             return;
6767         }
6768         if (cps->sendTime != 1 &&
6769             (StrStr(message, "time") || StrStr(message, "otim"))) {
6770           /* Program apparently doesn't have "time" or "otim" command */
6771           cps->sendTime = 0;
6772           return;
6773         }
6774         if (StrStr(message, "analyze")) {
6775             cps->analysisSupport = FALSE;
6776             cps->analyzing = FALSE;
6777             Reset(FALSE, TRUE);
6778             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6779             DisplayError(buf2, 0);
6780             return;
6781         }
6782         if (StrStr(message, "(no matching move)st")) {
6783           /* Special kludge for GNU Chess 4 only */
6784           cps->stKludge = TRUE;
6785           SendTimeControl(cps, movesPerSession, timeControl,
6786                           timeIncrement, appData.searchDepth,
6787                           searchTime);
6788           return;
6789         }
6790         if (StrStr(message, "(no matching move)sd")) {
6791           /* Special kludge for GNU Chess 4 only */
6792           cps->sdKludge = TRUE;
6793           SendTimeControl(cps, movesPerSession, timeControl,
6794                           timeIncrement, appData.searchDepth,
6795                           searchTime);
6796           return;
6797         }
6798         if (!StrStr(message, "llegal")) {
6799             return;
6800         }
6801         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6802             gameMode == IcsIdle) return;
6803         if (forwardMostMove <= backwardMostMove) return;
6804         if (pausing) PauseEvent();
6805       if(appData.forceIllegal) {
6806             // [HGM] illegal: machine refused move; force position after move into it
6807           SendToProgram("force\n", cps);
6808           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6809                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6810                 // when black is to move, while there might be nothing on a2 or black
6811                 // might already have the move. So send the board as if white has the move.
6812                 // But first we must change the stm of the engine, as it refused the last move
6813                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6814                 if(WhiteOnMove(forwardMostMove)) {
6815                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6816                     SendBoard(cps, forwardMostMove); // kludgeless board
6817                 } else {
6818                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6819                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6820                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6821                 }
6822           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6823             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6824                  gameMode == TwoMachinesPlay)
6825               SendToProgram("go\n", cps);
6826             return;
6827       } else
6828         if (gameMode == PlayFromGameFile) {
6829             /* Stop reading this game file */
6830             gameMode = EditGame;
6831             ModeHighlight();
6832         }
6833         currentMove = --forwardMostMove;
6834         DisplayMove(currentMove-1); /* before DisplayMoveError */
6835         SwitchClocks();
6836         DisplayBothClocks();
6837         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6838                 parseList[currentMove], cps->which);
6839         DisplayMoveError(buf1);
6840         DrawPosition(FALSE, boards[currentMove]);
6841
6842         /* [HGM] illegal-move claim should forfeit game when Xboard */
6843         /* only passes fully legal moves                            */
6844         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6845             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6846                                 "False illegal-move claim", GE_XBOARD );
6847         }
6848         return;
6849     }
6850     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6851         /* Program has a broken "time" command that
6852            outputs a string not ending in newline.
6853            Don't use it. */
6854         cps->sendTime = 0;
6855     }
6856     
6857     /*
6858      * If chess program startup fails, exit with an error message.
6859      * Attempts to recover here are futile.
6860      */
6861     if ((StrStr(message, "unknown host") != NULL)
6862         || (StrStr(message, "No remote directory") != NULL)
6863         || (StrStr(message, "not found") != NULL)
6864         || (StrStr(message, "No such file") != NULL)
6865         || (StrStr(message, "can't alloc") != NULL)
6866         || (StrStr(message, "Permission denied") != NULL)) {
6867
6868         cps->maybeThinking = FALSE;
6869         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6870                 cps->which, cps->program, cps->host, message);
6871         RemoveInputSource(cps->isr);
6872         DisplayFatalError(buf1, 0, 1);
6873         return;
6874     }
6875     
6876     /* 
6877      * Look for hint output
6878      */
6879     if (sscanf(message, "Hint: %s", buf1) == 1) {
6880         if (cps == &first && hintRequested) {
6881             hintRequested = FALSE;
6882             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6883                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6884                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6885                                     PosFlags(forwardMostMove),
6886                                     fromY, fromX, toY, toX, promoChar, buf1);
6887                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6888                 DisplayInformation(buf2);
6889             } else {
6890                 /* Hint move could not be parsed!? */
6891               snprintf(buf2, sizeof(buf2),
6892                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6893                         buf1, cps->which);
6894                 DisplayError(buf2, 0);
6895             }
6896         } else {
6897             strcpy(lastHint, buf1);
6898         }
6899         return;
6900     }
6901
6902     /*
6903      * Ignore other messages if game is not in progress
6904      */
6905     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6906         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6907
6908     /*
6909      * look for win, lose, draw, or draw offer
6910      */
6911     if (strncmp(message, "1-0", 3) == 0) {
6912         char *p, *q, *r = "";
6913         p = strchr(message, '{');
6914         if (p) {
6915             q = strchr(p, '}');
6916             if (q) {
6917                 *q = NULLCHAR;
6918                 r = p + 1;
6919             }
6920         }
6921         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6922         return;
6923     } else if (strncmp(message, "0-1", 3) == 0) {
6924         char *p, *q, *r = "";
6925         p = strchr(message, '{');
6926         if (p) {
6927             q = strchr(p, '}');
6928             if (q) {
6929                 *q = NULLCHAR;
6930                 r = p + 1;
6931             }
6932         }
6933         /* Kludge for Arasan 4.1 bug */
6934         if (strcmp(r, "Black resigns") == 0) {
6935             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6936             return;
6937         }
6938         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6939         return;
6940     } else if (strncmp(message, "1/2", 3) == 0) {
6941         char *p, *q, *r = "";
6942         p = strchr(message, '{');
6943         if (p) {
6944             q = strchr(p, '}');
6945             if (q) {
6946                 *q = NULLCHAR;
6947                 r = p + 1;
6948             }
6949         }
6950             
6951         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6952         return;
6953
6954     } else if (strncmp(message, "White resign", 12) == 0) {
6955         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6956         return;
6957     } else if (strncmp(message, "Black resign", 12) == 0) {
6958         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6959         return;
6960     } else if (strncmp(message, "White matches", 13) == 0 ||
6961                strncmp(message, "Black matches", 13) == 0   ) {
6962         /* [HGM] ignore GNUShogi noises */
6963         return;
6964     } else if (strncmp(message, "White", 5) == 0 &&
6965                message[5] != '(' &&
6966                StrStr(message, "Black") == NULL) {
6967         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6968         return;
6969     } else if (strncmp(message, "Black", 5) == 0 &&
6970                message[5] != '(') {
6971         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6972         return;
6973     } else if (strcmp(message, "resign") == 0 ||
6974                strcmp(message, "computer resigns") == 0) {
6975         switch (gameMode) {
6976           case MachinePlaysBlack:
6977           case IcsPlayingBlack:
6978             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6979             break;
6980           case MachinePlaysWhite:
6981           case IcsPlayingWhite:
6982             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6983             break;
6984           case TwoMachinesPlay:
6985             if (cps->twoMachinesColor[0] == 'w')
6986               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6987             else
6988               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6989             break;
6990           default:
6991             /* can't happen */
6992             break;
6993         }
6994         return;
6995     } else if (strncmp(message, "opponent mates", 14) == 0) {
6996         switch (gameMode) {
6997           case MachinePlaysBlack:
6998           case IcsPlayingBlack:
6999             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7000             break;
7001           case MachinePlaysWhite:
7002           case IcsPlayingWhite:
7003             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7004             break;
7005           case TwoMachinesPlay:
7006             if (cps->twoMachinesColor[0] == 'w')
7007               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7008             else
7009               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7010             break;
7011           default:
7012             /* can't happen */
7013             break;
7014         }
7015         return;
7016     } else if (strncmp(message, "computer mates", 14) == 0) {
7017         switch (gameMode) {
7018           case MachinePlaysBlack:
7019           case IcsPlayingBlack:
7020             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7021             break;
7022           case MachinePlaysWhite:
7023           case IcsPlayingWhite:
7024             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7025             break;
7026           case TwoMachinesPlay:
7027             if (cps->twoMachinesColor[0] == 'w')
7028               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7029             else
7030               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7031             break;
7032           default:
7033             /* can't happen */
7034             break;
7035         }
7036         return;
7037     } else if (strncmp(message, "checkmate", 9) == 0) {
7038         if (WhiteOnMove(forwardMostMove)) {
7039             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7040         } else {
7041             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7042         }
7043         return;
7044     } else if (strstr(message, "Draw") != NULL ||
7045                strstr(message, "game is a draw") != NULL) {
7046         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7047         return;
7048     } else if (strstr(message, "offer") != NULL &&
7049                strstr(message, "draw") != NULL) {
7050 #if ZIPPY
7051         if (appData.zippyPlay && first.initDone) {
7052             /* Relay offer to ICS */
7053             SendToICS(ics_prefix);
7054             SendToICS("draw\n");
7055         }
7056 #endif
7057         cps->offeredDraw = 2; /* valid until this engine moves twice */
7058         if (gameMode == TwoMachinesPlay) {
7059             if (cps->other->offeredDraw) {
7060                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7061             /* [HGM] in two-machine mode we delay relaying draw offer      */
7062             /* until after we also have move, to see if it is really claim */
7063             }
7064         } else if (gameMode == MachinePlaysWhite ||
7065                    gameMode == MachinePlaysBlack) {
7066           if (userOfferedDraw) {
7067             DisplayInformation(_("Machine accepts your draw offer"));
7068             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7069           } else {
7070             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7071           }
7072         }
7073     }
7074
7075     
7076     /*
7077      * Look for thinking output
7078      */
7079     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7080           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7081                                 ) {
7082         int plylev, mvleft, mvtot, curscore, time;
7083         char mvname[MOVE_LEN];
7084         u64 nodes; // [DM]
7085         char plyext;
7086         int ignore = FALSE;
7087         int prefixHint = FALSE;
7088         mvname[0] = NULLCHAR;
7089
7090         switch (gameMode) {
7091           case MachinePlaysBlack:
7092           case IcsPlayingBlack:
7093             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7094             break;
7095           case MachinePlaysWhite:
7096           case IcsPlayingWhite:
7097             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7098             break;
7099           case AnalyzeMode:
7100           case AnalyzeFile:
7101             break;
7102           case IcsObserving: /* [DM] icsEngineAnalyze */
7103             if (!appData.icsEngineAnalyze) ignore = TRUE;
7104             break;
7105           case TwoMachinesPlay:
7106             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7107                 ignore = TRUE;
7108             }
7109             break;
7110           default:
7111             ignore = TRUE;
7112             break;
7113         }
7114
7115         if (!ignore) {
7116             buf1[0] = NULLCHAR;
7117             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7118                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7119
7120                 if (plyext != ' ' && plyext != '\t') {
7121                     time *= 100;
7122                 }
7123
7124                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7125                 if( cps->scoreIsAbsolute && 
7126                     ( gameMode == MachinePlaysBlack ||
7127                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7128                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7129                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7130                      !WhiteOnMove(currentMove)
7131                     ) )
7132                 {
7133                     curscore = -curscore;
7134                 }
7135
7136
7137                 programStats.depth = plylev;
7138                 programStats.nodes = nodes;
7139                 programStats.time = time;
7140                 programStats.score = curscore;
7141                 programStats.got_only_move = 0;
7142
7143                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7144                         int ticklen;
7145
7146                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7147                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7148                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7149                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7150                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7151                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7152                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7153                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7154                 }
7155
7156                 /* Buffer overflow protection */
7157                 if (buf1[0] != NULLCHAR) {
7158                     if (strlen(buf1) >= sizeof(programStats.movelist)
7159                         && appData.debugMode) {
7160                         fprintf(debugFP,
7161                                 "PV is too long; using the first %u bytes.\n",
7162                                 (unsigned) sizeof(programStats.movelist) - 1);
7163                     }
7164
7165                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7166                 } else {
7167                     sprintf(programStats.movelist, " no PV\n");
7168                 }
7169
7170                 if (programStats.seen_stat) {
7171                     programStats.ok_to_send = 1;
7172                 }
7173
7174                 if (strchr(programStats.movelist, '(') != NULL) {
7175                     programStats.line_is_book = 1;
7176                     programStats.nr_moves = 0;
7177                     programStats.moves_left = 0;
7178                 } else {
7179                     programStats.line_is_book = 0;
7180                 }
7181
7182                 SendProgramStatsToFrontend( cps, &programStats );
7183
7184                 /* 
7185                     [AS] Protect the thinkOutput buffer from overflow... this
7186                     is only useful if buf1 hasn't overflowed first!
7187                 */
7188                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7189                         plylev, 
7190                         (gameMode == TwoMachinesPlay ?
7191                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7192                         ((double) curscore) / 100.0,
7193                         prefixHint ? lastHint : "",
7194                         prefixHint ? " " : "" );
7195
7196                 if( buf1[0] != NULLCHAR ) {
7197                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7198
7199                     if( strlen(buf1) > max_len ) {
7200                         if( appData.debugMode) {
7201                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7202                         }
7203                         buf1[max_len+1] = '\0';
7204                     }
7205
7206                     strcat( thinkOutput, buf1 );
7207                 }
7208
7209                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7210                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7211                     DisplayMove(currentMove - 1);
7212                 }
7213                 return;
7214
7215             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7216                 /* crafty (9.25+) says "(only move) <move>"
7217                  * if there is only 1 legal move
7218                  */
7219                 sscanf(p, "(only move) %s", buf1);
7220                 sprintf(thinkOutput, "%s (only move)", buf1);
7221                 sprintf(programStats.movelist, "%s (only move)", buf1);
7222                 programStats.depth = 1;
7223                 programStats.nr_moves = 1;
7224                 programStats.moves_left = 1;
7225                 programStats.nodes = 1;
7226                 programStats.time = 1;
7227                 programStats.got_only_move = 1;
7228
7229                 /* Not really, but we also use this member to
7230                    mean "line isn't going to change" (Crafty
7231                    isn't searching, so stats won't change) */
7232                 programStats.line_is_book = 1;
7233
7234                 SendProgramStatsToFrontend( cps, &programStats );
7235                 
7236                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7237                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7238                     DisplayMove(currentMove - 1);
7239                 }
7240                 return;
7241             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7242                               &time, &nodes, &plylev, &mvleft,
7243                               &mvtot, mvname) >= 5) {
7244                 /* The stat01: line is from Crafty (9.29+) in response
7245                    to the "." command */
7246                 programStats.seen_stat = 1;
7247                 cps->maybeThinking = TRUE;
7248
7249                 if (programStats.got_only_move || !appData.periodicUpdates)
7250                   return;
7251
7252                 programStats.depth = plylev;
7253                 programStats.time = time;
7254                 programStats.nodes = nodes;
7255                 programStats.moves_left = mvleft;
7256                 programStats.nr_moves = mvtot;
7257                 strcpy(programStats.move_name, mvname);
7258                 programStats.ok_to_send = 1;
7259                 programStats.movelist[0] = '\0';
7260
7261                 SendProgramStatsToFrontend( cps, &programStats );
7262
7263                 return;
7264
7265             } else if (strncmp(message,"++",2) == 0) {
7266                 /* Crafty 9.29+ outputs this */
7267                 programStats.got_fail = 2;
7268                 return;
7269
7270             } else if (strncmp(message,"--",2) == 0) {
7271                 /* Crafty 9.29+ outputs this */
7272                 programStats.got_fail = 1;
7273                 return;
7274
7275             } else if (thinkOutput[0] != NULLCHAR &&
7276                        strncmp(message, "    ", 4) == 0) {
7277                 unsigned message_len;
7278
7279                 p = message;
7280                 while (*p && *p == ' ') p++;
7281
7282                 message_len = strlen( p );
7283
7284                 /* [AS] Avoid buffer overflow */
7285                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7286                     strcat(thinkOutput, " ");
7287                     strcat(thinkOutput, p);
7288                 }
7289
7290                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7291                     strcat(programStats.movelist, " ");
7292                     strcat(programStats.movelist, p);
7293                 }
7294
7295                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7296                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7297                     DisplayMove(currentMove - 1);
7298                 }
7299                 return;
7300             }
7301         }
7302         else {
7303             buf1[0] = NULLCHAR;
7304
7305             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7306                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7307             {
7308                 ChessProgramStats cpstats;
7309
7310                 if (plyext != ' ' && plyext != '\t') {
7311                     time *= 100;
7312                 }
7313
7314                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7315                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7316                     curscore = -curscore;
7317                 }
7318
7319                 cpstats.depth = plylev;
7320                 cpstats.nodes = nodes;
7321                 cpstats.time = time;
7322                 cpstats.score = curscore;
7323                 cpstats.got_only_move = 0;
7324                 cpstats.movelist[0] = '\0';
7325
7326                 if (buf1[0] != NULLCHAR) {
7327                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7328                 }
7329
7330                 cpstats.ok_to_send = 0;
7331                 cpstats.line_is_book = 0;
7332                 cpstats.nr_moves = 0;
7333                 cpstats.moves_left = 0;
7334
7335                 SendProgramStatsToFrontend( cps, &cpstats );
7336             }
7337         }
7338     }
7339 }
7340
7341
7342 /* Parse a game score from the character string "game", and
7343    record it as the history of the current game.  The game
7344    score is NOT assumed to start from the standard position. 
7345    The display is not updated in any way.
7346    */
7347 void
7348 ParseGameHistory(game)
7349      char *game;
7350 {
7351     ChessMove moveType;
7352     int fromX, fromY, toX, toY, boardIndex;
7353     char promoChar;
7354     char *p, *q;
7355     char buf[MSG_SIZ];
7356
7357     if (appData.debugMode)
7358       fprintf(debugFP, "Parsing game history: %s\n", game);
7359
7360     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7361     gameInfo.site = StrSave(appData.icsHost);
7362     gameInfo.date = PGNDate();
7363     gameInfo.round = StrSave("-");
7364
7365     /* Parse out names of players */
7366     while (*game == ' ') game++;
7367     p = buf;
7368     while (*game != ' ') *p++ = *game++;
7369     *p = NULLCHAR;
7370     gameInfo.white = StrSave(buf);
7371     while (*game == ' ') game++;
7372     p = buf;
7373     while (*game != ' ' && *game != '\n') *p++ = *game++;
7374     *p = NULLCHAR;
7375     gameInfo.black = StrSave(buf);
7376
7377     /* Parse moves */
7378     boardIndex = blackPlaysFirst ? 1 : 0;
7379     yynewstr(game);
7380     for (;;) {
7381         yyboardindex = boardIndex;
7382         moveType = (ChessMove) yylex();
7383         switch (moveType) {
7384           case IllegalMove:             /* maybe suicide chess, etc. */
7385   if (appData.debugMode) {
7386     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7387     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7388     setbuf(debugFP, NULL);
7389   }
7390           case WhitePromotionChancellor:
7391           case BlackPromotionChancellor:
7392           case WhitePromotionArchbishop:
7393           case BlackPromotionArchbishop:
7394           case WhitePromotionQueen:
7395           case BlackPromotionQueen:
7396           case WhitePromotionRook:
7397           case BlackPromotionRook:
7398           case WhitePromotionBishop:
7399           case BlackPromotionBishop:
7400           case WhitePromotionKnight:
7401           case BlackPromotionKnight:
7402           case WhitePromotionKing:
7403           case BlackPromotionKing:
7404           case NormalMove:
7405           case WhiteCapturesEnPassant:
7406           case BlackCapturesEnPassant:
7407           case WhiteKingSideCastle:
7408           case WhiteQueenSideCastle:
7409           case BlackKingSideCastle:
7410           case BlackQueenSideCastle:
7411           case WhiteKingSideCastleWild:
7412           case WhiteQueenSideCastleWild:
7413           case BlackKingSideCastleWild:
7414           case BlackQueenSideCastleWild:
7415           /* PUSH Fabien */
7416           case WhiteHSideCastleFR:
7417           case WhiteASideCastleFR:
7418           case BlackHSideCastleFR:
7419           case BlackASideCastleFR:
7420           /* POP Fabien */
7421             fromX = currentMoveString[0] - AAA;
7422             fromY = currentMoveString[1] - ONE;
7423             toX = currentMoveString[2] - AAA;
7424             toY = currentMoveString[3] - ONE;
7425             promoChar = currentMoveString[4];
7426             break;
7427           case WhiteDrop:
7428           case BlackDrop:
7429             fromX = moveType == WhiteDrop ?
7430               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7431             (int) CharToPiece(ToLower(currentMoveString[0]));
7432             fromY = DROP_RANK;
7433             toX = currentMoveString[2] - AAA;
7434             toY = currentMoveString[3] - ONE;
7435             promoChar = NULLCHAR;
7436             break;
7437           case AmbiguousMove:
7438             /* bug? */
7439             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7440   if (appData.debugMode) {
7441     fprintf(debugFP, "Ambiguous 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 ImpossibleMove:
7448             /* bug? */
7449             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7450   if (appData.debugMode) {
7451     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7452     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7453     setbuf(debugFP, NULL);
7454   }
7455             DisplayError(buf, 0);
7456             return;
7457           case (ChessMove) 0:   /* end of file */
7458             if (boardIndex < backwardMostMove) {
7459                 /* Oops, gap.  How did that happen? */
7460                 DisplayError(_("Gap in move list"), 0);
7461                 return;
7462             }
7463             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7464             if (boardIndex > forwardMostMove) {
7465                 forwardMostMove = boardIndex;
7466             }
7467             return;
7468           case ElapsedTime:
7469             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7470                 strcat(parseList[boardIndex-1], " ");
7471                 strcat(parseList[boardIndex-1], yy_text);
7472             }
7473             continue;
7474           case Comment:
7475           case PGNTag:
7476           case NAG:
7477           default:
7478             /* ignore */
7479             continue;
7480           case WhiteWins:
7481           case BlackWins:
7482           case GameIsDrawn:
7483           case GameUnfinished:
7484             if (gameMode == IcsExamining) {
7485                 if (boardIndex < backwardMostMove) {
7486                     /* Oops, gap.  How did that happen? */
7487                     return;
7488                 }
7489                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7490                 return;
7491             }
7492             gameInfo.result = moveType;
7493             p = strchr(yy_text, '{');
7494             if (p == NULL) p = strchr(yy_text, '(');
7495             if (p == NULL) {
7496                 p = yy_text;
7497                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7498             } else {
7499                 q = strchr(p, *p == '{' ? '}' : ')');
7500                 if (q != NULL) *q = NULLCHAR;
7501                 p++;
7502             }
7503             gameInfo.resultDetails = StrSave(p);
7504             continue;
7505         }
7506         if (boardIndex >= forwardMostMove &&
7507             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7508             backwardMostMove = blackPlaysFirst ? 1 : 0;
7509             return;
7510         }
7511         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7512                                  fromY, fromX, toY, toX, promoChar,
7513                                  parseList[boardIndex]);
7514         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7515         /* currentMoveString is set as a side-effect of yylex */
7516         strcpy(moveList[boardIndex], currentMoveString);
7517         strcat(moveList[boardIndex], "\n");
7518         boardIndex++;
7519         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7520         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7521           case MT_NONE:
7522           case MT_STALEMATE:
7523           default:
7524             break;
7525           case MT_CHECK:
7526             if(gameInfo.variant != VariantShogi)
7527                 strcat(parseList[boardIndex - 1], "+");
7528             break;
7529           case MT_CHECKMATE:
7530           case MT_STAINMATE:
7531             strcat(parseList[boardIndex - 1], "#");
7532             break;
7533         }
7534     }
7535 }
7536
7537
7538 /* Apply a move to the given board  */
7539 void
7540 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7541      int fromX, fromY, toX, toY;
7542      int promoChar;
7543      Board board;
7544 {
7545   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7546
7547     /* [HGM] compute & store e.p. status and castling rights for new position */
7548     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7549     { int i;
7550
7551       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7552       oldEP = (signed char)board[EP_STATUS];
7553       board[EP_STATUS] = EP_NONE;
7554
7555       if( board[toY][toX] != EmptySquare ) 
7556            board[EP_STATUS] = EP_CAPTURE;  
7557
7558       if( board[fromY][fromX] == WhitePawn ) {
7559            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7560                board[EP_STATUS] = EP_PAWN_MOVE;
7561            if( toY-fromY==2) {
7562                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7563                         gameInfo.variant != VariantBerolina || toX < fromX)
7564                       board[EP_STATUS] = toX | berolina;
7565                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7566                         gameInfo.variant != VariantBerolina || toX > fromX) 
7567                       board[EP_STATUS] = toX;
7568            }
7569       } else 
7570       if( board[fromY][fromX] == BlackPawn ) {
7571            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7572                board[EP_STATUS] = EP_PAWN_MOVE; 
7573            if( toY-fromY== -2) {
7574                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7575                         gameInfo.variant != VariantBerolina || toX < fromX)
7576                       board[EP_STATUS] = toX | berolina;
7577                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7578                         gameInfo.variant != VariantBerolina || toX > fromX) 
7579                       board[EP_STATUS] = toX;
7580            }
7581        }
7582
7583        for(i=0; i<nrCastlingRights; i++) {
7584            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7585               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7586              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7587        }
7588
7589     }
7590
7591   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7592   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7593        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7594          
7595   if (fromX == toX && fromY == toY) return;
7596
7597   if (fromY == DROP_RANK) {
7598         /* must be first */
7599         piece = board[toY][toX] = (ChessSquare) fromX;
7600   } else {
7601      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7602      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7603      if(gameInfo.variant == VariantKnightmate)
7604          king += (int) WhiteUnicorn - (int) WhiteKing;
7605
7606     /* Code added by Tord: */
7607     /* FRC castling assumed when king captures friendly rook. */
7608     if (board[fromY][fromX] == WhiteKing &&
7609              board[toY][toX] == WhiteRook) {
7610       board[fromY][fromX] = EmptySquare;
7611       board[toY][toX] = EmptySquare;
7612       if(toX > fromX) {
7613         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7614       } else {
7615         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7616       }
7617     } else if (board[fromY][fromX] == BlackKing &&
7618                board[toY][toX] == BlackRook) {
7619       board[fromY][fromX] = EmptySquare;
7620       board[toY][toX] = EmptySquare;
7621       if(toX > fromX) {
7622         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7623       } else {
7624         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7625       }
7626     /* End of code added by Tord */
7627
7628     } else if (board[fromY][fromX] == king
7629         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7630         && toY == fromY && toX > fromX+1) {
7631         board[fromY][fromX] = EmptySquare;
7632         board[toY][toX] = king;
7633         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7634         board[fromY][BOARD_RGHT-1] = EmptySquare;
7635     } else if (board[fromY][fromX] == king
7636         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7637                && toY == fromY && toX < fromX-1) {
7638         board[fromY][fromX] = EmptySquare;
7639         board[toY][toX] = king;
7640         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7641         board[fromY][BOARD_LEFT] = EmptySquare;
7642     } else if (board[fromY][fromX] == WhitePawn
7643                && toY == BOARD_HEIGHT-1
7644                && gameInfo.variant != VariantXiangqi
7645                ) {
7646         /* white pawn promotion */
7647         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7648         if (board[toY][toX] == EmptySquare) {
7649             board[toY][toX] = WhiteQueen;
7650         }
7651         if(gameInfo.variant==VariantBughouse ||
7652            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7653             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7654         board[fromY][fromX] = EmptySquare;
7655     } else if ((fromY == BOARD_HEIGHT-4)
7656                && (toX != fromX)
7657                && gameInfo.variant != VariantXiangqi
7658                && gameInfo.variant != VariantBerolina
7659                && (board[fromY][fromX] == WhitePawn)
7660                && (board[toY][toX] == EmptySquare)) {
7661         board[fromY][fromX] = EmptySquare;
7662         board[toY][toX] = WhitePawn;
7663         captured = board[toY - 1][toX];
7664         board[toY - 1][toX] = EmptySquare;
7665     } else if ((fromY == BOARD_HEIGHT-4)
7666                && (toX == fromX)
7667                && gameInfo.variant == VariantBerolina
7668                && (board[fromY][fromX] == WhitePawn)
7669                && (board[toY][toX] == EmptySquare)) {
7670         board[fromY][fromX] = EmptySquare;
7671         board[toY][toX] = WhitePawn;
7672         if(oldEP & EP_BEROLIN_A) {
7673                 captured = board[fromY][fromX-1];
7674                 board[fromY][fromX-1] = EmptySquare;
7675         }else{  captured = board[fromY][fromX+1];
7676                 board[fromY][fromX+1] = EmptySquare;
7677         }
7678     } else if (board[fromY][fromX] == king
7679         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7680                && toY == fromY && toX > fromX+1) {
7681         board[fromY][fromX] = EmptySquare;
7682         board[toY][toX] = king;
7683         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7684         board[fromY][BOARD_RGHT-1] = EmptySquare;
7685     } else if (board[fromY][fromX] == king
7686         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7687                && toY == fromY && toX < fromX-1) {
7688         board[fromY][fromX] = EmptySquare;
7689         board[toY][toX] = king;
7690         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7691         board[fromY][BOARD_LEFT] = EmptySquare;
7692     } else if (fromY == 7 && fromX == 3
7693                && board[fromY][fromX] == BlackKing
7694                && toY == 7 && toX == 5) {
7695         board[fromY][fromX] = EmptySquare;
7696         board[toY][toX] = BlackKing;
7697         board[fromY][7] = EmptySquare;
7698         board[toY][4] = BlackRook;
7699     } else if (fromY == 7 && fromX == 3
7700                && board[fromY][fromX] == BlackKing
7701                && toY == 7 && toX == 1) {
7702         board[fromY][fromX] = EmptySquare;
7703         board[toY][toX] = BlackKing;
7704         board[fromY][0] = EmptySquare;
7705         board[toY][2] = BlackRook;
7706     } else if (board[fromY][fromX] == BlackPawn
7707                && toY == 0
7708                && gameInfo.variant != VariantXiangqi
7709                ) {
7710         /* black pawn promotion */
7711         board[0][toX] = CharToPiece(ToLower(promoChar));
7712         if (board[0][toX] == EmptySquare) {
7713             board[0][toX] = BlackQueen;
7714         }
7715         if(gameInfo.variant==VariantBughouse ||
7716            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7717             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7718         board[fromY][fromX] = EmptySquare;
7719     } else if ((fromY == 3)
7720                && (toX != fromX)
7721                && gameInfo.variant != VariantXiangqi
7722                && gameInfo.variant != VariantBerolina
7723                && (board[fromY][fromX] == BlackPawn)
7724                && (board[toY][toX] == EmptySquare)) {
7725         board[fromY][fromX] = EmptySquare;
7726         board[toY][toX] = BlackPawn;
7727         captured = board[toY + 1][toX];
7728         board[toY + 1][toX] = EmptySquare;
7729     } else if ((fromY == 3)
7730                && (toX == fromX)
7731                && gameInfo.variant == VariantBerolina
7732                && (board[fromY][fromX] == BlackPawn)
7733                && (board[toY][toX] == EmptySquare)) {
7734         board[fromY][fromX] = EmptySquare;
7735         board[toY][toX] = BlackPawn;
7736         if(oldEP & EP_BEROLIN_A) {
7737                 captured = board[fromY][fromX-1];
7738                 board[fromY][fromX-1] = EmptySquare;
7739         }else{  captured = board[fromY][fromX+1];
7740                 board[fromY][fromX+1] = EmptySquare;
7741         }
7742     } else {
7743         board[toY][toX] = board[fromY][fromX];
7744         board[fromY][fromX] = EmptySquare;
7745     }
7746
7747     /* [HGM] now we promote for Shogi, if needed */
7748     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7749         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7750   }
7751
7752     if (gameInfo.holdingsWidth != 0) {
7753
7754       /* !!A lot more code needs to be written to support holdings  */
7755       /* [HGM] OK, so I have written it. Holdings are stored in the */
7756       /* penultimate board files, so they are automaticlly stored   */
7757       /* in the game history.                                       */
7758       if (fromY == DROP_RANK) {
7759         /* Delete from holdings, by decreasing count */
7760         /* and erasing image if necessary            */
7761         p = (int) fromX;
7762         if(p < (int) BlackPawn) { /* white drop */
7763              p -= (int)WhitePawn;
7764                  p = PieceToNumber((ChessSquare)p);
7765              if(p >= gameInfo.holdingsSize) p = 0;
7766              if(--board[p][BOARD_WIDTH-2] <= 0)
7767                   board[p][BOARD_WIDTH-1] = EmptySquare;
7768              if((int)board[p][BOARD_WIDTH-2] < 0)
7769                         board[p][BOARD_WIDTH-2] = 0;
7770         } else {                  /* black drop */
7771              p -= (int)BlackPawn;
7772                  p = PieceToNumber((ChessSquare)p);
7773              if(p >= gameInfo.holdingsSize) p = 0;
7774              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7775                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7776              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7777                         board[BOARD_HEIGHT-1-p][1] = 0;
7778         }
7779       }
7780       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7781           && gameInfo.variant != VariantBughouse        ) {
7782         /* [HGM] holdings: Add to holdings, if holdings exist */
7783         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7784                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7785                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7786         }
7787         p = (int) captured;
7788         if (p >= (int) BlackPawn) {
7789           p -= (int)BlackPawn;
7790           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7791                   /* in Shogi restore piece to its original  first */
7792                   captured = (ChessSquare) (DEMOTED captured);
7793                   p = DEMOTED p;
7794           }
7795           p = PieceToNumber((ChessSquare)p);
7796           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7797           board[p][BOARD_WIDTH-2]++;
7798           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7799         } else {
7800           p -= (int)WhitePawn;
7801           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7802                   captured = (ChessSquare) (DEMOTED captured);
7803                   p = DEMOTED p;
7804           }
7805           p = PieceToNumber((ChessSquare)p);
7806           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7807           board[BOARD_HEIGHT-1-p][1]++;
7808           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7809         }
7810       }
7811     } else if (gameInfo.variant == VariantAtomic) {
7812       if (captured != EmptySquare) {
7813         int y, x;
7814         for (y = toY-1; y <= toY+1; y++) {
7815           for (x = toX-1; x <= toX+1; x++) {
7816             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7817                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7818               board[y][x] = EmptySquare;
7819             }
7820           }
7821         }
7822         board[toY][toX] = EmptySquare;
7823       }
7824     }
7825     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7826         /* [HGM] Shogi promotions */
7827         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7828     }
7829
7830     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7831                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7832         // [HGM] superchess: take promotion piece out of holdings
7833         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7834         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7835             if(!--board[k][BOARD_WIDTH-2])
7836                 board[k][BOARD_WIDTH-1] = EmptySquare;
7837         } else {
7838             if(!--board[BOARD_HEIGHT-1-k][1])
7839                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7840         }
7841     }
7842
7843 }
7844
7845 /* Updates forwardMostMove */
7846 void
7847 MakeMove(fromX, fromY, toX, toY, promoChar)
7848      int fromX, fromY, toX, toY;
7849      int promoChar;
7850 {
7851 //    forwardMostMove++; // [HGM] bare: moved downstream
7852
7853     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7854         int timeLeft; static int lastLoadFlag=0; int king, piece;
7855         piece = boards[forwardMostMove][fromY][fromX];
7856         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7857         if(gameInfo.variant == VariantKnightmate)
7858             king += (int) WhiteUnicorn - (int) WhiteKing;
7859         if(forwardMostMove == 0) {
7860             if(blackPlaysFirst) 
7861                 fprintf(serverMoves, "%s;", second.tidy);
7862             fprintf(serverMoves, "%s;", first.tidy);
7863             if(!blackPlaysFirst) 
7864                 fprintf(serverMoves, "%s;", second.tidy);
7865         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7866         lastLoadFlag = loadFlag;
7867         // print base move
7868         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7869         // print castling suffix
7870         if( toY == fromY && piece == king ) {
7871             if(toX-fromX > 1)
7872                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7873             if(fromX-toX >1)
7874                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7875         }
7876         // e.p. suffix
7877         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7878              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7879              boards[forwardMostMove][toY][toX] == EmptySquare
7880              && fromX != toX )
7881                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7882         // promotion suffix
7883         if(promoChar != NULLCHAR)
7884                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7885         if(!loadFlag) {
7886             fprintf(serverMoves, "/%d/%d",
7887                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7888             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7889             else                      timeLeft = blackTimeRemaining/1000;
7890             fprintf(serverMoves, "/%d", timeLeft);
7891         }
7892         fflush(serverMoves);
7893     }
7894
7895     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7896       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7897                         0, 1);
7898       return;
7899     }
7900     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7901     if (commentList[forwardMostMove+1] != NULL) {
7902         free(commentList[forwardMostMove+1]);
7903         commentList[forwardMostMove+1] = NULL;
7904     }
7905     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7906     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7907     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7908     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7909     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7910     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7911     gameInfo.result = GameUnfinished;
7912     if (gameInfo.resultDetails != NULL) {
7913         free(gameInfo.resultDetails);
7914         gameInfo.resultDetails = NULL;
7915     }
7916     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7917                               moveList[forwardMostMove - 1]);
7918     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7919                              PosFlags(forwardMostMove - 1),
7920                              fromY, fromX, toY, toX, promoChar,
7921                              parseList[forwardMostMove - 1]);
7922     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7923       case MT_NONE:
7924       case MT_STALEMATE:
7925       default:
7926         break;
7927       case MT_CHECK:
7928         if(gameInfo.variant != VariantShogi)
7929             strcat(parseList[forwardMostMove - 1], "+");
7930         break;
7931       case MT_CHECKMATE:
7932       case MT_STAINMATE:
7933         strcat(parseList[forwardMostMove - 1], "#");
7934         break;
7935     }
7936     if (appData.debugMode) {
7937         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7938     }
7939
7940 }
7941
7942 /* Updates currentMove if not pausing */
7943 void
7944 ShowMove(fromX, fromY, toX, toY)
7945 {
7946     int instant = (gameMode == PlayFromGameFile) ?
7947         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7948     if(appData.noGUI) return;
7949     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7950         if (!instant) {
7951             if (forwardMostMove == currentMove + 1) {
7952                 AnimateMove(boards[forwardMostMove - 1],
7953                             fromX, fromY, toX, toY);
7954             }
7955             if (appData.highlightLastMove) {
7956                 SetHighlights(fromX, fromY, toX, toY);
7957             }
7958         }
7959         currentMove = forwardMostMove;
7960     }
7961
7962     if (instant) return;
7963
7964     DisplayMove(currentMove - 1);
7965     DrawPosition(FALSE, boards[currentMove]);
7966     DisplayBothClocks();
7967     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7968 }
7969
7970 void SendEgtPath(ChessProgramState *cps)
7971 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7972         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7973
7974         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7975
7976         while(*p) {
7977             char c, *q = name+1, *r, *s;
7978
7979             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7980             while(*p && *p != ',') *q++ = *p++;
7981             *q++ = ':'; *q = 0;
7982             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7983                 strcmp(name, ",nalimov:") == 0 ) {
7984                 // take nalimov path from the menu-changeable option first, if it is defined
7985                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7986                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7987             } else
7988             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7989                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7990                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7991                 s = r = StrStr(s, ":") + 1; // beginning of path info
7992                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7993                 c = *r; *r = 0;             // temporarily null-terminate path info
7994                     *--q = 0;               // strip of trailig ':' from name
7995                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7996                 *r = c;
7997                 SendToProgram(buf,cps);     // send egtbpath command for this format
7998             }
7999             if(*p == ',') p++; // read away comma to position for next format name
8000         }
8001 }
8002
8003 void
8004 InitChessProgram(cps, setup)
8005      ChessProgramState *cps;
8006      int setup; /* [HGM] needed to setup FRC opening position */
8007 {
8008     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8009     if (appData.noChessProgram) return;
8010     hintRequested = FALSE;
8011     bookRequested = FALSE;
8012
8013     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8014     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8015     if(cps->memSize) { /* [HGM] memory */
8016         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8017         SendToProgram(buf, cps);
8018     }
8019     SendEgtPath(cps); /* [HGM] EGT */
8020     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8021         sprintf(buf, "cores %d\n", appData.smpCores);
8022         SendToProgram(buf, cps);
8023     }
8024
8025     SendToProgram(cps->initString, cps);
8026     if (gameInfo.variant != VariantNormal &&
8027         gameInfo.variant != VariantLoadable
8028         /* [HGM] also send variant if board size non-standard */
8029         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8030                                             ) {
8031       char *v = VariantName(gameInfo.variant);
8032       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8033         /* [HGM] in protocol 1 we have to assume all variants valid */
8034         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8035         DisplayFatalError(buf, 0, 1);
8036         return;
8037       }
8038
8039       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8040       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8041       if( gameInfo.variant == VariantXiangqi )
8042            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8043       if( gameInfo.variant == VariantShogi )
8044            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8045       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8046            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8047       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8048                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8049            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8050       if( gameInfo.variant == VariantCourier )
8051            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8052       if( gameInfo.variant == VariantSuper )
8053            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8054       if( gameInfo.variant == VariantGreat )
8055            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8056
8057       if(overruled) {
8058            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8059                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8060            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8061            if(StrStr(cps->variants, b) == NULL) { 
8062                // specific sized variant not known, check if general sizing allowed
8063                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8064                    if(StrStr(cps->variants, "boardsize") == NULL) {
8065                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8066                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8067                        DisplayFatalError(buf, 0, 1);
8068                        return;
8069                    }
8070                    /* [HGM] here we really should compare with the maximum supported board size */
8071                }
8072            }
8073       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8074       sprintf(buf, "variant %s\n", b);
8075       SendToProgram(buf, cps);
8076     }
8077     currentlyInitializedVariant = gameInfo.variant;
8078
8079     /* [HGM] send opening position in FRC to first engine */
8080     if(setup) {
8081           SendToProgram("force\n", cps);
8082           SendBoard(cps, 0);
8083           /* engine is now in force mode! Set flag to wake it up after first move. */
8084           setboardSpoiledMachineBlack = 1;
8085     }
8086
8087     if (cps->sendICS) {
8088       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8089       SendToProgram(buf, cps);
8090     }
8091     cps->maybeThinking = FALSE;
8092     cps->offeredDraw = 0;
8093     if (!appData.icsActive) {
8094         SendTimeControl(cps, movesPerSession, timeControl,
8095                         timeIncrement, appData.searchDepth,
8096                         searchTime);
8097     }
8098     if (appData.showThinking 
8099         // [HGM] thinking: four options require thinking output to be sent
8100         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8101                                 ) {
8102         SendToProgram("post\n", cps);
8103     }
8104     SendToProgram("hard\n", cps);
8105     if (!appData.ponderNextMove) {
8106         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8107            it without being sure what state we are in first.  "hard"
8108            is not a toggle, so that one is OK.
8109          */
8110         SendToProgram("easy\n", cps);
8111     }
8112     if (cps->usePing) {
8113       sprintf(buf, "ping %d\n", ++cps->lastPing);
8114       SendToProgram(buf, cps);
8115     }
8116     cps->initDone = TRUE;
8117 }   
8118
8119
8120 void
8121 StartChessProgram(cps)
8122      ChessProgramState *cps;
8123 {
8124     char buf[MSG_SIZ];
8125     int err;
8126
8127     if (appData.noChessProgram) return;
8128     cps->initDone = FALSE;
8129
8130     if (strcmp(cps->host, "localhost") == 0) {
8131         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8132     } else if (*appData.remoteShell == NULLCHAR) {
8133         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8134     } else {
8135         if (*appData.remoteUser == NULLCHAR) {
8136           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8137                     cps->program);
8138         } else {
8139           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8140                     cps->host, appData.remoteUser, cps->program);
8141         }
8142         err = StartChildProcess(buf, "", &cps->pr);
8143     }
8144     
8145     if (err != 0) {
8146         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8147         DisplayFatalError(buf, err, 1);
8148         cps->pr = NoProc;
8149         cps->isr = NULL;
8150         return;
8151     }
8152     
8153     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8154     if (cps->protocolVersion > 1) {
8155       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8156       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8157       cps->comboCnt = 0;  //                and values of combo boxes
8158       SendToProgram(buf, cps);
8159     } else {
8160       SendToProgram("xboard\n", cps);
8161     }
8162 }
8163
8164
8165 void
8166 TwoMachinesEventIfReady P((void))
8167 {
8168   if (first.lastPing != first.lastPong) {
8169     DisplayMessage("", _("Waiting for first chess program"));
8170     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8171     return;
8172   }
8173   if (second.lastPing != second.lastPong) {
8174     DisplayMessage("", _("Waiting for second chess program"));
8175     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8176     return;
8177   }
8178   ThawUI();
8179   TwoMachinesEvent();
8180 }
8181
8182 void
8183 NextMatchGame P((void))
8184 {
8185     int index; /* [HGM] autoinc: step load index during match */
8186     Reset(FALSE, TRUE);
8187     if (*appData.loadGameFile != NULLCHAR) {
8188         index = appData.loadGameIndex;
8189         if(index < 0) { // [HGM] autoinc
8190             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8191             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8192         } 
8193         LoadGameFromFile(appData.loadGameFile,
8194                          index,
8195                          appData.loadGameFile, FALSE);
8196     } else if (*appData.loadPositionFile != NULLCHAR) {
8197         index = appData.loadPositionIndex;
8198         if(index < 0) { // [HGM] autoinc
8199             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8200             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8201         } 
8202         LoadPositionFromFile(appData.loadPositionFile,
8203                              index,
8204                              appData.loadPositionFile);
8205     }
8206     TwoMachinesEventIfReady();
8207 }
8208
8209 void UserAdjudicationEvent( int result )
8210 {
8211     ChessMove gameResult = GameIsDrawn;
8212
8213     if( result > 0 ) {
8214         gameResult = WhiteWins;
8215     }
8216     else if( result < 0 ) {
8217         gameResult = BlackWins;
8218     }
8219
8220     if( gameMode == TwoMachinesPlay ) {
8221         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8222     }
8223 }
8224
8225
8226 // [HGM] save: calculate checksum of game to make games easily identifiable
8227 int StringCheckSum(char *s)
8228 {
8229         int i = 0;
8230         if(s==NULL) return 0;
8231         while(*s) i = i*259 + *s++;
8232         return i;
8233 }
8234
8235 int GameCheckSum()
8236 {
8237         int i, sum=0;
8238         for(i=backwardMostMove; i<forwardMostMove; i++) {
8239                 sum += pvInfoList[i].depth;
8240                 sum += StringCheckSum(parseList[i]);
8241                 sum += StringCheckSum(commentList[i]);
8242                 sum *= 261;
8243         }
8244         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8245         return sum + StringCheckSum(commentList[i]);
8246 } // end of save patch
8247
8248 void
8249 GameEnds(result, resultDetails, whosays)
8250      ChessMove result;
8251      char *resultDetails;
8252      int whosays;
8253 {
8254     GameMode nextGameMode;
8255     int isIcsGame;
8256     char buf[MSG_SIZ];
8257
8258     if(endingGame) return; /* [HGM] crash: forbid recursion */
8259     endingGame = 1;
8260
8261     if (appData.debugMode) {
8262       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8263               result, resultDetails ? resultDetails : "(null)", whosays);
8264     }
8265
8266     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8267         /* If we are playing on ICS, the server decides when the
8268            game is over, but the engine can offer to draw, claim 
8269            a draw, or resign. 
8270          */
8271 #if ZIPPY
8272         if (appData.zippyPlay && first.initDone) {
8273             if (result == GameIsDrawn) {
8274                 /* In case draw still needs to be claimed */
8275                 SendToICS(ics_prefix);
8276                 SendToICS("draw\n");
8277             } else if (StrCaseStr(resultDetails, "resign")) {
8278                 SendToICS(ics_prefix);
8279                 SendToICS("resign\n");
8280             }
8281         }
8282 #endif
8283         endingGame = 0; /* [HGM] crash */
8284         return;
8285     }
8286
8287     /* If we're loading the game from a file, stop */
8288     if (whosays == GE_FILE) {
8289       (void) StopLoadGameTimer();
8290       gameFileFP = NULL;
8291     }
8292
8293     /* Cancel draw offers */
8294     first.offeredDraw = second.offeredDraw = 0;
8295
8296     /* If this is an ICS game, only ICS can really say it's done;
8297        if not, anyone can. */
8298     isIcsGame = (gameMode == IcsPlayingWhite || 
8299                  gameMode == IcsPlayingBlack || 
8300                  gameMode == IcsObserving    || 
8301                  gameMode == IcsExamining);
8302
8303     if (!isIcsGame || whosays == GE_ICS) {
8304         /* OK -- not an ICS game, or ICS said it was done */
8305         StopClocks();
8306         if (!isIcsGame && !appData.noChessProgram) 
8307           SetUserThinkingEnables();
8308     
8309         /* [HGM] if a machine claims the game end we verify this claim */
8310         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8311             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8312                 char claimer;
8313                 ChessMove trueResult = (ChessMove) -1;
8314
8315                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8316                                             first.twoMachinesColor[0] :
8317                                             second.twoMachinesColor[0] ;
8318
8319                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8320                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8321                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8322                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8323                 } else
8324                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8325                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8326                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8327                 } else
8328                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8329                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8330                 }
8331
8332                 // now verify win claims, but not in drop games, as we don't understand those yet
8333                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8334                                                  || gameInfo.variant == VariantGreat) &&
8335                     (result == WhiteWins && claimer == 'w' ||
8336                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8337                       if (appData.debugMode) {
8338                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8339                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8340                       }
8341                       if(result != trueResult) {
8342                               sprintf(buf, "False win claim: '%s'", resultDetails);
8343                               result = claimer == 'w' ? BlackWins : WhiteWins;
8344                               resultDetails = buf;
8345                       }
8346                 } else
8347                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8348                     && (forwardMostMove <= backwardMostMove ||
8349                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8350                         (claimer=='b')==(forwardMostMove&1))
8351                                                                                   ) {
8352                       /* [HGM] verify: draws that were not flagged are false claims */
8353                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8354                       result = claimer == 'w' ? BlackWins : WhiteWins;
8355                       resultDetails = buf;
8356                 }
8357                 /* (Claiming a loss is accepted no questions asked!) */
8358             }
8359             /* [HGM] bare: don't allow bare King to win */
8360             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8361                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8362                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8363                && result != GameIsDrawn)
8364             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8365                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8366                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8367                         if(p >= 0 && p <= (int)WhiteKing) k++;
8368                 }
8369                 if (appData.debugMode) {
8370                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8371                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8372                 }
8373                 if(k <= 1) {
8374                         result = GameIsDrawn;
8375                         sprintf(buf, "%s but bare king", resultDetails);
8376                         resultDetails = buf;
8377                 }
8378             }
8379         }
8380
8381
8382         if(serverMoves != NULL && !loadFlag) { char c = '=';
8383             if(result==WhiteWins) c = '+';
8384             if(result==BlackWins) c = '-';
8385             if(resultDetails != NULL)
8386                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8387         }
8388         if (resultDetails != NULL) {
8389             gameInfo.result = result;
8390             gameInfo.resultDetails = StrSave(resultDetails);
8391
8392             /* display last move only if game was not loaded from file */
8393             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8394                 DisplayMove(currentMove - 1);
8395     
8396             if (forwardMostMove != 0) {
8397                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8398                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8399                                                                 ) {
8400                     if (*appData.saveGameFile != NULLCHAR) {
8401                         SaveGameToFile(appData.saveGameFile, TRUE);
8402                     } else if (appData.autoSaveGames) {
8403                         AutoSaveGame();
8404                     }
8405                     if (*appData.savePositionFile != NULLCHAR) {
8406                         SavePositionToFile(appData.savePositionFile);
8407                     }
8408                 }
8409             }
8410
8411             /* Tell program how game ended in case it is learning */
8412             /* [HGM] Moved this to after saving the PGN, just in case */
8413             /* engine died and we got here through time loss. In that */
8414             /* case we will get a fatal error writing the pipe, which */
8415             /* would otherwise lose us the PGN.                       */
8416             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8417             /* output during GameEnds should never be fatal anymore   */
8418             if (gameMode == MachinePlaysWhite ||
8419                 gameMode == MachinePlaysBlack ||
8420                 gameMode == TwoMachinesPlay ||
8421                 gameMode == IcsPlayingWhite ||
8422                 gameMode == IcsPlayingBlack ||
8423                 gameMode == BeginningOfGame) {
8424                 char buf[MSG_SIZ];
8425                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8426                         resultDetails);
8427                 if (first.pr != NoProc) {
8428                     SendToProgram(buf, &first);
8429                 }
8430                 if (second.pr != NoProc &&
8431                     gameMode == TwoMachinesPlay) {
8432                     SendToProgram(buf, &second);
8433                 }
8434             }
8435         }
8436
8437         if (appData.icsActive) {
8438             if (appData.quietPlay &&
8439                 (gameMode == IcsPlayingWhite ||
8440                  gameMode == IcsPlayingBlack)) {
8441                 SendToICS(ics_prefix);
8442                 SendToICS("set shout 1\n");
8443             }
8444             nextGameMode = IcsIdle;
8445             ics_user_moved = FALSE;
8446             /* clean up premove.  It's ugly when the game has ended and the
8447              * premove highlights are still on the board.
8448              */
8449             if (gotPremove) {
8450               gotPremove = FALSE;
8451               ClearPremoveHighlights();
8452               DrawPosition(FALSE, boards[currentMove]);
8453             }
8454             if (whosays == GE_ICS) {
8455                 switch (result) {
8456                 case WhiteWins:
8457                     if (gameMode == IcsPlayingWhite)
8458                         PlayIcsWinSound();
8459                     else if(gameMode == IcsPlayingBlack)
8460                         PlayIcsLossSound();
8461                     break;
8462                 case BlackWins:
8463                     if (gameMode == IcsPlayingBlack)
8464                         PlayIcsWinSound();
8465                     else if(gameMode == IcsPlayingWhite)
8466                         PlayIcsLossSound();
8467                     break;
8468                 case GameIsDrawn:
8469                     PlayIcsDrawSound();
8470                     break;
8471                 default:
8472                     PlayIcsUnfinishedSound();
8473                 }
8474             }
8475         } else if (gameMode == EditGame ||
8476                    gameMode == PlayFromGameFile || 
8477                    gameMode == AnalyzeMode || 
8478                    gameMode == AnalyzeFile) {
8479             nextGameMode = gameMode;
8480         } else {
8481             nextGameMode = EndOfGame;
8482         }
8483         pausing = FALSE;
8484         ModeHighlight();
8485     } else {
8486         nextGameMode = gameMode;
8487     }
8488
8489     if (appData.noChessProgram) {
8490         gameMode = nextGameMode;
8491         ModeHighlight();
8492         endingGame = 0; /* [HGM] crash */
8493         return;
8494     }
8495
8496     if (first.reuse) {
8497         /* Put first chess program into idle state */
8498         if (first.pr != NoProc &&
8499             (gameMode == MachinePlaysWhite ||
8500              gameMode == MachinePlaysBlack ||
8501              gameMode == TwoMachinesPlay ||
8502              gameMode == IcsPlayingWhite ||
8503              gameMode == IcsPlayingBlack ||
8504              gameMode == BeginningOfGame)) {
8505             SendToProgram("force\n", &first);
8506             if (first.usePing) {
8507               char buf[MSG_SIZ];
8508               sprintf(buf, "ping %d\n", ++first.lastPing);
8509               SendToProgram(buf, &first);
8510             }
8511         }
8512     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8513         /* Kill off first chess program */
8514         if (first.isr != NULL)
8515           RemoveInputSource(first.isr);
8516         first.isr = NULL;
8517     
8518         if (first.pr != NoProc) {
8519             ExitAnalyzeMode();
8520             DoSleep( appData.delayBeforeQuit );
8521             SendToProgram("quit\n", &first);
8522             DoSleep( appData.delayAfterQuit );
8523             DestroyChildProcess(first.pr, first.useSigterm);
8524         }
8525         first.pr = NoProc;
8526     }
8527     if (second.reuse) {
8528         /* Put second chess program into idle state */
8529         if (second.pr != NoProc &&
8530             gameMode == TwoMachinesPlay) {
8531             SendToProgram("force\n", &second);
8532             if (second.usePing) {
8533               char buf[MSG_SIZ];
8534               sprintf(buf, "ping %d\n", ++second.lastPing);
8535               SendToProgram(buf, &second);
8536             }
8537         }
8538     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8539         /* Kill off second chess program */
8540         if (second.isr != NULL)
8541           RemoveInputSource(second.isr);
8542         second.isr = NULL;
8543     
8544         if (second.pr != NoProc) {
8545             DoSleep( appData.delayBeforeQuit );
8546             SendToProgram("quit\n", &second);
8547             DoSleep( appData.delayAfterQuit );
8548             DestroyChildProcess(second.pr, second.useSigterm);
8549         }
8550         second.pr = NoProc;
8551     }
8552
8553     if (matchMode && gameMode == TwoMachinesPlay) {
8554         switch (result) {
8555         case WhiteWins:
8556           if (first.twoMachinesColor[0] == 'w') {
8557             first.matchWins++;
8558           } else {
8559             second.matchWins++;
8560           }
8561           break;
8562         case BlackWins:
8563           if (first.twoMachinesColor[0] == 'b') {
8564             first.matchWins++;
8565           } else {
8566             second.matchWins++;
8567           }
8568           break;
8569         default:
8570           break;
8571         }
8572         if (matchGame < appData.matchGames) {
8573             char *tmp;
8574             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8575                 tmp = first.twoMachinesColor;
8576                 first.twoMachinesColor = second.twoMachinesColor;
8577                 second.twoMachinesColor = tmp;
8578             }
8579             gameMode = nextGameMode;
8580             matchGame++;
8581             if(appData.matchPause>10000 || appData.matchPause<10)
8582                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8583             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8584             endingGame = 0; /* [HGM] crash */
8585             return;
8586         } else {
8587             char buf[MSG_SIZ];
8588             gameMode = nextGameMode;
8589             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8590                     first.tidy, second.tidy,
8591                     first.matchWins, second.matchWins,
8592                     appData.matchGames - (first.matchWins + second.matchWins));
8593             DisplayFatalError(buf, 0, 0);
8594         }
8595     }
8596     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8597         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8598       ExitAnalyzeMode();
8599     gameMode = nextGameMode;
8600     ModeHighlight();
8601     endingGame = 0;  /* [HGM] crash */
8602 }
8603
8604 /* Assumes program was just initialized (initString sent).
8605    Leaves program in force mode. */
8606 void
8607 FeedMovesToProgram(cps, upto) 
8608      ChessProgramState *cps;
8609      int upto;
8610 {
8611     int i;
8612     
8613     if (appData.debugMode)
8614       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8615               startedFromSetupPosition ? "position and " : "",
8616               backwardMostMove, upto, cps->which);
8617     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8618         // [HGM] variantswitch: make engine aware of new variant
8619         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8620                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8621         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8622         SendToProgram(buf, cps);
8623         currentlyInitializedVariant = gameInfo.variant;
8624     }
8625     SendToProgram("force\n", cps);
8626     if (startedFromSetupPosition) {
8627         SendBoard(cps, backwardMostMove);
8628     if (appData.debugMode) {
8629         fprintf(debugFP, "feedMoves\n");
8630     }
8631     }
8632     for (i = backwardMostMove; i < upto; i++) {
8633         SendMoveToProgram(i, cps);
8634     }
8635 }
8636
8637
8638 void
8639 ResurrectChessProgram()
8640 {
8641      /* The chess program may have exited.
8642         If so, restart it and feed it all the moves made so far. */
8643
8644     if (appData.noChessProgram || first.pr != NoProc) return;
8645     
8646     StartChessProgram(&first);
8647     InitChessProgram(&first, FALSE);
8648     FeedMovesToProgram(&first, currentMove);
8649
8650     if (!first.sendTime) {
8651         /* can't tell gnuchess what its clock should read,
8652            so we bow to its notion. */
8653         ResetClocks();
8654         timeRemaining[0][currentMove] = whiteTimeRemaining;
8655         timeRemaining[1][currentMove] = blackTimeRemaining;
8656     }
8657
8658     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8659                 appData.icsEngineAnalyze) && first.analysisSupport) {
8660       SendToProgram("analyze\n", &first);
8661       first.analyzing = TRUE;
8662     }
8663 }
8664
8665 /*
8666  * Button procedures
8667  */
8668 void
8669 Reset(redraw, init)
8670      int redraw, init;
8671 {
8672     int i;
8673
8674     if (appData.debugMode) {
8675         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8676                 redraw, init, gameMode);
8677     }
8678     CleanupTail(); // [HGM] vari: delete any stored variations
8679     pausing = pauseExamInvalid = FALSE;
8680     startedFromSetupPosition = blackPlaysFirst = FALSE;
8681     firstMove = TRUE;
8682     whiteFlag = blackFlag = FALSE;
8683     userOfferedDraw = FALSE;
8684     hintRequested = bookRequested = FALSE;
8685     first.maybeThinking = FALSE;
8686     second.maybeThinking = FALSE;
8687     first.bookSuspend = FALSE; // [HGM] book
8688     second.bookSuspend = FALSE;
8689     thinkOutput[0] = NULLCHAR;
8690     lastHint[0] = NULLCHAR;
8691     ClearGameInfo(&gameInfo);
8692     gameInfo.variant = StringToVariant(appData.variant);
8693     ics_user_moved = ics_clock_paused = FALSE;
8694     ics_getting_history = H_FALSE;
8695     ics_gamenum = -1;
8696     white_holding[0] = black_holding[0] = NULLCHAR;
8697     ClearProgramStats();
8698     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8699     
8700     ResetFrontEnd();
8701     ClearHighlights();
8702     flipView = appData.flipView;
8703     ClearPremoveHighlights();
8704     gotPremove = FALSE;
8705     alarmSounded = FALSE;
8706
8707     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8708     if(appData.serverMovesName != NULL) {
8709         /* [HGM] prepare to make moves file for broadcasting */
8710         clock_t t = clock();
8711         if(serverMoves != NULL) fclose(serverMoves);
8712         serverMoves = fopen(appData.serverMovesName, "r");
8713         if(serverMoves != NULL) {
8714             fclose(serverMoves);
8715             /* delay 15 sec before overwriting, so all clients can see end */
8716             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8717         }
8718         serverMoves = fopen(appData.serverMovesName, "w");
8719     }
8720
8721     ExitAnalyzeMode();
8722     gameMode = BeginningOfGame;
8723     ModeHighlight();
8724     if(appData.icsActive) gameInfo.variant = VariantNormal;
8725     currentMove = forwardMostMove = backwardMostMove = 0;
8726     InitPosition(redraw);
8727     for (i = 0; i < MAX_MOVES; i++) {
8728         if (commentList[i] != NULL) {
8729             free(commentList[i]);
8730             commentList[i] = NULL;
8731         }
8732     }
8733     ResetClocks();
8734     timeRemaining[0][0] = whiteTimeRemaining;
8735     timeRemaining[1][0] = blackTimeRemaining;
8736     if (first.pr == NULL) {
8737         StartChessProgram(&first);
8738     }
8739     if (init) {
8740             InitChessProgram(&first, startedFromSetupPosition);
8741     }
8742     DisplayTitle("");
8743     DisplayMessage("", "");
8744     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8745     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8746 }
8747
8748 void
8749 AutoPlayGameLoop()
8750 {
8751     for (;;) {
8752         if (!AutoPlayOneMove())
8753           return;
8754         if (matchMode || appData.timeDelay == 0)
8755           continue;
8756         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8757           return;
8758         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8759         break;
8760     }
8761 }
8762
8763
8764 int
8765 AutoPlayOneMove()
8766 {
8767     int fromX, fromY, toX, toY;
8768
8769     if (appData.debugMode) {
8770       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8771     }
8772
8773     if (gameMode != PlayFromGameFile)
8774       return FALSE;
8775
8776     if (currentMove >= forwardMostMove) {
8777       gameMode = EditGame;
8778       ModeHighlight();
8779
8780       /* [AS] Clear current move marker at the end of a game */
8781       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8782
8783       return FALSE;
8784     }
8785     
8786     toX = moveList[currentMove][2] - AAA;
8787     toY = moveList[currentMove][3] - ONE;
8788
8789     if (moveList[currentMove][1] == '@') {
8790         if (appData.highlightLastMove) {
8791             SetHighlights(-1, -1, toX, toY);
8792         }
8793     } else {
8794         fromX = moveList[currentMove][0] - AAA;
8795         fromY = moveList[currentMove][1] - ONE;
8796
8797         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8798
8799         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8800
8801         if (appData.highlightLastMove) {
8802             SetHighlights(fromX, fromY, toX, toY);
8803         }
8804     }
8805     DisplayMove(currentMove);
8806     SendMoveToProgram(currentMove++, &first);
8807     DisplayBothClocks();
8808     DrawPosition(FALSE, boards[currentMove]);
8809     // [HGM] PV info: always display, routine tests if empty
8810     DisplayComment(currentMove - 1, commentList[currentMove]);
8811     return TRUE;
8812 }
8813
8814
8815 int
8816 LoadGameOneMove(readAhead)
8817      ChessMove readAhead;
8818 {
8819     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8820     char promoChar = NULLCHAR;
8821     ChessMove moveType;
8822     char move[MSG_SIZ];
8823     char *p, *q;
8824     
8825     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8826         gameMode != AnalyzeMode && gameMode != Training) {
8827         gameFileFP = NULL;
8828         return FALSE;
8829     }
8830     
8831     yyboardindex = forwardMostMove;
8832     if (readAhead != (ChessMove)0) {
8833       moveType = readAhead;
8834     } else {
8835       if (gameFileFP == NULL)
8836           return FALSE;
8837       moveType = (ChessMove) yylex();
8838     }
8839     
8840     done = FALSE;
8841     switch (moveType) {
8842       case Comment:
8843         if (appData.debugMode) 
8844           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8845         p = yy_text;
8846
8847         /* append the comment but don't display it */
8848         AppendComment(currentMove, p, FALSE);
8849         return TRUE;
8850
8851       case WhiteCapturesEnPassant:
8852       case BlackCapturesEnPassant:
8853       case WhitePromotionChancellor:
8854       case BlackPromotionChancellor:
8855       case WhitePromotionArchbishop:
8856       case BlackPromotionArchbishop:
8857       case WhitePromotionCentaur:
8858       case BlackPromotionCentaur:
8859       case WhitePromotionQueen:
8860       case BlackPromotionQueen:
8861       case WhitePromotionRook:
8862       case BlackPromotionRook:
8863       case WhitePromotionBishop:
8864       case BlackPromotionBishop:
8865       case WhitePromotionKnight:
8866       case BlackPromotionKnight:
8867       case WhitePromotionKing:
8868       case BlackPromotionKing:
8869       case NormalMove:
8870       case WhiteKingSideCastle:
8871       case WhiteQueenSideCastle:
8872       case BlackKingSideCastle:
8873       case BlackQueenSideCastle:
8874       case WhiteKingSideCastleWild:
8875       case WhiteQueenSideCastleWild:
8876       case BlackKingSideCastleWild:
8877       case BlackQueenSideCastleWild:
8878       /* PUSH Fabien */
8879       case WhiteHSideCastleFR:
8880       case WhiteASideCastleFR:
8881       case BlackHSideCastleFR:
8882       case BlackASideCastleFR:
8883       /* POP Fabien */
8884         if (appData.debugMode)
8885           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8886         fromX = currentMoveString[0] - AAA;
8887         fromY = currentMoveString[1] - ONE;
8888         toX = currentMoveString[2] - AAA;
8889         toY = currentMoveString[3] - ONE;
8890         promoChar = currentMoveString[4];
8891         break;
8892
8893       case WhiteDrop:
8894       case BlackDrop:
8895         if (appData.debugMode)
8896           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8897         fromX = moveType == WhiteDrop ?
8898           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8899         (int) CharToPiece(ToLower(currentMoveString[0]));
8900         fromY = DROP_RANK;
8901         toX = currentMoveString[2] - AAA;
8902         toY = currentMoveString[3] - ONE;
8903         break;
8904
8905       case WhiteWins:
8906       case BlackWins:
8907       case GameIsDrawn:
8908       case GameUnfinished:
8909         if (appData.debugMode)
8910           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8911         p = strchr(yy_text, '{');
8912         if (p == NULL) p = strchr(yy_text, '(');
8913         if (p == NULL) {
8914             p = yy_text;
8915             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8916         } else {
8917             q = strchr(p, *p == '{' ? '}' : ')');
8918             if (q != NULL) *q = NULLCHAR;
8919             p++;
8920         }
8921         GameEnds(moveType, p, GE_FILE);
8922         done = TRUE;
8923         if (cmailMsgLoaded) {
8924             ClearHighlights();
8925             flipView = WhiteOnMove(currentMove);
8926             if (moveType == GameUnfinished) flipView = !flipView;
8927             if (appData.debugMode)
8928               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8929         }
8930         break;
8931
8932       case (ChessMove) 0:       /* end of file */
8933         if (appData.debugMode)
8934           fprintf(debugFP, "Parser hit end of file\n");
8935         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8936           case MT_NONE:
8937           case MT_CHECK:
8938             break;
8939           case MT_CHECKMATE:
8940           case MT_STAINMATE:
8941             if (WhiteOnMove(currentMove)) {
8942                 GameEnds(BlackWins, "Black mates", GE_FILE);
8943             } else {
8944                 GameEnds(WhiteWins, "White mates", GE_FILE);
8945             }
8946             break;
8947           case MT_STALEMATE:
8948             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8949             break;
8950         }
8951         done = TRUE;
8952         break;
8953
8954       case MoveNumberOne:
8955         if (lastLoadGameStart == GNUChessGame) {
8956             /* GNUChessGames have numbers, but they aren't move numbers */
8957             if (appData.debugMode)
8958               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8959                       yy_text, (int) moveType);
8960             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8961         }
8962         /* else fall thru */
8963
8964       case XBoardGame:
8965       case GNUChessGame:
8966       case PGNTag:
8967         /* Reached start of next game in file */
8968         if (appData.debugMode)
8969           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8970         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8971           case MT_NONE:
8972           case MT_CHECK:
8973             break;
8974           case MT_CHECKMATE:
8975           case MT_STAINMATE:
8976             if (WhiteOnMove(currentMove)) {
8977                 GameEnds(BlackWins, "Black mates", GE_FILE);
8978             } else {
8979                 GameEnds(WhiteWins, "White mates", GE_FILE);
8980             }
8981             break;
8982           case MT_STALEMATE:
8983             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8984             break;
8985         }
8986         done = TRUE;
8987         break;
8988
8989       case PositionDiagram:     /* should not happen; ignore */
8990       case ElapsedTime:         /* ignore */
8991       case NAG:                 /* ignore */
8992         if (appData.debugMode)
8993           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8994                   yy_text, (int) moveType);
8995         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8996
8997       case IllegalMove:
8998         if (appData.testLegality) {
8999             if (appData.debugMode)
9000               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9001             sprintf(move, _("Illegal move: %d.%s%s"),
9002                     (forwardMostMove / 2) + 1,
9003                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9004             DisplayError(move, 0);
9005             done = TRUE;
9006         } else {
9007             if (appData.debugMode)
9008               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9009                       yy_text, currentMoveString);
9010             fromX = currentMoveString[0] - AAA;
9011             fromY = currentMoveString[1] - ONE;
9012             toX = currentMoveString[2] - AAA;
9013             toY = currentMoveString[3] - ONE;
9014             promoChar = currentMoveString[4];
9015         }
9016         break;
9017
9018       case AmbiguousMove:
9019         if (appData.debugMode)
9020           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9021         sprintf(move, _("Ambiguous move: %d.%s%s"),
9022                 (forwardMostMove / 2) + 1,
9023                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9024         DisplayError(move, 0);
9025         done = TRUE;
9026         break;
9027
9028       default:
9029       case ImpossibleMove:
9030         if (appData.debugMode)
9031           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9032         sprintf(move, _("Illegal move: %d.%s%s"),
9033                 (forwardMostMove / 2) + 1,
9034                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9035         DisplayError(move, 0);
9036         done = TRUE;
9037         break;
9038     }
9039
9040     if (done) {
9041         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9042             DrawPosition(FALSE, boards[currentMove]);
9043             DisplayBothClocks();
9044             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9045               DisplayComment(currentMove - 1, commentList[currentMove]);
9046         }
9047         (void) StopLoadGameTimer();
9048         gameFileFP = NULL;
9049         cmailOldMove = forwardMostMove;
9050         return FALSE;
9051     } else {
9052         /* currentMoveString is set as a side-effect of yylex */
9053         strcat(currentMoveString, "\n");
9054         strcpy(moveList[forwardMostMove], currentMoveString);
9055         
9056         thinkOutput[0] = NULLCHAR;
9057         MakeMove(fromX, fromY, toX, toY, promoChar);
9058         currentMove = forwardMostMove;
9059         return TRUE;
9060     }
9061 }
9062
9063 /* Load the nth game from the given file */
9064 int
9065 LoadGameFromFile(filename, n, title, useList)
9066      char *filename;
9067      int n;
9068      char *title;
9069      /*Boolean*/ int useList;
9070 {
9071     FILE *f;
9072     char buf[MSG_SIZ];
9073
9074     if (strcmp(filename, "-") == 0) {
9075         f = stdin;
9076         title = "stdin";
9077     } else {
9078         f = fopen(filename, "rb");
9079         if (f == NULL) {
9080           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9081             DisplayError(buf, errno);
9082             return FALSE;
9083         }
9084     }
9085     if (fseek(f, 0, 0) == -1) {
9086         /* f is not seekable; probably a pipe */
9087         useList = FALSE;
9088     }
9089     if (useList && n == 0) {
9090         int error = GameListBuild(f);
9091         if (error) {
9092             DisplayError(_("Cannot build game list"), error);
9093         } else if (!ListEmpty(&gameList) &&
9094                    ((ListGame *) gameList.tailPred)->number > 1) {
9095             GameListPopUp(f, title);
9096             return TRUE;
9097         }
9098         GameListDestroy();
9099         n = 1;
9100     }
9101     if (n == 0) n = 1;
9102     return LoadGame(f, n, title, FALSE);
9103 }
9104
9105
9106 void
9107 MakeRegisteredMove()
9108 {
9109     int fromX, fromY, toX, toY;
9110     char promoChar;
9111     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9112         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9113           case CMAIL_MOVE:
9114           case CMAIL_DRAW:
9115             if (appData.debugMode)
9116               fprintf(debugFP, "Restoring %s for game %d\n",
9117                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9118     
9119             thinkOutput[0] = NULLCHAR;
9120             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9121             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9122             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9123             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9124             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9125             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9126             MakeMove(fromX, fromY, toX, toY, promoChar);
9127             ShowMove(fromX, fromY, toX, toY);
9128               
9129             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9130               case MT_NONE:
9131               case MT_CHECK:
9132                 break;
9133                 
9134               case MT_CHECKMATE:
9135               case MT_STAINMATE:
9136                 if (WhiteOnMove(currentMove)) {
9137                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9138                 } else {
9139                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9140                 }
9141                 break;
9142                 
9143               case MT_STALEMATE:
9144                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9145                 break;
9146             }
9147
9148             break;
9149             
9150           case CMAIL_RESIGN:
9151             if (WhiteOnMove(currentMove)) {
9152                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9153             } else {
9154                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9155             }
9156             break;
9157             
9158           case CMAIL_ACCEPT:
9159             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9160             break;
9161               
9162           default:
9163             break;
9164         }
9165     }
9166
9167     return;
9168 }
9169
9170 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9171 int
9172 CmailLoadGame(f, gameNumber, title, useList)
9173      FILE *f;
9174      int gameNumber;
9175      char *title;
9176      int useList;
9177 {
9178     int retVal;
9179
9180     if (gameNumber > nCmailGames) {
9181         DisplayError(_("No more games in this message"), 0);
9182         return FALSE;
9183     }
9184     if (f == lastLoadGameFP) {
9185         int offset = gameNumber - lastLoadGameNumber;
9186         if (offset == 0) {
9187             cmailMsg[0] = NULLCHAR;
9188             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9189                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9190                 nCmailMovesRegistered--;
9191             }
9192             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9193             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9194                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9195             }
9196         } else {
9197             if (! RegisterMove()) return FALSE;
9198         }
9199     }
9200
9201     retVal = LoadGame(f, gameNumber, title, useList);
9202
9203     /* Make move registered during previous look at this game, if any */
9204     MakeRegisteredMove();
9205
9206     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9207         commentList[currentMove]
9208           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9209         DisplayComment(currentMove - 1, commentList[currentMove]);
9210     }
9211
9212     return retVal;
9213 }
9214
9215 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9216 int
9217 ReloadGame(offset)
9218      int offset;
9219 {
9220     int gameNumber = lastLoadGameNumber + offset;
9221     if (lastLoadGameFP == NULL) {
9222         DisplayError(_("No game has been loaded yet"), 0);
9223         return FALSE;
9224     }
9225     if (gameNumber <= 0) {
9226         DisplayError(_("Can't back up any further"), 0);
9227         return FALSE;
9228     }
9229     if (cmailMsgLoaded) {
9230         return CmailLoadGame(lastLoadGameFP, gameNumber,
9231                              lastLoadGameTitle, lastLoadGameUseList);
9232     } else {
9233         return LoadGame(lastLoadGameFP, gameNumber,
9234                         lastLoadGameTitle, lastLoadGameUseList);
9235     }
9236 }
9237
9238
9239
9240 /* Load the nth game from open file f */
9241 int
9242 LoadGame(f, gameNumber, title, useList)
9243      FILE *f;
9244      int gameNumber;
9245      char *title;
9246      int useList;
9247 {
9248     ChessMove cm;
9249     char buf[MSG_SIZ];
9250     int gn = gameNumber;
9251     ListGame *lg = NULL;
9252     int numPGNTags = 0;
9253     int err;
9254     GameMode oldGameMode;
9255     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9256
9257     if (appData.debugMode) 
9258         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9259
9260     if (gameMode == Training )
9261         SetTrainingModeOff();
9262
9263     oldGameMode = gameMode;
9264     if (gameMode != BeginningOfGame) {
9265       Reset(FALSE, TRUE);
9266     }
9267
9268     gameFileFP = f;
9269     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9270         fclose(lastLoadGameFP);
9271     }
9272
9273     if (useList) {
9274         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9275         
9276         if (lg) {
9277             fseek(f, lg->offset, 0);
9278             GameListHighlight(gameNumber);
9279             gn = 1;
9280         }
9281         else {
9282             DisplayError(_("Game number out of range"), 0);
9283             return FALSE;
9284         }
9285     } else {
9286         GameListDestroy();
9287         if (fseek(f, 0, 0) == -1) {
9288             if (f == lastLoadGameFP ?
9289                 gameNumber == lastLoadGameNumber + 1 :
9290                 gameNumber == 1) {
9291                 gn = 1;
9292             } else {
9293                 DisplayError(_("Can't seek on game file"), 0);
9294                 return FALSE;
9295             }
9296         }
9297     }
9298     lastLoadGameFP = f;
9299     lastLoadGameNumber = gameNumber;
9300     strcpy(lastLoadGameTitle, title);
9301     lastLoadGameUseList = useList;
9302
9303     yynewfile(f);
9304
9305     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9306       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9307                 lg->gameInfo.black);
9308             DisplayTitle(buf);
9309     } else if (*title != NULLCHAR) {
9310         if (gameNumber > 1) {
9311             sprintf(buf, "%s %d", title, gameNumber);
9312             DisplayTitle(buf);
9313         } else {
9314             DisplayTitle(title);
9315         }
9316     }
9317
9318     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9319         gameMode = PlayFromGameFile;
9320         ModeHighlight();
9321     }
9322
9323     currentMove = forwardMostMove = backwardMostMove = 0;
9324     CopyBoard(boards[0], initialPosition);
9325     StopClocks();
9326
9327     /*
9328      * Skip the first gn-1 games in the file.
9329      * Also skip over anything that precedes an identifiable 
9330      * start of game marker, to avoid being confused by 
9331      * garbage at the start of the file.  Currently 
9332      * recognized start of game markers are the move number "1",
9333      * the pattern "gnuchess .* game", the pattern
9334      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9335      * A game that starts with one of the latter two patterns
9336      * will also have a move number 1, possibly
9337      * following a position diagram.
9338      * 5-4-02: Let's try being more lenient and allowing a game to
9339      * start with an unnumbered move.  Does that break anything?
9340      */
9341     cm = lastLoadGameStart = (ChessMove) 0;
9342     while (gn > 0) {
9343         yyboardindex = forwardMostMove;
9344         cm = (ChessMove) yylex();
9345         switch (cm) {
9346           case (ChessMove) 0:
9347             if (cmailMsgLoaded) {
9348                 nCmailGames = CMAIL_MAX_GAMES - gn;
9349             } else {
9350                 Reset(TRUE, TRUE);
9351                 DisplayError(_("Game not found in file"), 0);
9352             }
9353             return FALSE;
9354
9355           case GNUChessGame:
9356           case XBoardGame:
9357             gn--;
9358             lastLoadGameStart = cm;
9359             break;
9360             
9361           case MoveNumberOne:
9362             switch (lastLoadGameStart) {
9363               case GNUChessGame:
9364               case XBoardGame:
9365               case PGNTag:
9366                 break;
9367               case MoveNumberOne:
9368               case (ChessMove) 0:
9369                 gn--;           /* count this game */
9370                 lastLoadGameStart = cm;
9371                 break;
9372               default:
9373                 /* impossible */
9374                 break;
9375             }
9376             break;
9377
9378           case PGNTag:
9379             switch (lastLoadGameStart) {
9380               case GNUChessGame:
9381               case PGNTag:
9382               case MoveNumberOne:
9383               case (ChessMove) 0:
9384                 gn--;           /* count this game */
9385                 lastLoadGameStart = cm;
9386                 break;
9387               case XBoardGame:
9388                 lastLoadGameStart = cm; /* game counted already */
9389                 break;
9390               default:
9391                 /* impossible */
9392                 break;
9393             }
9394             if (gn > 0) {
9395                 do {
9396                     yyboardindex = forwardMostMove;
9397                     cm = (ChessMove) yylex();
9398                 } while (cm == PGNTag || cm == Comment);
9399             }
9400             break;
9401
9402           case WhiteWins:
9403           case BlackWins:
9404           case GameIsDrawn:
9405             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9406                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9407                     != CMAIL_OLD_RESULT) {
9408                     nCmailResults ++ ;
9409                     cmailResult[  CMAIL_MAX_GAMES
9410                                 - gn - 1] = CMAIL_OLD_RESULT;
9411                 }
9412             }
9413             break;
9414
9415           case NormalMove:
9416             /* Only a NormalMove can be at the start of a game
9417              * without a position diagram. */
9418             if (lastLoadGameStart == (ChessMove) 0) {
9419               gn--;
9420               lastLoadGameStart = MoveNumberOne;
9421             }
9422             break;
9423
9424           default:
9425             break;
9426         }
9427     }
9428     
9429     if (appData.debugMode)
9430       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9431
9432     if (cm == XBoardGame) {
9433         /* Skip any header junk before position diagram and/or move 1 */
9434         for (;;) {
9435             yyboardindex = forwardMostMove;
9436             cm = (ChessMove) yylex();
9437
9438             if (cm == (ChessMove) 0 ||
9439                 cm == GNUChessGame || cm == XBoardGame) {
9440                 /* Empty game; pretend end-of-file and handle later */
9441                 cm = (ChessMove) 0;
9442                 break;
9443             }
9444
9445             if (cm == MoveNumberOne || cm == PositionDiagram ||
9446                 cm == PGNTag || cm == Comment)
9447               break;
9448         }
9449     } else if (cm == GNUChessGame) {
9450         if (gameInfo.event != NULL) {
9451             free(gameInfo.event);
9452         }
9453         gameInfo.event = StrSave(yy_text);
9454     }   
9455
9456     startedFromSetupPosition = FALSE;
9457     while (cm == PGNTag) {
9458         if (appData.debugMode) 
9459           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9460         err = ParsePGNTag(yy_text, &gameInfo);
9461         if (!err) numPGNTags++;
9462
9463         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9464         if(gameInfo.variant != oldVariant) {
9465             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9466             InitPosition(TRUE);
9467             oldVariant = gameInfo.variant;
9468             if (appData.debugMode) 
9469               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9470         }
9471
9472
9473         if (gameInfo.fen != NULL) {
9474           Board initial_position;
9475           startedFromSetupPosition = TRUE;
9476           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9477             Reset(TRUE, TRUE);
9478             DisplayError(_("Bad FEN position in file"), 0);
9479             return FALSE;
9480           }
9481           CopyBoard(boards[0], initial_position);
9482           if (blackPlaysFirst) {
9483             currentMove = forwardMostMove = backwardMostMove = 1;
9484             CopyBoard(boards[1], initial_position);
9485             strcpy(moveList[0], "");
9486             strcpy(parseList[0], "");
9487             timeRemaining[0][1] = whiteTimeRemaining;
9488             timeRemaining[1][1] = blackTimeRemaining;
9489             if (commentList[0] != NULL) {
9490               commentList[1] = commentList[0];
9491               commentList[0] = NULL;
9492             }
9493           } else {
9494             currentMove = forwardMostMove = backwardMostMove = 0;
9495           }
9496           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9497           {   int i;
9498               initialRulePlies = FENrulePlies;
9499               for( i=0; i< nrCastlingRights; i++ )
9500                   initialRights[i] = initial_position[CASTLING][i];
9501           }
9502           yyboardindex = forwardMostMove;
9503           free(gameInfo.fen);
9504           gameInfo.fen = NULL;
9505         }
9506
9507         yyboardindex = forwardMostMove;
9508         cm = (ChessMove) yylex();
9509
9510         /* Handle comments interspersed among the tags */
9511         while (cm == Comment) {
9512             char *p;
9513             if (appData.debugMode) 
9514               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9515             p = yy_text;
9516             AppendComment(currentMove, p, FALSE);
9517             yyboardindex = forwardMostMove;
9518             cm = (ChessMove) yylex();
9519         }
9520     }
9521
9522     /* don't rely on existence of Event tag since if game was
9523      * pasted from clipboard the Event tag may not exist
9524      */
9525     if (numPGNTags > 0){
9526         char *tags;
9527         if (gameInfo.variant == VariantNormal) {
9528           gameInfo.variant = StringToVariant(gameInfo.event);
9529         }
9530         if (!matchMode) {
9531           if( appData.autoDisplayTags ) {
9532             tags = PGNTags(&gameInfo);
9533             TagsPopUp(tags, CmailMsg());
9534             free(tags);
9535           }
9536         }
9537     } else {
9538         /* Make something up, but don't display it now */
9539         SetGameInfo();
9540         TagsPopDown();
9541     }
9542
9543     if (cm == PositionDiagram) {
9544         int i, j;
9545         char *p;
9546         Board initial_position;
9547
9548         if (appData.debugMode)
9549           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9550
9551         if (!startedFromSetupPosition) {
9552             p = yy_text;
9553             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9554               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9555                 switch (*p) {
9556                   case '[':
9557                   case '-':
9558                   case ' ':
9559                   case '\t':
9560                   case '\n':
9561                   case '\r':
9562                     break;
9563                   default:
9564                     initial_position[i][j++] = CharToPiece(*p);
9565                     break;
9566                 }
9567             while (*p == ' ' || *p == '\t' ||
9568                    *p == '\n' || *p == '\r') p++;
9569         
9570             if (strncmp(p, "black", strlen("black"))==0)
9571               blackPlaysFirst = TRUE;
9572             else
9573               blackPlaysFirst = FALSE;
9574             startedFromSetupPosition = TRUE;
9575         
9576             CopyBoard(boards[0], initial_position);
9577             if (blackPlaysFirst) {
9578                 currentMove = forwardMostMove = backwardMostMove = 1;
9579                 CopyBoard(boards[1], initial_position);
9580                 strcpy(moveList[0], "");
9581                 strcpy(parseList[0], "");
9582                 timeRemaining[0][1] = whiteTimeRemaining;
9583                 timeRemaining[1][1] = blackTimeRemaining;
9584                 if (commentList[0] != NULL) {
9585                     commentList[1] = commentList[0];
9586                     commentList[0] = NULL;
9587                 }
9588             } else {
9589                 currentMove = forwardMostMove = backwardMostMove = 0;
9590             }
9591         }
9592         yyboardindex = forwardMostMove;
9593         cm = (ChessMove) yylex();
9594     }
9595
9596     if (first.pr == NoProc) {
9597         StartChessProgram(&first);
9598     }
9599     InitChessProgram(&first, FALSE);
9600     SendToProgram("force\n", &first);
9601     if (startedFromSetupPosition) {
9602         SendBoard(&first, forwardMostMove);
9603     if (appData.debugMode) {
9604         fprintf(debugFP, "Load Game\n");
9605     }
9606         DisplayBothClocks();
9607     }      
9608
9609     /* [HGM] server: flag to write setup moves in broadcast file as one */
9610     loadFlag = appData.suppressLoadMoves;
9611
9612     while (cm == Comment) {
9613         char *p;
9614         if (appData.debugMode) 
9615           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9616         p = yy_text;
9617         AppendComment(currentMove, p, FALSE);
9618         yyboardindex = forwardMostMove;
9619         cm = (ChessMove) yylex();
9620     }
9621
9622     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9623         cm == WhiteWins || cm == BlackWins ||
9624         cm == GameIsDrawn || cm == GameUnfinished) {
9625         DisplayMessage("", _("No moves in game"));
9626         if (cmailMsgLoaded) {
9627             if (appData.debugMode)
9628               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9629             ClearHighlights();
9630             flipView = FALSE;
9631         }
9632         DrawPosition(FALSE, boards[currentMove]);
9633         DisplayBothClocks();
9634         gameMode = EditGame;
9635         ModeHighlight();
9636         gameFileFP = NULL;
9637         cmailOldMove = 0;
9638         return TRUE;
9639     }
9640
9641     // [HGM] PV info: routine tests if comment empty
9642     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9643         DisplayComment(currentMove - 1, commentList[currentMove]);
9644     }
9645     if (!matchMode && appData.timeDelay != 0) 
9646       DrawPosition(FALSE, boards[currentMove]);
9647
9648     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9649       programStats.ok_to_send = 1;
9650     }
9651
9652     /* if the first token after the PGN tags is a move
9653      * and not move number 1, retrieve it from the parser 
9654      */
9655     if (cm != MoveNumberOne)
9656         LoadGameOneMove(cm);
9657
9658     /* load the remaining moves from the file */
9659     while (LoadGameOneMove((ChessMove)0)) {
9660       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9661       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9662     }
9663
9664     /* rewind to the start of the game */
9665     currentMove = backwardMostMove;
9666
9667     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9668
9669     if (oldGameMode == AnalyzeFile ||
9670         oldGameMode == AnalyzeMode) {
9671       AnalyzeFileEvent();
9672     }
9673
9674     if (matchMode || appData.timeDelay == 0) {
9675       ToEndEvent();
9676       gameMode = EditGame;
9677       ModeHighlight();
9678     } else if (appData.timeDelay > 0) {
9679       AutoPlayGameLoop();
9680     }
9681
9682     if (appData.debugMode) 
9683         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9684
9685     loadFlag = 0; /* [HGM] true game starts */
9686     return TRUE;
9687 }
9688
9689 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9690 int
9691 ReloadPosition(offset)
9692      int offset;
9693 {
9694     int positionNumber = lastLoadPositionNumber + offset;
9695     if (lastLoadPositionFP == NULL) {
9696         DisplayError(_("No position has been loaded yet"), 0);
9697         return FALSE;
9698     }
9699     if (positionNumber <= 0) {
9700         DisplayError(_("Can't back up any further"), 0);
9701         return FALSE;
9702     }
9703     return LoadPosition(lastLoadPositionFP, positionNumber,
9704                         lastLoadPositionTitle);
9705 }
9706
9707 /* Load the nth position from the given file */
9708 int
9709 LoadPositionFromFile(filename, n, title)
9710      char *filename;
9711      int n;
9712      char *title;
9713 {
9714     FILE *f;
9715     char buf[MSG_SIZ];
9716
9717     if (strcmp(filename, "-") == 0) {
9718         return LoadPosition(stdin, n, "stdin");
9719     } else {
9720         f = fopen(filename, "rb");
9721         if (f == NULL) {
9722             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9723             DisplayError(buf, errno);
9724             return FALSE;
9725         } else {
9726             return LoadPosition(f, n, title);
9727         }
9728     }
9729 }
9730
9731 /* Load the nth position from the given open file, and close it */
9732 int
9733 LoadPosition(f, positionNumber, title)
9734      FILE *f;
9735      int positionNumber;
9736      char *title;
9737 {
9738     char *p, line[MSG_SIZ];
9739     Board initial_position;
9740     int i, j, fenMode, pn;
9741     
9742     if (gameMode == Training )
9743         SetTrainingModeOff();
9744
9745     if (gameMode != BeginningOfGame) {
9746         Reset(FALSE, TRUE);
9747     }
9748     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9749         fclose(lastLoadPositionFP);
9750     }
9751     if (positionNumber == 0) positionNumber = 1;
9752     lastLoadPositionFP = f;
9753     lastLoadPositionNumber = positionNumber;
9754     strcpy(lastLoadPositionTitle, title);
9755     if (first.pr == NoProc) {
9756       StartChessProgram(&first);
9757       InitChessProgram(&first, FALSE);
9758     }    
9759     pn = positionNumber;
9760     if (positionNumber < 0) {
9761         /* Negative position number means to seek to that byte offset */
9762         if (fseek(f, -positionNumber, 0) == -1) {
9763             DisplayError(_("Can't seek on position file"), 0);
9764             return FALSE;
9765         };
9766         pn = 1;
9767     } else {
9768         if (fseek(f, 0, 0) == -1) {
9769             if (f == lastLoadPositionFP ?
9770                 positionNumber == lastLoadPositionNumber + 1 :
9771                 positionNumber == 1) {
9772                 pn = 1;
9773             } else {
9774                 DisplayError(_("Can't seek on position file"), 0);
9775                 return FALSE;
9776             }
9777         }
9778     }
9779     /* See if this file is FEN or old-style xboard */
9780     if (fgets(line, MSG_SIZ, f) == NULL) {
9781         DisplayError(_("Position not found in file"), 0);
9782         return FALSE;
9783     }
9784     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9785     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9786
9787     if (pn >= 2) {
9788         if (fenMode || line[0] == '#') pn--;
9789         while (pn > 0) {
9790             /* skip positions before number pn */
9791             if (fgets(line, MSG_SIZ, f) == NULL) {
9792                 Reset(TRUE, TRUE);
9793                 DisplayError(_("Position not found in file"), 0);
9794                 return FALSE;
9795             }
9796             if (fenMode || line[0] == '#') pn--;
9797         }
9798     }
9799
9800     if (fenMode) {
9801         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9802             DisplayError(_("Bad FEN position in file"), 0);
9803             return FALSE;
9804         }
9805     } else {
9806         (void) fgets(line, MSG_SIZ, f);
9807         (void) fgets(line, MSG_SIZ, f);
9808     
9809         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9810             (void) fgets(line, MSG_SIZ, f);
9811             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9812                 if (*p == ' ')
9813                   continue;
9814                 initial_position[i][j++] = CharToPiece(*p);
9815             }
9816         }
9817     
9818         blackPlaysFirst = FALSE;
9819         if (!feof(f)) {
9820             (void) fgets(line, MSG_SIZ, f);
9821             if (strncmp(line, "black", strlen("black"))==0)
9822               blackPlaysFirst = TRUE;
9823         }
9824     }
9825     startedFromSetupPosition = TRUE;
9826     
9827     SendToProgram("force\n", &first);
9828     CopyBoard(boards[0], initial_position);
9829     if (blackPlaysFirst) {
9830         currentMove = forwardMostMove = backwardMostMove = 1;
9831         strcpy(moveList[0], "");
9832         strcpy(parseList[0], "");
9833         CopyBoard(boards[1], initial_position);
9834         DisplayMessage("", _("Black to play"));
9835     } else {
9836         currentMove = forwardMostMove = backwardMostMove = 0;
9837         DisplayMessage("", _("White to play"));
9838     }
9839     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9840     SendBoard(&first, forwardMostMove);
9841     if (appData.debugMode) {
9842 int i, j;
9843   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9844   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9845         fprintf(debugFP, "Load Position\n");
9846     }
9847
9848     if (positionNumber > 1) {
9849         sprintf(line, "%s %d", title, positionNumber);
9850         DisplayTitle(line);
9851     } else {
9852         DisplayTitle(title);
9853     }
9854     gameMode = EditGame;
9855     ModeHighlight();
9856     ResetClocks();
9857     timeRemaining[0][1] = whiteTimeRemaining;
9858     timeRemaining[1][1] = blackTimeRemaining;
9859     DrawPosition(FALSE, boards[currentMove]);
9860    
9861     return TRUE;
9862 }
9863
9864
9865 void
9866 CopyPlayerNameIntoFileName(dest, src)
9867      char **dest, *src;
9868 {
9869     while (*src != NULLCHAR && *src != ',') {
9870         if (*src == ' ') {
9871             *(*dest)++ = '_';
9872             src++;
9873         } else {
9874             *(*dest)++ = *src++;
9875         }
9876     }
9877 }
9878
9879 char *DefaultFileName(ext)
9880      char *ext;
9881 {
9882     static char def[MSG_SIZ];
9883     char *p;
9884
9885     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9886         p = def;
9887         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9888         *p++ = '-';
9889         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9890         *p++ = '.';
9891         strcpy(p, ext);
9892     } else {
9893         def[0] = NULLCHAR;
9894     }
9895     return def;
9896 }
9897
9898 /* Save the current game to the given file */
9899 int
9900 SaveGameToFile(filename, append)
9901      char *filename;
9902      int append;
9903 {
9904     FILE *f;
9905     char buf[MSG_SIZ];
9906
9907     if (strcmp(filename, "-") == 0) {
9908         return SaveGame(stdout, 0, NULL);
9909     } else {
9910         f = fopen(filename, append ? "a" : "w");
9911         if (f == NULL) {
9912             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9913             DisplayError(buf, errno);
9914             return FALSE;
9915         } else {
9916             return SaveGame(f, 0, NULL);
9917         }
9918     }
9919 }
9920
9921 char *
9922 SavePart(str)
9923      char *str;
9924 {
9925     static char buf[MSG_SIZ];
9926     char *p;
9927     
9928     p = strchr(str, ' ');
9929     if (p == NULL) return str;
9930     strncpy(buf, str, p - str);
9931     buf[p - str] = NULLCHAR;
9932     return buf;
9933 }
9934
9935 #define PGN_MAX_LINE 75
9936
9937 #define PGN_SIDE_WHITE  0
9938 #define PGN_SIDE_BLACK  1
9939
9940 /* [AS] */
9941 static int FindFirstMoveOutOfBook( int side )
9942 {
9943     int result = -1;
9944
9945     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9946         int index = backwardMostMove;
9947         int has_book_hit = 0;
9948
9949         if( (index % 2) != side ) {
9950             index++;
9951         }
9952
9953         while( index < forwardMostMove ) {
9954             /* Check to see if engine is in book */
9955             int depth = pvInfoList[index].depth;
9956             int score = pvInfoList[index].score;
9957             int in_book = 0;
9958
9959             if( depth <= 2 ) {
9960                 in_book = 1;
9961             }
9962             else if( score == 0 && depth == 63 ) {
9963                 in_book = 1; /* Zappa */
9964             }
9965             else if( score == 2 && depth == 99 ) {
9966                 in_book = 1; /* Abrok */
9967             }
9968
9969             has_book_hit += in_book;
9970
9971             if( ! in_book ) {
9972                 result = index;
9973
9974                 break;
9975             }
9976
9977             index += 2;
9978         }
9979     }
9980
9981     return result;
9982 }
9983
9984 /* [AS] */
9985 void GetOutOfBookInfo( char * buf )
9986 {
9987     int oob[2];
9988     int i;
9989     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9990
9991     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9992     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9993
9994     *buf = '\0';
9995
9996     if( oob[0] >= 0 || oob[1] >= 0 ) {
9997         for( i=0; i<2; i++ ) {
9998             int idx = oob[i];
9999
10000             if( idx >= 0 ) {
10001                 if( i > 0 && oob[0] >= 0 ) {
10002                     strcat( buf, "   " );
10003                 }
10004
10005                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10006                 sprintf( buf+strlen(buf), "%s%.2f", 
10007                     pvInfoList[idx].score >= 0 ? "+" : "",
10008                     pvInfoList[idx].score / 100.0 );
10009             }
10010         }
10011     }
10012 }
10013
10014 /* Save game in PGN style and close the file */
10015 int
10016 SaveGamePGN(f)
10017      FILE *f;
10018 {
10019     int i, offset, linelen, newblock;
10020     time_t tm;
10021 //    char *movetext;
10022     char numtext[32];
10023     int movelen, numlen, blank;
10024     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10025
10026     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10027     
10028     tm = time((time_t *) NULL);
10029     
10030     PrintPGNTags(f, &gameInfo);
10031     
10032     if (backwardMostMove > 0 || startedFromSetupPosition) {
10033         char *fen = PositionToFEN(backwardMostMove, NULL);
10034         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10035         fprintf(f, "\n{--------------\n");
10036         PrintPosition(f, backwardMostMove);
10037         fprintf(f, "--------------}\n");
10038         free(fen);
10039     }
10040     else {
10041         /* [AS] Out of book annotation */
10042         if( appData.saveOutOfBookInfo ) {
10043             char buf[64];
10044
10045             GetOutOfBookInfo( buf );
10046
10047             if( buf[0] != '\0' ) {
10048                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10049             }
10050         }
10051
10052         fprintf(f, "\n");
10053     }
10054
10055     i = backwardMostMove;
10056     linelen = 0;
10057     newblock = TRUE;
10058
10059     while (i < forwardMostMove) {
10060         /* Print comments preceding this move */
10061         if (commentList[i] != NULL) {
10062             if (linelen > 0) fprintf(f, "\n");
10063             fprintf(f, "%s", commentList[i]);
10064             linelen = 0;
10065             newblock = TRUE;
10066         }
10067
10068         /* Format move number */
10069         if ((i % 2) == 0) {
10070             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10071         } else {
10072             if (newblock) {
10073                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10074             } else {
10075                 numtext[0] = NULLCHAR;
10076             }
10077         }
10078         numlen = strlen(numtext);
10079         newblock = FALSE;
10080
10081         /* Print move number */
10082         blank = linelen > 0 && numlen > 0;
10083         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10084             fprintf(f, "\n");
10085             linelen = 0;
10086             blank = 0;
10087         }
10088         if (blank) {
10089             fprintf(f, " ");
10090             linelen++;
10091         }
10092         fprintf(f, "%s", numtext);
10093         linelen += numlen;
10094
10095         /* Get move */
10096         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10097         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10098
10099         /* Print move */
10100         blank = linelen > 0 && movelen > 0;
10101         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10102             fprintf(f, "\n");
10103             linelen = 0;
10104             blank = 0;
10105         }
10106         if (blank) {
10107             fprintf(f, " ");
10108             linelen++;
10109         }
10110         fprintf(f, "%s", move_buffer);
10111         linelen += movelen;
10112
10113         /* [AS] Add PV info if present */
10114         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10115             /* [HGM] add time */
10116             char buf[MSG_SIZ]; int seconds;
10117
10118             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10119
10120             if( seconds <= 0) buf[0] = 0; else
10121             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10122                 seconds = (seconds + 4)/10; // round to full seconds
10123                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10124                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10125             }
10126
10127             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10128                 pvInfoList[i].score >= 0 ? "+" : "",
10129                 pvInfoList[i].score / 100.0,
10130                 pvInfoList[i].depth,
10131                 buf );
10132
10133             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10134
10135             /* Print score/depth */
10136             blank = linelen > 0 && movelen > 0;
10137             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10138                 fprintf(f, "\n");
10139                 linelen = 0;
10140                 blank = 0;
10141             }
10142             if (blank) {
10143                 fprintf(f, " ");
10144                 linelen++;
10145             }
10146             fprintf(f, "%s", move_buffer);
10147             linelen += movelen;
10148         }
10149
10150         i++;
10151     }
10152     
10153     /* Start a new line */
10154     if (linelen > 0) fprintf(f, "\n");
10155
10156     /* Print comments after last move */
10157     if (commentList[i] != NULL) {
10158         fprintf(f, "%s\n", commentList[i]);
10159     }
10160
10161     /* Print result */
10162     if (gameInfo.resultDetails != NULL &&
10163         gameInfo.resultDetails[0] != NULLCHAR) {
10164         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10165                 PGNResult(gameInfo.result));
10166     } else {
10167         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10168     }
10169
10170     fclose(f);
10171     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10172     return TRUE;
10173 }
10174
10175 /* Save game in old style and close the file */
10176 int
10177 SaveGameOldStyle(f)
10178      FILE *f;
10179 {
10180     int i, offset;
10181     time_t tm;
10182     
10183     tm = time((time_t *) NULL);
10184     
10185     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10186     PrintOpponents(f);
10187     
10188     if (backwardMostMove > 0 || startedFromSetupPosition) {
10189         fprintf(f, "\n[--------------\n");
10190         PrintPosition(f, backwardMostMove);
10191         fprintf(f, "--------------]\n");
10192     } else {
10193         fprintf(f, "\n");
10194     }
10195
10196     i = backwardMostMove;
10197     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10198
10199     while (i < forwardMostMove) {
10200         if (commentList[i] != NULL) {
10201             fprintf(f, "[%s]\n", commentList[i]);
10202         }
10203
10204         if ((i % 2) == 1) {
10205             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10206             i++;
10207         } else {
10208             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10209             i++;
10210             if (commentList[i] != NULL) {
10211                 fprintf(f, "\n");
10212                 continue;
10213             }
10214             if (i >= forwardMostMove) {
10215                 fprintf(f, "\n");
10216                 break;
10217             }
10218             fprintf(f, "%s\n", parseList[i]);
10219             i++;
10220         }
10221     }
10222     
10223     if (commentList[i] != NULL) {
10224         fprintf(f, "[%s]\n", commentList[i]);
10225     }
10226
10227     /* This isn't really the old style, but it's close enough */
10228     if (gameInfo.resultDetails != NULL &&
10229         gameInfo.resultDetails[0] != NULLCHAR) {
10230         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10231                 gameInfo.resultDetails);
10232     } else {
10233         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10234     }
10235
10236     fclose(f);
10237     return TRUE;
10238 }
10239
10240 /* Save the current game to open file f and close the file */
10241 int
10242 SaveGame(f, dummy, dummy2)
10243      FILE *f;
10244      int dummy;
10245      char *dummy2;
10246 {
10247     if (gameMode == EditPosition) EditPositionDone(TRUE);
10248     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10249     if (appData.oldSaveStyle)
10250       return SaveGameOldStyle(f);
10251     else
10252       return SaveGamePGN(f);
10253 }
10254
10255 /* Save the current position to the given file */
10256 int
10257 SavePositionToFile(filename)
10258      char *filename;
10259 {
10260     FILE *f;
10261     char buf[MSG_SIZ];
10262
10263     if (strcmp(filename, "-") == 0) {
10264         return SavePosition(stdout, 0, NULL);
10265     } else {
10266         f = fopen(filename, "a");
10267         if (f == NULL) {
10268             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10269             DisplayError(buf, errno);
10270             return FALSE;
10271         } else {
10272             SavePosition(f, 0, NULL);
10273             return TRUE;
10274         }
10275     }
10276 }
10277
10278 /* Save the current position to the given open file and close the file */
10279 int
10280 SavePosition(f, dummy, dummy2)
10281      FILE *f;
10282      int dummy;
10283      char *dummy2;
10284 {
10285     time_t tm;
10286     char *fen;
10287     
10288     if (gameMode == EditPosition) EditPositionDone(TRUE);
10289     if (appData.oldSaveStyle) {
10290         tm = time((time_t *) NULL);
10291     
10292         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10293         PrintOpponents(f);
10294         fprintf(f, "[--------------\n");
10295         PrintPosition(f, currentMove);
10296         fprintf(f, "--------------]\n");
10297     } else {
10298         fen = PositionToFEN(currentMove, NULL);
10299         fprintf(f, "%s\n", fen);
10300         free(fen);
10301     }
10302     fclose(f);
10303     return TRUE;
10304 }
10305
10306 void
10307 ReloadCmailMsgEvent(unregister)
10308      int unregister;
10309 {
10310 #if !WIN32
10311     static char *inFilename = NULL;
10312     static char *outFilename;
10313     int i;
10314     struct stat inbuf, outbuf;
10315     int status;
10316     
10317     /* Any registered moves are unregistered if unregister is set, */
10318     /* i.e. invoked by the signal handler */
10319     if (unregister) {
10320         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10321             cmailMoveRegistered[i] = FALSE;
10322             if (cmailCommentList[i] != NULL) {
10323                 free(cmailCommentList[i]);
10324                 cmailCommentList[i] = NULL;
10325             }
10326         }
10327         nCmailMovesRegistered = 0;
10328     }
10329
10330     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10331         cmailResult[i] = CMAIL_NOT_RESULT;
10332     }
10333     nCmailResults = 0;
10334
10335     if (inFilename == NULL) {
10336         /* Because the filenames are static they only get malloced once  */
10337         /* and they never get freed                                      */
10338         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10339         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10340
10341         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10342         sprintf(outFilename, "%s.out", appData.cmailGameName);
10343     }
10344     
10345     status = stat(outFilename, &outbuf);
10346     if (status < 0) {
10347         cmailMailedMove = FALSE;
10348     } else {
10349         status = stat(inFilename, &inbuf);
10350         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10351     }
10352     
10353     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10354        counts the games, notes how each one terminated, etc.
10355        
10356        It would be nice to remove this kludge and instead gather all
10357        the information while building the game list.  (And to keep it
10358        in the game list nodes instead of having a bunch of fixed-size
10359        parallel arrays.)  Note this will require getting each game's
10360        termination from the PGN tags, as the game list builder does
10361        not process the game moves.  --mann
10362        */
10363     cmailMsgLoaded = TRUE;
10364     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10365     
10366     /* Load first game in the file or popup game menu */
10367     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10368
10369 #endif /* !WIN32 */
10370     return;
10371 }
10372
10373 int
10374 RegisterMove()
10375 {
10376     FILE *f;
10377     char string[MSG_SIZ];
10378
10379     if (   cmailMailedMove
10380         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10381         return TRUE;            /* Allow free viewing  */
10382     }
10383
10384     /* Unregister move to ensure that we don't leave RegisterMove        */
10385     /* with the move registered when the conditions for registering no   */
10386     /* longer hold                                                       */
10387     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10388         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10389         nCmailMovesRegistered --;
10390
10391         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10392           {
10393               free(cmailCommentList[lastLoadGameNumber - 1]);
10394               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10395           }
10396     }
10397
10398     if (cmailOldMove == -1) {
10399         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10400         return FALSE;
10401     }
10402
10403     if (currentMove > cmailOldMove + 1) {
10404         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10405         return FALSE;
10406     }
10407
10408     if (currentMove < cmailOldMove) {
10409         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10410         return FALSE;
10411     }
10412
10413     if (forwardMostMove > currentMove) {
10414         /* Silently truncate extra moves */
10415         TruncateGame();
10416     }
10417
10418     if (   (currentMove == cmailOldMove + 1)
10419         || (   (currentMove == cmailOldMove)
10420             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10421                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10422         if (gameInfo.result != GameUnfinished) {
10423             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10424         }
10425
10426         if (commentList[currentMove] != NULL) {
10427             cmailCommentList[lastLoadGameNumber - 1]
10428               = StrSave(commentList[currentMove]);
10429         }
10430         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10431
10432         if (appData.debugMode)
10433           fprintf(debugFP, "Saving %s for game %d\n",
10434                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10435
10436         sprintf(string,
10437                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10438         
10439         f = fopen(string, "w");
10440         if (appData.oldSaveStyle) {
10441             SaveGameOldStyle(f); /* also closes the file */
10442             
10443             sprintf(string, "%s.pos.out", appData.cmailGameName);
10444             f = fopen(string, "w");
10445             SavePosition(f, 0, NULL); /* also closes the file */
10446         } else {
10447             fprintf(f, "{--------------\n");
10448             PrintPosition(f, currentMove);
10449             fprintf(f, "--------------}\n\n");
10450             
10451             SaveGame(f, 0, NULL); /* also closes the file*/
10452         }
10453         
10454         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10455         nCmailMovesRegistered ++;
10456     } else if (nCmailGames == 1) {
10457         DisplayError(_("You have not made a move yet"), 0);
10458         return FALSE;
10459     }
10460
10461     return TRUE;
10462 }
10463
10464 void
10465 MailMoveEvent()
10466 {
10467 #if !WIN32
10468     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10469     FILE *commandOutput;
10470     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10471     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10472     int nBuffers;
10473     int i;
10474     int archived;
10475     char *arcDir;
10476
10477     if (! cmailMsgLoaded) {
10478         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10479         return;
10480     }
10481
10482     if (nCmailGames == nCmailResults) {
10483         DisplayError(_("No unfinished games"), 0);
10484         return;
10485     }
10486
10487 #if CMAIL_PROHIBIT_REMAIL
10488     if (cmailMailedMove) {
10489         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);
10490         DisplayError(msg, 0);
10491         return;
10492     }
10493 #endif
10494
10495     if (! (cmailMailedMove || RegisterMove())) return;
10496     
10497     if (   cmailMailedMove
10498         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10499         sprintf(string, partCommandString,
10500                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10501         commandOutput = popen(string, "r");
10502
10503         if (commandOutput == NULL) {
10504             DisplayError(_("Failed to invoke cmail"), 0);
10505         } else {
10506             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10507                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10508             }
10509             if (nBuffers > 1) {
10510                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10511                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10512                 nBytes = MSG_SIZ - 1;
10513             } else {
10514                 (void) memcpy(msg, buffer, nBytes);
10515             }
10516             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10517
10518             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10519                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10520
10521                 archived = TRUE;
10522                 for (i = 0; i < nCmailGames; i ++) {
10523                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10524                         archived = FALSE;
10525                     }
10526                 }
10527                 if (   archived
10528                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10529                         != NULL)) {
10530                     sprintf(buffer, "%s/%s.%s.archive",
10531                             arcDir,
10532                             appData.cmailGameName,
10533                             gameInfo.date);
10534                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10535                     cmailMsgLoaded = FALSE;
10536                 }
10537             }
10538
10539             DisplayInformation(msg);
10540             pclose(commandOutput);
10541         }
10542     } else {
10543         if ((*cmailMsg) != '\0') {
10544             DisplayInformation(cmailMsg);
10545         }
10546     }
10547
10548     return;
10549 #endif /* !WIN32 */
10550 }
10551
10552 char *
10553 CmailMsg()
10554 {
10555 #if WIN32
10556     return NULL;
10557 #else
10558     int  prependComma = 0;
10559     char number[5];
10560     char string[MSG_SIZ];       /* Space for game-list */
10561     int  i;
10562     
10563     if (!cmailMsgLoaded) return "";
10564
10565     if (cmailMailedMove) {
10566         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10567     } else {
10568         /* Create a list of games left */
10569         sprintf(string, "[");
10570         for (i = 0; i < nCmailGames; i ++) {
10571             if (! (   cmailMoveRegistered[i]
10572                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10573                 if (prependComma) {
10574                     sprintf(number, ",%d", i + 1);
10575                 } else {
10576                     sprintf(number, "%d", i + 1);
10577                     prependComma = 1;
10578                 }
10579                 
10580                 strcat(string, number);
10581             }
10582         }
10583         strcat(string, "]");
10584
10585         if (nCmailMovesRegistered + nCmailResults == 0) {
10586             switch (nCmailGames) {
10587               case 1:
10588                 sprintf(cmailMsg,
10589                         _("Still need to make move for game\n"));
10590                 break;
10591                 
10592               case 2:
10593                 sprintf(cmailMsg,
10594                         _("Still need to make moves for both games\n"));
10595                 break;
10596                 
10597               default:
10598                 sprintf(cmailMsg,
10599                         _("Still need to make moves for all %d games\n"),
10600                         nCmailGames);
10601                 break;
10602             }
10603         } else {
10604             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10605               case 1:
10606                 sprintf(cmailMsg,
10607                         _("Still need to make a move for game %s\n"),
10608                         string);
10609                 break;
10610                 
10611               case 0:
10612                 if (nCmailResults == nCmailGames) {
10613                     sprintf(cmailMsg, _("No unfinished games\n"));
10614                 } else {
10615                     sprintf(cmailMsg, _("Ready to send mail\n"));
10616                 }
10617                 break;
10618                 
10619               default:
10620                 sprintf(cmailMsg,
10621                         _("Still need to make moves for games %s\n"),
10622                         string);
10623             }
10624         }
10625     }
10626     return cmailMsg;
10627 #endif /* WIN32 */
10628 }
10629
10630 void
10631 ResetGameEvent()
10632 {
10633     if (gameMode == Training)
10634       SetTrainingModeOff();
10635
10636     Reset(TRUE, TRUE);
10637     cmailMsgLoaded = FALSE;
10638     if (appData.icsActive) {
10639       SendToICS(ics_prefix);
10640       SendToICS("refresh\n");
10641     }
10642 }
10643
10644 void
10645 ExitEvent(status)
10646      int status;
10647 {
10648     exiting++;
10649     if (exiting > 2) {
10650       /* Give up on clean exit */
10651       exit(status);
10652     }
10653     if (exiting > 1) {
10654       /* Keep trying for clean exit */
10655       return;
10656     }
10657
10658     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10659
10660     if (telnetISR != NULL) {
10661       RemoveInputSource(telnetISR);
10662     }
10663     if (icsPR != NoProc) {
10664       DestroyChildProcess(icsPR, TRUE);
10665     }
10666
10667     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10668     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10669
10670     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10671     /* make sure this other one finishes before killing it!                  */
10672     if(endingGame) { int count = 0;
10673         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10674         while(endingGame && count++ < 10) DoSleep(1);
10675         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10676     }
10677
10678     /* Kill off chess programs */
10679     if (first.pr != NoProc) {
10680         ExitAnalyzeMode();
10681         
10682         DoSleep( appData.delayBeforeQuit );
10683         SendToProgram("quit\n", &first);
10684         DoSleep( appData.delayAfterQuit );
10685         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10686     }
10687     if (second.pr != NoProc) {
10688         DoSleep( appData.delayBeforeQuit );
10689         SendToProgram("quit\n", &second);
10690         DoSleep( appData.delayAfterQuit );
10691         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10692     }
10693     if (first.isr != NULL) {
10694         RemoveInputSource(first.isr);
10695     }
10696     if (second.isr != NULL) {
10697         RemoveInputSource(second.isr);
10698     }
10699
10700     ShutDownFrontEnd();
10701     exit(status);
10702 }
10703
10704 void
10705 PauseEvent()
10706 {
10707     if (appData.debugMode)
10708         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10709     if (pausing) {
10710         pausing = FALSE;
10711         ModeHighlight();
10712         if (gameMode == MachinePlaysWhite ||
10713             gameMode == MachinePlaysBlack) {
10714             StartClocks();
10715         } else {
10716             DisplayBothClocks();
10717         }
10718         if (gameMode == PlayFromGameFile) {
10719             if (appData.timeDelay >= 0) 
10720                 AutoPlayGameLoop();
10721         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10722             Reset(FALSE, TRUE);
10723             SendToICS(ics_prefix);
10724             SendToICS("refresh\n");
10725         } else if (currentMove < forwardMostMove) {
10726             ForwardInner(forwardMostMove);
10727         }
10728         pauseExamInvalid = FALSE;
10729     } else {
10730         switch (gameMode) {
10731           default:
10732             return;
10733           case IcsExamining:
10734             pauseExamForwardMostMove = forwardMostMove;
10735             pauseExamInvalid = FALSE;
10736             /* fall through */
10737           case IcsObserving:
10738           case IcsPlayingWhite:
10739           case IcsPlayingBlack:
10740             pausing = TRUE;
10741             ModeHighlight();
10742             return;
10743           case PlayFromGameFile:
10744             (void) StopLoadGameTimer();
10745             pausing = TRUE;
10746             ModeHighlight();
10747             break;
10748           case BeginningOfGame:
10749             if (appData.icsActive) return;
10750             /* else fall through */
10751           case MachinePlaysWhite:
10752           case MachinePlaysBlack:
10753           case TwoMachinesPlay:
10754             if (forwardMostMove == 0)
10755               return;           /* don't pause if no one has moved */
10756             if ((gameMode == MachinePlaysWhite &&
10757                  !WhiteOnMove(forwardMostMove)) ||
10758                 (gameMode == MachinePlaysBlack &&
10759                  WhiteOnMove(forwardMostMove))) {
10760                 StopClocks();
10761             }
10762             pausing = TRUE;
10763             ModeHighlight();
10764             break;
10765         }
10766     }
10767 }
10768
10769 void
10770 EditCommentEvent()
10771 {
10772     char title[MSG_SIZ];
10773
10774     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10775         strcpy(title, _("Edit comment"));
10776     } else {
10777         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10778                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10779                 parseList[currentMove - 1]);
10780     }
10781
10782     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10783 }
10784
10785
10786 void
10787 EditTagsEvent()
10788 {
10789     char *tags = PGNTags(&gameInfo);
10790     EditTagsPopUp(tags);
10791     free(tags);
10792 }
10793
10794 void
10795 AnalyzeModeEvent()
10796 {
10797     if (appData.noChessProgram || gameMode == AnalyzeMode)
10798       return;
10799
10800     if (gameMode != AnalyzeFile) {
10801         if (!appData.icsEngineAnalyze) {
10802                EditGameEvent();
10803                if (gameMode != EditGame) return;
10804         }
10805         ResurrectChessProgram();
10806         SendToProgram("analyze\n", &first);
10807         first.analyzing = TRUE;
10808         /*first.maybeThinking = TRUE;*/
10809         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10810         EngineOutputPopUp();
10811     }
10812     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10813     pausing = FALSE;
10814     ModeHighlight();
10815     SetGameInfo();
10816
10817     StartAnalysisClock();
10818     GetTimeMark(&lastNodeCountTime);
10819     lastNodeCount = 0;
10820 }
10821
10822 void
10823 AnalyzeFileEvent()
10824 {
10825     if (appData.noChessProgram || gameMode == AnalyzeFile)
10826       return;
10827
10828     if (gameMode != AnalyzeMode) {
10829         EditGameEvent();
10830         if (gameMode != EditGame) return;
10831         ResurrectChessProgram();
10832         SendToProgram("analyze\n", &first);
10833         first.analyzing = TRUE;
10834         /*first.maybeThinking = TRUE;*/
10835         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10836         EngineOutputPopUp();
10837     }
10838     gameMode = AnalyzeFile;
10839     pausing = FALSE;
10840     ModeHighlight();
10841     SetGameInfo();
10842
10843     StartAnalysisClock();
10844     GetTimeMark(&lastNodeCountTime);
10845     lastNodeCount = 0;
10846 }
10847
10848 void
10849 MachineWhiteEvent()
10850 {
10851     char buf[MSG_SIZ];
10852     char *bookHit = NULL;
10853
10854     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10855       return;
10856
10857
10858     if (gameMode == PlayFromGameFile || 
10859         gameMode == TwoMachinesPlay  || 
10860         gameMode == Training         || 
10861         gameMode == AnalyzeMode      || 
10862         gameMode == EndOfGame)
10863         EditGameEvent();
10864
10865     if (gameMode == EditPosition) 
10866         EditPositionDone(TRUE);
10867
10868     if (!WhiteOnMove(currentMove)) {
10869         DisplayError(_("It is not White's turn"), 0);
10870         return;
10871     }
10872   
10873     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10874       ExitAnalyzeMode();
10875
10876     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10877         gameMode == AnalyzeFile)
10878         TruncateGame();
10879
10880     ResurrectChessProgram();    /* in case it isn't running */
10881     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10882         gameMode = MachinePlaysWhite;
10883         ResetClocks();
10884     } else
10885     gameMode = MachinePlaysWhite;
10886     pausing = FALSE;
10887     ModeHighlight();
10888     SetGameInfo();
10889     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10890     DisplayTitle(buf);
10891     if (first.sendName) {
10892       sprintf(buf, "name %s\n", gameInfo.black);
10893       SendToProgram(buf, &first);
10894     }
10895     if (first.sendTime) {
10896       if (first.useColors) {
10897         SendToProgram("black\n", &first); /*gnu kludge*/
10898       }
10899       SendTimeRemaining(&first, TRUE);
10900     }
10901     if (first.useColors) {
10902       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10903     }
10904     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10905     SetMachineThinkingEnables();
10906     first.maybeThinking = TRUE;
10907     StartClocks();
10908     firstMove = FALSE;
10909
10910     if (appData.autoFlipView && !flipView) {
10911       flipView = !flipView;
10912       DrawPosition(FALSE, NULL);
10913       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10914     }
10915
10916     if(bookHit) { // [HGM] book: simulate book reply
10917         static char bookMove[MSG_SIZ]; // a bit generous?
10918
10919         programStats.nodes = programStats.depth = programStats.time = 
10920         programStats.score = programStats.got_only_move = 0;
10921         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10922
10923         strcpy(bookMove, "move ");
10924         strcat(bookMove, bookHit);
10925         HandleMachineMove(bookMove, &first);
10926     }
10927 }
10928
10929 void
10930 MachineBlackEvent()
10931 {
10932     char buf[MSG_SIZ];
10933    char *bookHit = NULL;
10934
10935     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10936         return;
10937
10938
10939     if (gameMode == PlayFromGameFile || 
10940         gameMode == TwoMachinesPlay  || 
10941         gameMode == Training         || 
10942         gameMode == AnalyzeMode      || 
10943         gameMode == EndOfGame)
10944         EditGameEvent();
10945
10946     if (gameMode == EditPosition) 
10947         EditPositionDone(TRUE);
10948
10949     if (WhiteOnMove(currentMove)) {
10950         DisplayError(_("It is not Black's turn"), 0);
10951         return;
10952     }
10953     
10954     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10955       ExitAnalyzeMode();
10956
10957     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10958         gameMode == AnalyzeFile)
10959         TruncateGame();
10960
10961     ResurrectChessProgram();    /* in case it isn't running */
10962     gameMode = MachinePlaysBlack;
10963     pausing = FALSE;
10964     ModeHighlight();
10965     SetGameInfo();
10966     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10967     DisplayTitle(buf);
10968     if (first.sendName) {
10969       sprintf(buf, "name %s\n", gameInfo.white);
10970       SendToProgram(buf, &first);
10971     }
10972     if (first.sendTime) {
10973       if (first.useColors) {
10974         SendToProgram("white\n", &first); /*gnu kludge*/
10975       }
10976       SendTimeRemaining(&first, FALSE);
10977     }
10978     if (first.useColors) {
10979       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10980     }
10981     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10982     SetMachineThinkingEnables();
10983     first.maybeThinking = TRUE;
10984     StartClocks();
10985
10986     if (appData.autoFlipView && flipView) {
10987       flipView = !flipView;
10988       DrawPosition(FALSE, NULL);
10989       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10990     }
10991     if(bookHit) { // [HGM] book: simulate book reply
10992         static char bookMove[MSG_SIZ]; // a bit generous?
10993
10994         programStats.nodes = programStats.depth = programStats.time = 
10995         programStats.score = programStats.got_only_move = 0;
10996         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10997
10998         strcpy(bookMove, "move ");
10999         strcat(bookMove, bookHit);
11000         HandleMachineMove(bookMove, &first);
11001     }
11002 }
11003
11004
11005 void
11006 DisplayTwoMachinesTitle()
11007 {
11008     char buf[MSG_SIZ];
11009     if (appData.matchGames > 0) {
11010         if (first.twoMachinesColor[0] == 'w') {
11011             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11012                     gameInfo.white, gameInfo.black,
11013                     first.matchWins, second.matchWins,
11014                     matchGame - 1 - (first.matchWins + second.matchWins));
11015         } else {
11016             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11017                     gameInfo.white, gameInfo.black,
11018                     second.matchWins, first.matchWins,
11019                     matchGame - 1 - (first.matchWins + second.matchWins));
11020         }
11021     } else {
11022         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11023     }
11024     DisplayTitle(buf);
11025 }
11026
11027 void
11028 TwoMachinesEvent P((void))
11029 {
11030     int i;
11031     char buf[MSG_SIZ];
11032     ChessProgramState *onmove;
11033     char *bookHit = NULL;
11034     
11035     if (appData.noChessProgram) return;
11036
11037     switch (gameMode) {
11038       case TwoMachinesPlay:
11039         return;
11040       case MachinePlaysWhite:
11041       case MachinePlaysBlack:
11042         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11043             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11044             return;
11045         }
11046         /* fall through */
11047       case BeginningOfGame:
11048       case PlayFromGameFile:
11049       case EndOfGame:
11050         EditGameEvent();
11051         if (gameMode != EditGame) return;
11052         break;
11053       case EditPosition:
11054         EditPositionDone(TRUE);
11055         break;
11056       case AnalyzeMode:
11057       case AnalyzeFile:
11058         ExitAnalyzeMode();
11059         break;
11060       case EditGame:
11061       default:
11062         break;
11063     }
11064
11065 //    forwardMostMove = currentMove;
11066     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11067     ResurrectChessProgram();    /* in case first program isn't running */
11068
11069     if (second.pr == NULL) {
11070         StartChessProgram(&second);
11071         if (second.protocolVersion == 1) {
11072           TwoMachinesEventIfReady();
11073         } else {
11074           /* kludge: allow timeout for initial "feature" command */
11075           FreezeUI();
11076           DisplayMessage("", _("Starting second chess program"));
11077           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11078         }
11079         return;
11080     }
11081     DisplayMessage("", "");
11082     InitChessProgram(&second, FALSE);
11083     SendToProgram("force\n", &second);
11084     if (startedFromSetupPosition) {
11085         SendBoard(&second, backwardMostMove);
11086     if (appData.debugMode) {
11087         fprintf(debugFP, "Two Machines\n");
11088     }
11089     }
11090     for (i = backwardMostMove; i < forwardMostMove; i++) {
11091         SendMoveToProgram(i, &second);
11092     }
11093
11094     gameMode = TwoMachinesPlay;
11095     pausing = FALSE;
11096     ModeHighlight();
11097     SetGameInfo();
11098     DisplayTwoMachinesTitle();
11099     firstMove = TRUE;
11100     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11101         onmove = &first;
11102     } else {
11103         onmove = &second;
11104     }
11105
11106     SendToProgram(first.computerString, &first);
11107     if (first.sendName) {
11108       sprintf(buf, "name %s\n", second.tidy);
11109       SendToProgram(buf, &first);
11110     }
11111     SendToProgram(second.computerString, &second);
11112     if (second.sendName) {
11113       sprintf(buf, "name %s\n", first.tidy);
11114       SendToProgram(buf, &second);
11115     }
11116
11117     ResetClocks();
11118     if (!first.sendTime || !second.sendTime) {
11119         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11120         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11121     }
11122     if (onmove->sendTime) {
11123       if (onmove->useColors) {
11124         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11125       }
11126       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11127     }
11128     if (onmove->useColors) {
11129       SendToProgram(onmove->twoMachinesColor, onmove);
11130     }
11131     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11132 //    SendToProgram("go\n", onmove);
11133     onmove->maybeThinking = TRUE;
11134     SetMachineThinkingEnables();
11135
11136     StartClocks();
11137
11138     if(bookHit) { // [HGM] book: simulate book reply
11139         static char bookMove[MSG_SIZ]; // a bit generous?
11140
11141         programStats.nodes = programStats.depth = programStats.time = 
11142         programStats.score = programStats.got_only_move = 0;
11143         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11144
11145         strcpy(bookMove, "move ");
11146         strcat(bookMove, bookHit);
11147         savedMessage = bookMove; // args for deferred call
11148         savedState = onmove;
11149         ScheduleDelayedEvent(DeferredBookMove, 1);
11150     }
11151 }
11152
11153 void
11154 TrainingEvent()
11155 {
11156     if (gameMode == Training) {
11157       SetTrainingModeOff();
11158       gameMode = PlayFromGameFile;
11159       DisplayMessage("", _("Training mode off"));
11160     } else {
11161       gameMode = Training;
11162       animateTraining = appData.animate;
11163
11164       /* make sure we are not already at the end of the game */
11165       if (currentMove < forwardMostMove) {
11166         SetTrainingModeOn();
11167         DisplayMessage("", _("Training mode on"));
11168       } else {
11169         gameMode = PlayFromGameFile;
11170         DisplayError(_("Already at end of game"), 0);
11171       }
11172     }
11173     ModeHighlight();
11174 }
11175
11176 void
11177 IcsClientEvent()
11178 {
11179     if (!appData.icsActive) return;
11180     switch (gameMode) {
11181       case IcsPlayingWhite:
11182       case IcsPlayingBlack:
11183       case IcsObserving:
11184       case IcsIdle:
11185       case BeginningOfGame:
11186       case IcsExamining:
11187         return;
11188
11189       case EditGame:
11190         break;
11191
11192       case EditPosition:
11193         EditPositionDone(TRUE);
11194         break;
11195
11196       case AnalyzeMode:
11197       case AnalyzeFile:
11198         ExitAnalyzeMode();
11199         break;
11200         
11201       default:
11202         EditGameEvent();
11203         break;
11204     }
11205
11206     gameMode = IcsIdle;
11207     ModeHighlight();
11208     return;
11209 }
11210
11211
11212 void
11213 EditGameEvent()
11214 {
11215     int i;
11216
11217     switch (gameMode) {
11218       case Training:
11219         SetTrainingModeOff();
11220         break;
11221       case MachinePlaysWhite:
11222       case MachinePlaysBlack:
11223       case BeginningOfGame:
11224         SendToProgram("force\n", &first);
11225         SetUserThinkingEnables();
11226         break;
11227       case PlayFromGameFile:
11228         (void) StopLoadGameTimer();
11229         if (gameFileFP != NULL) {
11230             gameFileFP = NULL;
11231         }
11232         break;
11233       case EditPosition:
11234         EditPositionDone(TRUE);
11235         break;
11236       case AnalyzeMode:
11237       case AnalyzeFile:
11238         ExitAnalyzeMode();
11239         SendToProgram("force\n", &first);
11240         break;
11241       case TwoMachinesPlay:
11242         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11243         ResurrectChessProgram();
11244         SetUserThinkingEnables();
11245         break;
11246       case EndOfGame:
11247         ResurrectChessProgram();
11248         break;
11249       case IcsPlayingBlack:
11250       case IcsPlayingWhite:
11251         DisplayError(_("Warning: You are still playing a game"), 0);
11252         break;
11253       case IcsObserving:
11254         DisplayError(_("Warning: You are still observing a game"), 0);
11255         break;
11256       case IcsExamining:
11257         DisplayError(_("Warning: You are still examining a game"), 0);
11258         break;
11259       case IcsIdle:
11260         break;
11261       case EditGame:
11262       default:
11263         return;
11264     }
11265     
11266     pausing = FALSE;
11267     StopClocks();
11268     first.offeredDraw = second.offeredDraw = 0;
11269
11270     if (gameMode == PlayFromGameFile) {
11271         whiteTimeRemaining = timeRemaining[0][currentMove];
11272         blackTimeRemaining = timeRemaining[1][currentMove];
11273         DisplayTitle("");
11274     }
11275
11276     if (gameMode == MachinePlaysWhite ||
11277         gameMode == MachinePlaysBlack ||
11278         gameMode == TwoMachinesPlay ||
11279         gameMode == EndOfGame) {
11280         i = forwardMostMove;
11281         while (i > currentMove) {
11282             SendToProgram("undo\n", &first);
11283             i--;
11284         }
11285         whiteTimeRemaining = timeRemaining[0][currentMove];
11286         blackTimeRemaining = timeRemaining[1][currentMove];
11287         DisplayBothClocks();
11288         if (whiteFlag || blackFlag) {
11289             whiteFlag = blackFlag = 0;
11290         }
11291         DisplayTitle("");
11292     }           
11293     
11294     gameMode = EditGame;
11295     ModeHighlight();
11296     SetGameInfo();
11297 }
11298
11299
11300 void
11301 EditPositionEvent()
11302 {
11303     if (gameMode == EditPosition) {
11304         EditGameEvent();
11305         return;
11306     }
11307     
11308     EditGameEvent();
11309     if (gameMode != EditGame) return;
11310     
11311     gameMode = EditPosition;
11312     ModeHighlight();
11313     SetGameInfo();
11314     if (currentMove > 0)
11315       CopyBoard(boards[0], boards[currentMove]);
11316     
11317     blackPlaysFirst = !WhiteOnMove(currentMove);
11318     ResetClocks();
11319     currentMove = forwardMostMove = backwardMostMove = 0;
11320     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11321     DisplayMove(-1);
11322 }
11323
11324 void
11325 ExitAnalyzeMode()
11326 {
11327     /* [DM] icsEngineAnalyze - possible call from other functions */
11328     if (appData.icsEngineAnalyze) {
11329         appData.icsEngineAnalyze = FALSE;
11330
11331         DisplayMessage("",_("Close ICS engine analyze..."));
11332     }
11333     if (first.analysisSupport && first.analyzing) {
11334       SendToProgram("exit\n", &first);
11335       first.analyzing = FALSE;
11336     }
11337     thinkOutput[0] = NULLCHAR;
11338 }
11339
11340 void
11341 EditPositionDone(Boolean fakeRights)
11342 {
11343     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11344
11345     startedFromSetupPosition = TRUE;
11346     InitChessProgram(&first, FALSE);
11347     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11348       boards[0][EP_STATUS] = EP_NONE;
11349       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11350     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11351         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11352         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11353       } else boards[0][CASTLING][2] = NoRights;
11354     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11355         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11356         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11357       } else boards[0][CASTLING][5] = NoRights;
11358     }
11359     SendToProgram("force\n", &first);
11360     if (blackPlaysFirst) {
11361         strcpy(moveList[0], "");
11362         strcpy(parseList[0], "");
11363         currentMove = forwardMostMove = backwardMostMove = 1;
11364         CopyBoard(boards[1], boards[0]);
11365     } else {
11366         currentMove = forwardMostMove = backwardMostMove = 0;
11367     }
11368     SendBoard(&first, forwardMostMove);
11369     if (appData.debugMode) {
11370         fprintf(debugFP, "EditPosDone\n");
11371     }
11372     DisplayTitle("");
11373     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11374     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11375     gameMode = EditGame;
11376     ModeHighlight();
11377     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11378     ClearHighlights(); /* [AS] */
11379 }
11380
11381 /* Pause for `ms' milliseconds */
11382 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11383 void
11384 TimeDelay(ms)
11385      long ms;
11386 {
11387     TimeMark m1, m2;
11388
11389     GetTimeMark(&m1);
11390     do {
11391         GetTimeMark(&m2);
11392     } while (SubtractTimeMarks(&m2, &m1) < ms);
11393 }
11394
11395 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11396 void
11397 SendMultiLineToICS(buf)
11398      char *buf;
11399 {
11400     char temp[MSG_SIZ+1], *p;
11401     int len;
11402
11403     len = strlen(buf);
11404     if (len > MSG_SIZ)
11405       len = MSG_SIZ;
11406   
11407     strncpy(temp, buf, len);
11408     temp[len] = 0;
11409
11410     p = temp;
11411     while (*p) {
11412         if (*p == '\n' || *p == '\r')
11413           *p = ' ';
11414         ++p;
11415     }
11416
11417     strcat(temp, "\n");
11418     SendToICS(temp);
11419     SendToPlayer(temp, strlen(temp));
11420 }
11421
11422 void
11423 SetWhiteToPlayEvent()
11424 {
11425     if (gameMode == EditPosition) {
11426         blackPlaysFirst = FALSE;
11427         DisplayBothClocks();    /* works because currentMove is 0 */
11428     } else if (gameMode == IcsExamining) {
11429         SendToICS(ics_prefix);
11430         SendToICS("tomove white\n");
11431     }
11432 }
11433
11434 void
11435 SetBlackToPlayEvent()
11436 {
11437     if (gameMode == EditPosition) {
11438         blackPlaysFirst = TRUE;
11439         currentMove = 1;        /* kludge */
11440         DisplayBothClocks();
11441         currentMove = 0;
11442     } else if (gameMode == IcsExamining) {
11443         SendToICS(ics_prefix);
11444         SendToICS("tomove black\n");
11445     }
11446 }
11447
11448 void
11449 EditPositionMenuEvent(selection, x, y)
11450      ChessSquare selection;
11451      int x, y;
11452 {
11453     char buf[MSG_SIZ];
11454     ChessSquare piece = boards[0][y][x];
11455
11456     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11457
11458     switch (selection) {
11459       case ClearBoard:
11460         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11461             SendToICS(ics_prefix);
11462             SendToICS("bsetup clear\n");
11463         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11464             SendToICS(ics_prefix);
11465             SendToICS("clearboard\n");
11466         } else {
11467             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11468                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11469                 for (y = 0; y < BOARD_HEIGHT; y++) {
11470                     if (gameMode == IcsExamining) {
11471                         if (boards[currentMove][y][x] != EmptySquare) {
11472                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11473                                     AAA + x, ONE + y);
11474                             SendToICS(buf);
11475                         }
11476                     } else {
11477                         boards[0][y][x] = p;
11478                     }
11479                 }
11480             }
11481         }
11482         if (gameMode == EditPosition) {
11483             DrawPosition(FALSE, boards[0]);
11484         }
11485         break;
11486
11487       case WhitePlay:
11488         SetWhiteToPlayEvent();
11489         break;
11490
11491       case BlackPlay:
11492         SetBlackToPlayEvent();
11493         break;
11494
11495       case EmptySquare:
11496         if (gameMode == IcsExamining) {
11497             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11498             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11499             SendToICS(buf);
11500         } else {
11501             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11502                 if(x == BOARD_LEFT-2) {
11503                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11504                     boards[0][y][1] = 0;
11505                 } else
11506                 if(x == BOARD_RGHT+1) {
11507                     if(y >= gameInfo.holdingsSize) break;
11508                     boards[0][y][BOARD_WIDTH-2] = 0;
11509                 } else break;
11510             }
11511             boards[0][y][x] = EmptySquare;
11512             DrawPosition(FALSE, boards[0]);
11513         }
11514         break;
11515
11516       case PromotePiece:
11517         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11518            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11519             selection = (ChessSquare) (PROMOTED piece);
11520         } else if(piece == EmptySquare) selection = WhiteSilver;
11521         else selection = (ChessSquare)((int)piece - 1);
11522         goto defaultlabel;
11523
11524       case DemotePiece:
11525         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11526            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11527             selection = (ChessSquare) (DEMOTED piece);
11528         } else if(piece == EmptySquare) selection = BlackSilver;
11529         else selection = (ChessSquare)((int)piece + 1);       
11530         goto defaultlabel;
11531
11532       case WhiteQueen:
11533       case BlackQueen:
11534         if(gameInfo.variant == VariantShatranj ||
11535            gameInfo.variant == VariantXiangqi  ||
11536            gameInfo.variant == VariantCourier    )
11537             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11538         goto defaultlabel;
11539
11540       case WhiteKing:
11541       case BlackKing:
11542         if(gameInfo.variant == VariantXiangqi)
11543             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11544         if(gameInfo.variant == VariantKnightmate)
11545             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11546       default:
11547         defaultlabel:
11548         if (gameMode == IcsExamining) {
11549             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11550             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11551                     PieceToChar(selection), AAA + x, ONE + y);
11552             SendToICS(buf);
11553         } else {
11554             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11555                 int n;
11556                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11557                     n = PieceToNumber(selection - BlackPawn);
11558                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11559                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11560                     boards[0][BOARD_HEIGHT-1-n][1]++;
11561                 } else
11562                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11563                     n = PieceToNumber(selection);
11564                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11565                     boards[0][n][BOARD_WIDTH-1] = selection;
11566                     boards[0][n][BOARD_WIDTH-2]++;
11567                 }
11568             } else
11569             boards[0][y][x] = selection;
11570             DrawPosition(TRUE, boards[0]);
11571         }
11572         break;
11573     }
11574 }
11575
11576
11577 void
11578 DropMenuEvent(selection, x, y)
11579      ChessSquare selection;
11580      int x, y;
11581 {
11582     ChessMove moveType;
11583
11584     switch (gameMode) {
11585       case IcsPlayingWhite:
11586       case MachinePlaysBlack:
11587         if (!WhiteOnMove(currentMove)) {
11588             DisplayMoveError(_("It is Black's turn"));
11589             return;
11590         }
11591         moveType = WhiteDrop;
11592         break;
11593       case IcsPlayingBlack:
11594       case MachinePlaysWhite:
11595         if (WhiteOnMove(currentMove)) {
11596             DisplayMoveError(_("It is White's turn"));
11597             return;
11598         }
11599         moveType = BlackDrop;
11600         break;
11601       case EditGame:
11602         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11603         break;
11604       default:
11605         return;
11606     }
11607
11608     if (moveType == BlackDrop && selection < BlackPawn) {
11609       selection = (ChessSquare) ((int) selection
11610                                  + (int) BlackPawn - (int) WhitePawn);
11611     }
11612     if (boards[currentMove][y][x] != EmptySquare) {
11613         DisplayMoveError(_("That square is occupied"));
11614         return;
11615     }
11616
11617     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11618 }
11619
11620 void
11621 AcceptEvent()
11622 {
11623     /* Accept a pending offer of any kind from opponent */
11624     
11625     if (appData.icsActive) {
11626         SendToICS(ics_prefix);
11627         SendToICS("accept\n");
11628     } else if (cmailMsgLoaded) {
11629         if (currentMove == cmailOldMove &&
11630             commentList[cmailOldMove] != NULL &&
11631             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11632                    "Black offers a draw" : "White offers a draw")) {
11633             TruncateGame();
11634             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11635             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11636         } else {
11637             DisplayError(_("There is no pending offer on this move"), 0);
11638             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11639         }
11640     } else {
11641         /* Not used for offers from chess program */
11642     }
11643 }
11644
11645 void
11646 DeclineEvent()
11647 {
11648     /* Decline a pending offer of any kind from opponent */
11649     
11650     if (appData.icsActive) {
11651         SendToICS(ics_prefix);
11652         SendToICS("decline\n");
11653     } else if (cmailMsgLoaded) {
11654         if (currentMove == cmailOldMove &&
11655             commentList[cmailOldMove] != NULL &&
11656             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11657                    "Black offers a draw" : "White offers a draw")) {
11658 #ifdef NOTDEF
11659             AppendComment(cmailOldMove, "Draw declined", TRUE);
11660             DisplayComment(cmailOldMove - 1, "Draw declined");
11661 #endif /*NOTDEF*/
11662         } else {
11663             DisplayError(_("There is no pending offer on this move"), 0);
11664         }
11665     } else {
11666         /* Not used for offers from chess program */
11667     }
11668 }
11669
11670 void
11671 RematchEvent()
11672 {
11673     /* Issue ICS rematch command */
11674     if (appData.icsActive) {
11675         SendToICS(ics_prefix);
11676         SendToICS("rematch\n");
11677     }
11678 }
11679
11680 void
11681 CallFlagEvent()
11682 {
11683     /* Call your opponent's flag (claim a win on time) */
11684     if (appData.icsActive) {
11685         SendToICS(ics_prefix);
11686         SendToICS("flag\n");
11687     } else {
11688         switch (gameMode) {
11689           default:
11690             return;
11691           case MachinePlaysWhite:
11692             if (whiteFlag) {
11693                 if (blackFlag)
11694                   GameEnds(GameIsDrawn, "Both players ran out of time",
11695                            GE_PLAYER);
11696                 else
11697                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11698             } else {
11699                 DisplayError(_("Your opponent is not out of time"), 0);
11700             }
11701             break;
11702           case MachinePlaysBlack:
11703             if (blackFlag) {
11704                 if (whiteFlag)
11705                   GameEnds(GameIsDrawn, "Both players ran out of time",
11706                            GE_PLAYER);
11707                 else
11708                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11709             } else {
11710                 DisplayError(_("Your opponent is not out of time"), 0);
11711             }
11712             break;
11713         }
11714     }
11715 }
11716
11717 void
11718 DrawEvent()
11719 {
11720     /* Offer draw or accept pending draw offer from opponent */
11721     
11722     if (appData.icsActive) {
11723         /* Note: tournament rules require draw offers to be
11724            made after you make your move but before you punch
11725            your clock.  Currently ICS doesn't let you do that;
11726            instead, you immediately punch your clock after making
11727            a move, but you can offer a draw at any time. */
11728         
11729         SendToICS(ics_prefix);
11730         SendToICS("draw\n");
11731     } else if (cmailMsgLoaded) {
11732         if (currentMove == cmailOldMove &&
11733             commentList[cmailOldMove] != NULL &&
11734             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11735                    "Black offers a draw" : "White offers a draw")) {
11736             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11737             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11738         } else if (currentMove == cmailOldMove + 1) {
11739             char *offer = WhiteOnMove(cmailOldMove) ?
11740               "White offers a draw" : "Black offers a draw";
11741             AppendComment(currentMove, offer, TRUE);
11742             DisplayComment(currentMove - 1, offer);
11743             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11744         } else {
11745             DisplayError(_("You must make your move before offering a draw"), 0);
11746             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11747         }
11748     } else if (first.offeredDraw) {
11749         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11750     } else {
11751         if (first.sendDrawOffers) {
11752             SendToProgram("draw\n", &first);
11753             userOfferedDraw = TRUE;
11754         }
11755     }
11756 }
11757
11758 void
11759 AdjournEvent()
11760 {
11761     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11762     
11763     if (appData.icsActive) {
11764         SendToICS(ics_prefix);
11765         SendToICS("adjourn\n");
11766     } else {
11767         /* Currently GNU Chess doesn't offer or accept Adjourns */
11768     }
11769 }
11770
11771
11772 void
11773 AbortEvent()
11774 {
11775     /* Offer Abort or accept pending Abort offer from opponent */
11776     
11777     if (appData.icsActive) {
11778         SendToICS(ics_prefix);
11779         SendToICS("abort\n");
11780     } else {
11781         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11782     }
11783 }
11784
11785 void
11786 ResignEvent()
11787 {
11788     /* Resign.  You can do this even if it's not your turn. */
11789     
11790     if (appData.icsActive) {
11791         SendToICS(ics_prefix);
11792         SendToICS("resign\n");
11793     } else {
11794         switch (gameMode) {
11795           case MachinePlaysWhite:
11796             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11797             break;
11798           case MachinePlaysBlack:
11799             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11800             break;
11801           case EditGame:
11802             if (cmailMsgLoaded) {
11803                 TruncateGame();
11804                 if (WhiteOnMove(cmailOldMove)) {
11805                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11806                 } else {
11807                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11808                 }
11809                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11810             }
11811             break;
11812           default:
11813             break;
11814         }
11815     }
11816 }
11817
11818
11819 void
11820 StopObservingEvent()
11821 {
11822     /* Stop observing current games */
11823     SendToICS(ics_prefix);
11824     SendToICS("unobserve\n");
11825 }
11826
11827 void
11828 StopExaminingEvent()
11829 {
11830     /* Stop observing current game */
11831     SendToICS(ics_prefix);
11832     SendToICS("unexamine\n");
11833 }
11834
11835 void
11836 ForwardInner(target)
11837      int target;
11838 {
11839     int limit;
11840
11841     if (appData.debugMode)
11842         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11843                 target, currentMove, forwardMostMove);
11844
11845     if (gameMode == EditPosition)
11846       return;
11847
11848     if (gameMode == PlayFromGameFile && !pausing)
11849       PauseEvent();
11850     
11851     if (gameMode == IcsExamining && pausing)
11852       limit = pauseExamForwardMostMove;
11853     else
11854       limit = forwardMostMove;
11855     
11856     if (target > limit) target = limit;
11857
11858     if (target > 0 && moveList[target - 1][0]) {
11859         int fromX, fromY, toX, toY;
11860         toX = moveList[target - 1][2] - AAA;
11861         toY = moveList[target - 1][3] - ONE;
11862         if (moveList[target - 1][1] == '@') {
11863             if (appData.highlightLastMove) {
11864                 SetHighlights(-1, -1, toX, toY);
11865             }
11866         } else {
11867             fromX = moveList[target - 1][0] - AAA;
11868             fromY = moveList[target - 1][1] - ONE;
11869             if (target == currentMove + 1) {
11870                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11871             }
11872             if (appData.highlightLastMove) {
11873                 SetHighlights(fromX, fromY, toX, toY);
11874             }
11875         }
11876     }
11877     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11878         gameMode == Training || gameMode == PlayFromGameFile || 
11879         gameMode == AnalyzeFile) {
11880         while (currentMove < target) {
11881             SendMoveToProgram(currentMove++, &first);
11882         }
11883     } else {
11884         currentMove = target;
11885     }
11886     
11887     if (gameMode == EditGame || gameMode == EndOfGame) {
11888         whiteTimeRemaining = timeRemaining[0][currentMove];
11889         blackTimeRemaining = timeRemaining[1][currentMove];
11890     }
11891     DisplayBothClocks();
11892     DisplayMove(currentMove - 1);
11893     DrawPosition(FALSE, boards[currentMove]);
11894     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11895     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11896         DisplayComment(currentMove - 1, commentList[currentMove]);
11897     }
11898 }
11899
11900
11901 void
11902 ForwardEvent()
11903 {
11904     if (gameMode == IcsExamining && !pausing) {
11905         SendToICS(ics_prefix);
11906         SendToICS("forward\n");
11907     } else {
11908         ForwardInner(currentMove + 1);
11909     }
11910 }
11911
11912 void
11913 ToEndEvent()
11914 {
11915     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11916         /* to optimze, we temporarily turn off analysis mode while we feed
11917          * the remaining moves to the engine. Otherwise we get analysis output
11918          * after each move.
11919          */ 
11920         if (first.analysisSupport) {
11921           SendToProgram("exit\nforce\n", &first);
11922           first.analyzing = FALSE;
11923         }
11924     }
11925         
11926     if (gameMode == IcsExamining && !pausing) {
11927         SendToICS(ics_prefix);
11928         SendToICS("forward 999999\n");
11929     } else {
11930         ForwardInner(forwardMostMove);
11931     }
11932
11933     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11934         /* we have fed all the moves, so reactivate analysis mode */
11935         SendToProgram("analyze\n", &first);
11936         first.analyzing = TRUE;
11937         /*first.maybeThinking = TRUE;*/
11938         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11939     }
11940 }
11941
11942 void
11943 BackwardInner(target)
11944      int target;
11945 {
11946     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11947
11948     if (appData.debugMode)
11949         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11950                 target, currentMove, forwardMostMove);
11951
11952     if (gameMode == EditPosition) return;
11953     if (currentMove <= backwardMostMove) {
11954         ClearHighlights();
11955         DrawPosition(full_redraw, boards[currentMove]);
11956         return;
11957     }
11958     if (gameMode == PlayFromGameFile && !pausing)
11959       PauseEvent();
11960     
11961     if (moveList[target][0]) {
11962         int fromX, fromY, toX, toY;
11963         toX = moveList[target][2] - AAA;
11964         toY = moveList[target][3] - ONE;
11965         if (moveList[target][1] == '@') {
11966             if (appData.highlightLastMove) {
11967                 SetHighlights(-1, -1, toX, toY);
11968             }
11969         } else {
11970             fromX = moveList[target][0] - AAA;
11971             fromY = moveList[target][1] - ONE;
11972             if (target == currentMove - 1) {
11973                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11974             }
11975             if (appData.highlightLastMove) {
11976                 SetHighlights(fromX, fromY, toX, toY);
11977             }
11978         }
11979     }
11980     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11981         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11982         while (currentMove > target) {
11983             SendToProgram("undo\n", &first);
11984             currentMove--;
11985         }
11986     } else {
11987         currentMove = target;
11988     }
11989     
11990     if (gameMode == EditGame || gameMode == EndOfGame) {
11991         whiteTimeRemaining = timeRemaining[0][currentMove];
11992         blackTimeRemaining = timeRemaining[1][currentMove];
11993     }
11994     DisplayBothClocks();
11995     DisplayMove(currentMove - 1);
11996     DrawPosition(full_redraw, boards[currentMove]);
11997     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11998     // [HGM] PV info: routine tests if comment empty
11999     DisplayComment(currentMove - 1, commentList[currentMove]);
12000 }
12001
12002 void
12003 BackwardEvent()
12004 {
12005     if (gameMode == IcsExamining && !pausing) {
12006         SendToICS(ics_prefix);
12007         SendToICS("backward\n");
12008     } else {
12009         BackwardInner(currentMove - 1);
12010     }
12011 }
12012
12013 void
12014 ToStartEvent()
12015 {
12016     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12017         /* to optimize, we temporarily turn off analysis mode while we undo
12018          * all the moves. Otherwise we get analysis output after each undo.
12019          */ 
12020         if (first.analysisSupport) {
12021           SendToProgram("exit\nforce\n", &first);
12022           first.analyzing = FALSE;
12023         }
12024     }
12025
12026     if (gameMode == IcsExamining && !pausing) {
12027         SendToICS(ics_prefix);
12028         SendToICS("backward 999999\n");
12029     } else {
12030         BackwardInner(backwardMostMove);
12031     }
12032
12033     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12034         /* we have fed all the moves, so reactivate analysis mode */
12035         SendToProgram("analyze\n", &first);
12036         first.analyzing = TRUE;
12037         /*first.maybeThinking = TRUE;*/
12038         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12039     }
12040 }
12041
12042 void
12043 ToNrEvent(int to)
12044 {
12045   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12046   if (to >= forwardMostMove) to = forwardMostMove;
12047   if (to <= backwardMostMove) to = backwardMostMove;
12048   if (to < currentMove) {
12049     BackwardInner(to);
12050   } else {
12051     ForwardInner(to);
12052   }
12053 }
12054
12055 void
12056 RevertEvent()
12057 {
12058     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12059         return;
12060     }
12061     if (gameMode != IcsExamining) {
12062         DisplayError(_("You are not examining a game"), 0);
12063         return;
12064     }
12065     if (pausing) {
12066         DisplayError(_("You can't revert while pausing"), 0);
12067         return;
12068     }
12069     SendToICS(ics_prefix);
12070     SendToICS("revert\n");
12071 }
12072
12073 void
12074 RetractMoveEvent()
12075 {
12076     switch (gameMode) {
12077       case MachinePlaysWhite:
12078       case MachinePlaysBlack:
12079         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12080             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12081             return;
12082         }
12083         if (forwardMostMove < 2) return;
12084         currentMove = forwardMostMove = forwardMostMove - 2;
12085         whiteTimeRemaining = timeRemaining[0][currentMove];
12086         blackTimeRemaining = timeRemaining[1][currentMove];
12087         DisplayBothClocks();
12088         DisplayMove(currentMove - 1);
12089         ClearHighlights();/*!! could figure this out*/
12090         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12091         SendToProgram("remove\n", &first);
12092         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12093         break;
12094
12095       case BeginningOfGame:
12096       default:
12097         break;
12098
12099       case IcsPlayingWhite:
12100       case IcsPlayingBlack:
12101         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12102             SendToICS(ics_prefix);
12103             SendToICS("takeback 2\n");
12104         } else {
12105             SendToICS(ics_prefix);
12106             SendToICS("takeback 1\n");
12107         }
12108         break;
12109     }
12110 }
12111
12112 void
12113 MoveNowEvent()
12114 {
12115     ChessProgramState *cps;
12116
12117     switch (gameMode) {
12118       case MachinePlaysWhite:
12119         if (!WhiteOnMove(forwardMostMove)) {
12120             DisplayError(_("It is your turn"), 0);
12121             return;
12122         }
12123         cps = &first;
12124         break;
12125       case MachinePlaysBlack:
12126         if (WhiteOnMove(forwardMostMove)) {
12127             DisplayError(_("It is your turn"), 0);
12128             return;
12129         }
12130         cps = &first;
12131         break;
12132       case TwoMachinesPlay:
12133         if (WhiteOnMove(forwardMostMove) ==
12134             (first.twoMachinesColor[0] == 'w')) {
12135             cps = &first;
12136         } else {
12137             cps = &second;
12138         }
12139         break;
12140       case BeginningOfGame:
12141       default:
12142         return;
12143     }
12144     SendToProgram("?\n", cps);
12145 }
12146
12147 void
12148 TruncateGameEvent()
12149 {
12150     EditGameEvent();
12151     if (gameMode != EditGame) return;
12152     TruncateGame();
12153 }
12154
12155 void
12156 TruncateGame()
12157 {
12158     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12159     if (forwardMostMove > currentMove) {
12160         if (gameInfo.resultDetails != NULL) {
12161             free(gameInfo.resultDetails);
12162             gameInfo.resultDetails = NULL;
12163             gameInfo.result = GameUnfinished;
12164         }
12165         forwardMostMove = currentMove;
12166         HistorySet(parseList, backwardMostMove, forwardMostMove,
12167                    currentMove-1);
12168     }
12169 }
12170
12171 void
12172 HintEvent()
12173 {
12174     if (appData.noChessProgram) return;
12175     switch (gameMode) {
12176       case MachinePlaysWhite:
12177         if (WhiteOnMove(forwardMostMove)) {
12178             DisplayError(_("Wait until your turn"), 0);
12179             return;
12180         }
12181         break;
12182       case BeginningOfGame:
12183       case MachinePlaysBlack:
12184         if (!WhiteOnMove(forwardMostMove)) {
12185             DisplayError(_("Wait until your turn"), 0);
12186             return;
12187         }
12188         break;
12189       default:
12190         DisplayError(_("No hint available"), 0);
12191         return;
12192     }
12193     SendToProgram("hint\n", &first);
12194     hintRequested = TRUE;
12195 }
12196
12197 void
12198 BookEvent()
12199 {
12200     if (appData.noChessProgram) return;
12201     switch (gameMode) {
12202       case MachinePlaysWhite:
12203         if (WhiteOnMove(forwardMostMove)) {
12204             DisplayError(_("Wait until your turn"), 0);
12205             return;
12206         }
12207         break;
12208       case BeginningOfGame:
12209       case MachinePlaysBlack:
12210         if (!WhiteOnMove(forwardMostMove)) {
12211             DisplayError(_("Wait until your turn"), 0);
12212             return;
12213         }
12214         break;
12215       case EditPosition:
12216         EditPositionDone(TRUE);
12217         break;
12218       case TwoMachinesPlay:
12219         return;
12220       default:
12221         break;
12222     }
12223     SendToProgram("bk\n", &first);
12224     bookOutput[0] = NULLCHAR;
12225     bookRequested = TRUE;
12226 }
12227
12228 void
12229 AboutGameEvent()
12230 {
12231     char *tags = PGNTags(&gameInfo);
12232     TagsPopUp(tags, CmailMsg());
12233     free(tags);
12234 }
12235
12236 /* end button procedures */
12237
12238 void
12239 PrintPosition(fp, move)
12240      FILE *fp;
12241      int move;
12242 {
12243     int i, j;
12244     
12245     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12246         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12247             char c = PieceToChar(boards[move][i][j]);
12248             fputc(c == 'x' ? '.' : c, fp);
12249             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12250         }
12251     }
12252     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12253       fprintf(fp, "white to play\n");
12254     else
12255       fprintf(fp, "black to play\n");
12256 }
12257
12258 void
12259 PrintOpponents(fp)
12260      FILE *fp;
12261 {
12262     if (gameInfo.white != NULL) {
12263         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12264     } else {
12265         fprintf(fp, "\n");
12266     }
12267 }
12268
12269 /* Find last component of program's own name, using some heuristics */
12270 void
12271 TidyProgramName(prog, host, buf)
12272      char *prog, *host, buf[MSG_SIZ];
12273 {
12274     char *p, *q;
12275     int local = (strcmp(host, "localhost") == 0);
12276     while (!local && (p = strchr(prog, ';')) != NULL) {
12277         p++;
12278         while (*p == ' ') p++;
12279         prog = p;
12280     }
12281     if (*prog == '"' || *prog == '\'') {
12282         q = strchr(prog + 1, *prog);
12283     } else {
12284         q = strchr(prog, ' ');
12285     }
12286     if (q == NULL) q = prog + strlen(prog);
12287     p = q;
12288     while (p >= prog && *p != '/' && *p != '\\') p--;
12289     p++;
12290     if(p == prog && *p == '"') p++;
12291     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12292     memcpy(buf, p, q - p);
12293     buf[q - p] = NULLCHAR;
12294     if (!local) {
12295         strcat(buf, "@");
12296         strcat(buf, host);
12297     }
12298 }
12299
12300 char *
12301 TimeControlTagValue()
12302 {
12303     char buf[MSG_SIZ];
12304     if (!appData.clockMode) {
12305         strcpy(buf, "-");
12306     } else if (movesPerSession > 0) {
12307         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12308     } else if (timeIncrement == 0) {
12309         sprintf(buf, "%ld", timeControl/1000);
12310     } else {
12311         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12312     }
12313     return StrSave(buf);
12314 }
12315
12316 void
12317 SetGameInfo()
12318 {
12319     /* This routine is used only for certain modes */
12320     VariantClass v = gameInfo.variant;
12321     ChessMove r = GameUnfinished;
12322     char *p = NULL;
12323
12324     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12325         r = gameInfo.result; 
12326         p = gameInfo.resultDetails; 
12327         gameInfo.resultDetails = NULL;
12328     }
12329     ClearGameInfo(&gameInfo);
12330     gameInfo.variant = v;
12331
12332     switch (gameMode) {
12333       case MachinePlaysWhite:
12334         gameInfo.event = StrSave( appData.pgnEventHeader );
12335         gameInfo.site = StrSave(HostName());
12336         gameInfo.date = PGNDate();
12337         gameInfo.round = StrSave("-");
12338         gameInfo.white = StrSave(first.tidy);
12339         gameInfo.black = StrSave(UserName());
12340         gameInfo.timeControl = TimeControlTagValue();
12341         break;
12342
12343       case MachinePlaysBlack:
12344         gameInfo.event = StrSave( appData.pgnEventHeader );
12345         gameInfo.site = StrSave(HostName());
12346         gameInfo.date = PGNDate();
12347         gameInfo.round = StrSave("-");
12348         gameInfo.white = StrSave(UserName());
12349         gameInfo.black = StrSave(first.tidy);
12350         gameInfo.timeControl = TimeControlTagValue();
12351         break;
12352
12353       case TwoMachinesPlay:
12354         gameInfo.event = StrSave( appData.pgnEventHeader );
12355         gameInfo.site = StrSave(HostName());
12356         gameInfo.date = PGNDate();
12357         if (matchGame > 0) {
12358             char buf[MSG_SIZ];
12359             sprintf(buf, "%d", matchGame);
12360             gameInfo.round = StrSave(buf);
12361         } else {
12362             gameInfo.round = StrSave("-");
12363         }
12364         if (first.twoMachinesColor[0] == 'w') {
12365             gameInfo.white = StrSave(first.tidy);
12366             gameInfo.black = StrSave(second.tidy);
12367         } else {
12368             gameInfo.white = StrSave(second.tidy);
12369             gameInfo.black = StrSave(first.tidy);
12370         }
12371         gameInfo.timeControl = TimeControlTagValue();
12372         break;
12373
12374       case EditGame:
12375         gameInfo.event = StrSave("Edited game");
12376         gameInfo.site = StrSave(HostName());
12377         gameInfo.date = PGNDate();
12378         gameInfo.round = StrSave("-");
12379         gameInfo.white = StrSave("-");
12380         gameInfo.black = StrSave("-");
12381         gameInfo.result = r;
12382         gameInfo.resultDetails = p;
12383         break;
12384
12385       case EditPosition:
12386         gameInfo.event = StrSave("Edited position");
12387         gameInfo.site = StrSave(HostName());
12388         gameInfo.date = PGNDate();
12389         gameInfo.round = StrSave("-");
12390         gameInfo.white = StrSave("-");
12391         gameInfo.black = StrSave("-");
12392         break;
12393
12394       case IcsPlayingWhite:
12395       case IcsPlayingBlack:
12396       case IcsObserving:
12397       case IcsExamining:
12398         break;
12399
12400       case PlayFromGameFile:
12401         gameInfo.event = StrSave("Game from non-PGN file");
12402         gameInfo.site = StrSave(HostName());
12403         gameInfo.date = PGNDate();
12404         gameInfo.round = StrSave("-");
12405         gameInfo.white = StrSave("?");
12406         gameInfo.black = StrSave("?");
12407         break;
12408
12409       default:
12410         break;
12411     }
12412 }
12413
12414 void
12415 ReplaceComment(index, text)
12416      int index;
12417      char *text;
12418 {
12419     int len;
12420
12421     while (*text == '\n') text++;
12422     len = strlen(text);
12423     while (len > 0 && text[len - 1] == '\n') len--;
12424
12425     if (commentList[index] != NULL)
12426       free(commentList[index]);
12427
12428     if (len == 0) {
12429         commentList[index] = NULL;
12430         return;
12431     }
12432   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12433       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12434       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12435     commentList[index] = (char *) malloc(len + 2);
12436     strncpy(commentList[index], text, len);
12437     commentList[index][len] = '\n';
12438     commentList[index][len + 1] = NULLCHAR;
12439   } else { 
12440     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12441     char *p;
12442     commentList[index] = (char *) malloc(len + 6);
12443     strcpy(commentList[index], "{\n");
12444     strncpy(commentList[index]+2, text, len);
12445     commentList[index][len+2] = NULLCHAR;
12446     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12447     strcat(commentList[index], "\n}\n");
12448   }
12449 }
12450
12451 void
12452 CrushCRs(text)
12453      char *text;
12454 {
12455   char *p = text;
12456   char *q = text;
12457   char ch;
12458
12459   do {
12460     ch = *p++;
12461     if (ch == '\r') continue;
12462     *q++ = ch;
12463   } while (ch != '\0');
12464 }
12465
12466 void
12467 AppendComment(index, text, addBraces)
12468      int index;
12469      char *text;
12470      Boolean addBraces; // [HGM] braces: tells if we should add {}
12471 {
12472     int oldlen, len;
12473     char *old;
12474
12475 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12476     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12477
12478     CrushCRs(text);
12479     while (*text == '\n') text++;
12480     len = strlen(text);
12481     while (len > 0 && text[len - 1] == '\n') len--;
12482
12483     if (len == 0) return;
12484
12485     if (commentList[index] != NULL) {
12486         old = commentList[index];
12487         oldlen = strlen(old);
12488         while(commentList[index][oldlen-1] ==  '\n')
12489           commentList[index][--oldlen] = NULLCHAR;
12490         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12491         strcpy(commentList[index], old);
12492         free(old);
12493         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12494         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12495           if(addBraces) addBraces = FALSE; else { text++; len--; }
12496           while (*text == '\n') { text++; len--; }
12497           commentList[index][--oldlen] = NULLCHAR;
12498       }
12499         if(addBraces) strcat(commentList[index], "\n{\n");
12500         else          strcat(commentList[index], "\n");
12501         strcat(commentList[index], text);
12502         if(addBraces) strcat(commentList[index], "\n}\n");
12503         else          strcat(commentList[index], "\n");
12504     } else {
12505         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12506         if(addBraces)
12507              strcpy(commentList[index], "{\n");
12508         else commentList[index][0] = NULLCHAR;
12509         strcat(commentList[index], text);
12510         strcat(commentList[index], "\n");
12511         if(addBraces) strcat(commentList[index], "}\n");
12512     }
12513 }
12514
12515 static char * FindStr( char * text, char * sub_text )
12516 {
12517     char * result = strstr( text, sub_text );
12518
12519     if( result != NULL ) {
12520         result += strlen( sub_text );
12521     }
12522
12523     return result;
12524 }
12525
12526 /* [AS] Try to extract PV info from PGN comment */
12527 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12528 char *GetInfoFromComment( int index, char * text )
12529 {
12530     char * sep = text;
12531
12532     if( text != NULL && index > 0 ) {
12533         int score = 0;
12534         int depth = 0;
12535         int time = -1, sec = 0, deci;
12536         char * s_eval = FindStr( text, "[%eval " );
12537         char * s_emt = FindStr( text, "[%emt " );
12538
12539         if( s_eval != NULL || s_emt != NULL ) {
12540             /* New style */
12541             char delim;
12542
12543             if( s_eval != NULL ) {
12544                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12545                     return text;
12546                 }
12547
12548                 if( delim != ']' ) {
12549                     return text;
12550                 }
12551             }
12552
12553             if( s_emt != NULL ) {
12554             }
12555                 return text;
12556         }
12557         else {
12558             /* We expect something like: [+|-]nnn.nn/dd */
12559             int score_lo = 0;
12560
12561             if(*text != '{') return text; // [HGM] braces: must be normal comment
12562
12563             sep = strchr( text, '/' );
12564             if( sep == NULL || sep < (text+4) ) {
12565                 return text;
12566             }
12567
12568             time = -1; sec = -1; deci = -1;
12569             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12570                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12571                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12572                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12573                 return text;
12574             }
12575
12576             if( score_lo < 0 || score_lo >= 100 ) {
12577                 return text;
12578             }
12579
12580             if(sec >= 0) time = 600*time + 10*sec; else
12581             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12582
12583             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12584
12585             /* [HGM] PV time: now locate end of PV info */
12586             while( *++sep >= '0' && *sep <= '9'); // strip depth
12587             if(time >= 0)
12588             while( *++sep >= '0' && *sep <= '9'); // strip time
12589             if(sec >= 0)
12590             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12591             if(deci >= 0)
12592             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12593             while(*sep == ' ') sep++;
12594         }
12595
12596         if( depth <= 0 ) {
12597             return text;
12598         }
12599
12600         if( time < 0 ) {
12601             time = -1;
12602         }
12603
12604         pvInfoList[index-1].depth = depth;
12605         pvInfoList[index-1].score = score;
12606         pvInfoList[index-1].time  = 10*time; // centi-sec
12607         if(*sep == '}') *sep = 0; else *--sep = '{';
12608     }
12609     return sep;
12610 }
12611
12612 void
12613 SendToProgram(message, cps)
12614      char *message;
12615      ChessProgramState *cps;
12616 {
12617     int count, outCount, error;
12618     char buf[MSG_SIZ];
12619
12620     if (cps->pr == NULL) return;
12621     Attention(cps);
12622     
12623     if (appData.debugMode) {
12624         TimeMark now;
12625         GetTimeMark(&now);
12626         fprintf(debugFP, "%ld >%-6s: %s", 
12627                 SubtractTimeMarks(&now, &programStartTime),
12628                 cps->which, message);
12629     }
12630     
12631     count = strlen(message);
12632     outCount = OutputToProcess(cps->pr, message, count, &error);
12633     if (outCount < count && !exiting 
12634                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12635         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12636         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12637             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12638                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12639                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12640             } else {
12641                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12642             }
12643             gameInfo.resultDetails = StrSave(buf);
12644         }
12645         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12646     }
12647 }
12648
12649 void
12650 ReceiveFromProgram(isr, closure, message, count, error)
12651      InputSourceRef isr;
12652      VOIDSTAR closure;
12653      char *message;
12654      int count;
12655      int error;
12656 {
12657     char *end_str;
12658     char buf[MSG_SIZ];
12659     ChessProgramState *cps = (ChessProgramState *)closure;
12660
12661     if (isr != cps->isr) return; /* Killed intentionally */
12662     if (count <= 0) {
12663         if (count == 0) {
12664             sprintf(buf,
12665                     _("Error: %s chess program (%s) exited unexpectedly"),
12666                     cps->which, cps->program);
12667         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12668                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12669                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12670                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12671                 } else {
12672                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12673                 }
12674                 gameInfo.resultDetails = StrSave(buf);
12675             }
12676             RemoveInputSource(cps->isr);
12677             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12678         } else {
12679             sprintf(buf,
12680                     _("Error reading from %s chess program (%s)"),
12681                     cps->which, cps->program);
12682             RemoveInputSource(cps->isr);
12683
12684             /* [AS] Program is misbehaving badly... kill it */
12685             if( count == -2 ) {
12686                 DestroyChildProcess( cps->pr, 9 );
12687                 cps->pr = NoProc;
12688             }
12689
12690             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12691         }
12692         return;
12693     }
12694     
12695     if ((end_str = strchr(message, '\r')) != NULL)
12696       *end_str = NULLCHAR;
12697     if ((end_str = strchr(message, '\n')) != NULL)
12698       *end_str = NULLCHAR;
12699     
12700     if (appData.debugMode) {
12701         TimeMark now; int print = 1;
12702         char *quote = ""; char c; int i;
12703
12704         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12705                 char start = message[0];
12706                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12707                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12708                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12709                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12710                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12711                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12712                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12713                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12714                         { quote = "# "; print = (appData.engineComments == 2); }
12715                 message[0] = start; // restore original message
12716         }
12717         if(print) {
12718                 GetTimeMark(&now);
12719                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12720                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12721                         quote,
12722                         message);
12723         }
12724     }
12725
12726     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12727     if (appData.icsEngineAnalyze) {
12728         if (strstr(message, "whisper") != NULL ||
12729              strstr(message, "kibitz") != NULL || 
12730             strstr(message, "tellics") != NULL) return;
12731     }
12732
12733     HandleMachineMove(message, cps);
12734 }
12735
12736
12737 void
12738 SendTimeControl(cps, mps, tc, inc, sd, st)
12739      ChessProgramState *cps;
12740      int mps, inc, sd, st;
12741      long tc;
12742 {
12743     char buf[MSG_SIZ];
12744     int seconds;
12745
12746     if( timeControl_2 > 0 ) {
12747         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12748             tc = timeControl_2;
12749         }
12750     }
12751     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12752     inc /= cps->timeOdds;
12753     st  /= cps->timeOdds;
12754
12755     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12756
12757     if (st > 0) {
12758       /* Set exact time per move, normally using st command */
12759       if (cps->stKludge) {
12760         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12761         seconds = st % 60;
12762         if (seconds == 0) {
12763           sprintf(buf, "level 1 %d\n", st/60);
12764         } else {
12765           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12766         }
12767       } else {
12768         sprintf(buf, "st %d\n", st);
12769       }
12770     } else {
12771       /* Set conventional or incremental time control, using level command */
12772       if (seconds == 0) {
12773         /* Note old gnuchess bug -- minutes:seconds used to not work.
12774            Fixed in later versions, but still avoid :seconds
12775            when seconds is 0. */
12776         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12777       } else {
12778         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12779                 seconds, inc/1000);
12780       }
12781     }
12782     SendToProgram(buf, cps);
12783
12784     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12785     /* Orthogonally, limit search to given depth */
12786     if (sd > 0) {
12787       if (cps->sdKludge) {
12788         sprintf(buf, "depth\n%d\n", sd);
12789       } else {
12790         sprintf(buf, "sd %d\n", sd);
12791       }
12792       SendToProgram(buf, cps);
12793     }
12794
12795     if(cps->nps > 0) { /* [HGM] nps */
12796         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12797         else {
12798                 sprintf(buf, "nps %d\n", cps->nps);
12799               SendToProgram(buf, cps);
12800         }
12801     }
12802 }
12803
12804 ChessProgramState *WhitePlayer()
12805 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12806 {
12807     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12808        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12809         return &second;
12810     return &first;
12811 }
12812
12813 void
12814 SendTimeRemaining(cps, machineWhite)
12815      ChessProgramState *cps;
12816      int /*boolean*/ machineWhite;
12817 {
12818     char message[MSG_SIZ];
12819     long time, otime;
12820
12821     /* Note: this routine must be called when the clocks are stopped
12822        or when they have *just* been set or switched; otherwise
12823        it will be off by the time since the current tick started.
12824     */
12825     if (machineWhite) {
12826         time = whiteTimeRemaining / 10;
12827         otime = blackTimeRemaining / 10;
12828     } else {
12829         time = blackTimeRemaining / 10;
12830         otime = whiteTimeRemaining / 10;
12831     }
12832     /* [HGM] translate opponent's time by time-odds factor */
12833     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12834     if (appData.debugMode) {
12835         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12836     }
12837
12838     if (time <= 0) time = 1;
12839     if (otime <= 0) otime = 1;
12840     
12841     sprintf(message, "time %ld\n", time);
12842     SendToProgram(message, cps);
12843
12844     sprintf(message, "otim %ld\n", otime);
12845     SendToProgram(message, cps);
12846 }
12847
12848 int
12849 BoolFeature(p, name, loc, cps)
12850      char **p;
12851      char *name;
12852      int *loc;
12853      ChessProgramState *cps;
12854 {
12855   char buf[MSG_SIZ];
12856   int len = strlen(name);
12857   int val;
12858   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12859     (*p) += len + 1;
12860     sscanf(*p, "%d", &val);
12861     *loc = (val != 0);
12862     while (**p && **p != ' ') (*p)++;
12863     sprintf(buf, "accepted %s\n", name);
12864     SendToProgram(buf, cps);
12865     return TRUE;
12866   }
12867   return FALSE;
12868 }
12869
12870 int
12871 IntFeature(p, name, loc, cps)
12872      char **p;
12873      char *name;
12874      int *loc;
12875      ChessProgramState *cps;
12876 {
12877   char buf[MSG_SIZ];
12878   int len = strlen(name);
12879   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12880     (*p) += len + 1;
12881     sscanf(*p, "%d", loc);
12882     while (**p && **p != ' ') (*p)++;
12883     sprintf(buf, "accepted %s\n", name);
12884     SendToProgram(buf, cps);
12885     return TRUE;
12886   }
12887   return FALSE;
12888 }
12889
12890 int
12891 StringFeature(p, name, loc, cps)
12892      char **p;
12893      char *name;
12894      char loc[];
12895      ChessProgramState *cps;
12896 {
12897   char buf[MSG_SIZ];
12898   int len = strlen(name);
12899   if (strncmp((*p), name, len) == 0
12900       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12901     (*p) += len + 2;
12902     sscanf(*p, "%[^\"]", loc);
12903     while (**p && **p != '\"') (*p)++;
12904     if (**p == '\"') (*p)++;
12905     sprintf(buf, "accepted %s\n", name);
12906     SendToProgram(buf, cps);
12907     return TRUE;
12908   }
12909   return FALSE;
12910 }
12911
12912 int 
12913 ParseOption(Option *opt, ChessProgramState *cps)
12914 // [HGM] options: process the string that defines an engine option, and determine
12915 // name, type, default value, and allowed value range
12916 {
12917         char *p, *q, buf[MSG_SIZ];
12918         int n, min = (-1)<<31, max = 1<<31, def;
12919
12920         if(p = strstr(opt->name, " -spin ")) {
12921             if((n = sscanf(p, " -spin %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;
12929         } else if((p = strstr(opt->name, " -slider "))) {
12930             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12931             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12932             if(max < min) max = min; // enforce consistency
12933             if(def < min) def = min;
12934             if(def > max) def = max;
12935             opt->value = def;
12936             opt->min = min;
12937             opt->max = max;
12938             opt->type = Spin; // Slider;
12939         } else if((p = strstr(opt->name, " -string "))) {
12940             opt->textValue = p+9;
12941             opt->type = TextBox;
12942         } else if((p = strstr(opt->name, " -file "))) {
12943             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12944             opt->textValue = p+7;
12945             opt->type = TextBox; // FileName;
12946         } else if((p = strstr(opt->name, " -path "))) {
12947             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12948             opt->textValue = p+7;
12949             opt->type = TextBox; // PathName;
12950         } else if(p = strstr(opt->name, " -check ")) {
12951             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12952             opt->value = (def != 0);
12953             opt->type = CheckBox;
12954         } else if(p = strstr(opt->name, " -combo ")) {
12955             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12956             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12957             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12958             opt->value = n = 0;
12959             while(q = StrStr(q, " /// ")) {
12960                 n++; *q = 0;    // count choices, and null-terminate each of them
12961                 q += 5;
12962                 if(*q == '*') { // remember default, which is marked with * prefix
12963                     q++;
12964                     opt->value = n;
12965                 }
12966                 cps->comboList[cps->comboCnt++] = q;
12967             }
12968             cps->comboList[cps->comboCnt++] = NULL;
12969             opt->max = n + 1;
12970             opt->type = ComboBox;
12971         } else if(p = strstr(opt->name, " -button")) {
12972             opt->type = Button;
12973         } else if(p = strstr(opt->name, " -save")) {
12974             opt->type = SaveButton;
12975         } else return FALSE;
12976         *p = 0; // terminate option name
12977         // now look if the command-line options define a setting for this engine option.
12978         if(cps->optionSettings && cps->optionSettings[0])
12979             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12980         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12981                 sprintf(buf, "option %s", p);
12982                 if(p = strstr(buf, ",")) *p = 0;
12983                 strcat(buf, "\n");
12984                 SendToProgram(buf, cps);
12985         }
12986         return TRUE;
12987 }
12988
12989 void
12990 FeatureDone(cps, val)
12991      ChessProgramState* cps;
12992      int val;
12993 {
12994   DelayedEventCallback cb = GetDelayedEvent();
12995   if ((cb == InitBackEnd3 && cps == &first) ||
12996       (cb == TwoMachinesEventIfReady && cps == &second)) {
12997     CancelDelayedEvent();
12998     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12999   }
13000   cps->initDone = val;
13001 }
13002
13003 /* Parse feature command from engine */
13004 void
13005 ParseFeatures(args, cps)
13006      char* args;
13007      ChessProgramState *cps;  
13008 {
13009   char *p = args;
13010   char *q;
13011   int val;
13012   char buf[MSG_SIZ];
13013
13014   for (;;) {
13015     while (*p == ' ') p++;
13016     if (*p == NULLCHAR) return;
13017
13018     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13019     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13020     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13021     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13022     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13023     if (BoolFeature(&p, "reuse", &val, cps)) {
13024       /* Engine can disable reuse, but can't enable it if user said no */
13025       if (!val) cps->reuse = FALSE;
13026       continue;
13027     }
13028     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13029     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13030       if (gameMode == TwoMachinesPlay) {
13031         DisplayTwoMachinesTitle();
13032       } else {
13033         DisplayTitle("");
13034       }
13035       continue;
13036     }
13037     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13038     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13039     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13040     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13041     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13042     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13043     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13044     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13045     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13046     if (IntFeature(&p, "done", &val, cps)) {
13047       FeatureDone(cps, val);
13048       continue;
13049     }
13050     /* Added by Tord: */
13051     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13052     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13053     /* End of additions by Tord */
13054
13055     /* [HGM] added features: */
13056     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13057     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13058     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13059     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13060     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13061     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13062     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13063         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13064             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13065             SendToProgram(buf, cps);
13066             continue;
13067         }
13068         if(cps->nrOptions >= MAX_OPTIONS) {
13069             cps->nrOptions--;
13070             sprintf(buf, "%s engine has too many options\n", cps->which);
13071             DisplayError(buf, 0);
13072         }
13073         continue;
13074     }
13075     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13076     /* End of additions by HGM */
13077
13078     /* unknown feature: complain and skip */
13079     q = p;
13080     while (*q && *q != '=') q++;
13081     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13082     SendToProgram(buf, cps);
13083     p = q;
13084     if (*p == '=') {
13085       p++;
13086       if (*p == '\"') {
13087         p++;
13088         while (*p && *p != '\"') p++;
13089         if (*p == '\"') p++;
13090       } else {
13091         while (*p && *p != ' ') p++;
13092       }
13093     }
13094   }
13095
13096 }
13097
13098 void
13099 PeriodicUpdatesEvent(newState)
13100      int newState;
13101 {
13102     if (newState == appData.periodicUpdates)
13103       return;
13104
13105     appData.periodicUpdates=newState;
13106
13107     /* Display type changes, so update it now */
13108 //    DisplayAnalysis();
13109
13110     /* Get the ball rolling again... */
13111     if (newState) {
13112         AnalysisPeriodicEvent(1);
13113         StartAnalysisClock();
13114     }
13115 }
13116
13117 void
13118 PonderNextMoveEvent(newState)
13119      int newState;
13120 {
13121     if (newState == appData.ponderNextMove) return;
13122     if (gameMode == EditPosition) EditPositionDone(TRUE);
13123     if (newState) {
13124         SendToProgram("hard\n", &first);
13125         if (gameMode == TwoMachinesPlay) {
13126             SendToProgram("hard\n", &second);
13127         }
13128     } else {
13129         SendToProgram("easy\n", &first);
13130         thinkOutput[0] = NULLCHAR;
13131         if (gameMode == TwoMachinesPlay) {
13132             SendToProgram("easy\n", &second);
13133         }
13134     }
13135     appData.ponderNextMove = newState;
13136 }
13137
13138 void
13139 NewSettingEvent(option, command, value)
13140      char *command;
13141      int option, value;
13142 {
13143     char buf[MSG_SIZ];
13144
13145     if (gameMode == EditPosition) EditPositionDone(TRUE);
13146     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13147     SendToProgram(buf, &first);
13148     if (gameMode == TwoMachinesPlay) {
13149         SendToProgram(buf, &second);
13150     }
13151 }
13152
13153 void
13154 ShowThinkingEvent()
13155 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13156 {
13157     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13158     int newState = appData.showThinking
13159         // [HGM] thinking: other features now need thinking output as well
13160         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13161     
13162     if (oldState == newState) return;
13163     oldState = newState;
13164     if (gameMode == EditPosition) EditPositionDone(TRUE);
13165     if (oldState) {
13166         SendToProgram("post\n", &first);
13167         if (gameMode == TwoMachinesPlay) {
13168             SendToProgram("post\n", &second);
13169         }
13170     } else {
13171         SendToProgram("nopost\n", &first);
13172         thinkOutput[0] = NULLCHAR;
13173         if (gameMode == TwoMachinesPlay) {
13174             SendToProgram("nopost\n", &second);
13175         }
13176     }
13177 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13178 }
13179
13180 void
13181 AskQuestionEvent(title, question, replyPrefix, which)
13182      char *title; char *question; char *replyPrefix; char *which;
13183 {
13184   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13185   if (pr == NoProc) return;
13186   AskQuestion(title, question, replyPrefix, pr);
13187 }
13188
13189 void
13190 DisplayMove(moveNumber)
13191      int moveNumber;
13192 {
13193     char message[MSG_SIZ];
13194     char res[MSG_SIZ];
13195     char cpThinkOutput[MSG_SIZ];
13196
13197     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13198     
13199     if (moveNumber == forwardMostMove - 1 || 
13200         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13201
13202         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13203
13204         if (strchr(cpThinkOutput, '\n')) {
13205             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13206         }
13207     } else {
13208         *cpThinkOutput = NULLCHAR;
13209     }
13210
13211     /* [AS] Hide thinking from human user */
13212     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13213         *cpThinkOutput = NULLCHAR;
13214         if( thinkOutput[0] != NULLCHAR ) {
13215             int i;
13216
13217             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13218                 cpThinkOutput[i] = '.';
13219             }
13220             cpThinkOutput[i] = NULLCHAR;
13221             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13222         }
13223     }
13224
13225     if (moveNumber == forwardMostMove - 1 &&
13226         gameInfo.resultDetails != NULL) {
13227         if (gameInfo.resultDetails[0] == NULLCHAR) {
13228             sprintf(res, " %s", PGNResult(gameInfo.result));
13229         } else {
13230             sprintf(res, " {%s} %s",
13231                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13232         }
13233     } else {
13234         res[0] = NULLCHAR;
13235     }
13236
13237     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13238         DisplayMessage(res, cpThinkOutput);
13239     } else {
13240         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13241                 WhiteOnMove(moveNumber) ? " " : ".. ",
13242                 parseList[moveNumber], res);
13243         DisplayMessage(message, cpThinkOutput);
13244     }
13245 }
13246
13247 void
13248 DisplayComment(moveNumber, text)
13249      int moveNumber;
13250      char *text;
13251 {
13252     char title[MSG_SIZ];
13253     char buf[8000]; // comment can be long!
13254     int score, depth;
13255     
13256     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13257       strcpy(title, "Comment");
13258     } else {
13259       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13260               WhiteOnMove(moveNumber) ? " " : ".. ",
13261               parseList[moveNumber]);
13262     }
13263     // [HGM] PV info: display PV info together with (or as) comment
13264     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13265       if(text == NULL) text = "";                                           
13266       score = pvInfoList[moveNumber].score;
13267       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13268               depth, (pvInfoList[moveNumber].time+50)/100, text);
13269       text = buf;
13270     }
13271     if (text != NULL && (appData.autoDisplayComment || commentUp))
13272         CommentPopUp(title, text);
13273 }
13274
13275 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13276  * might be busy thinking or pondering.  It can be omitted if your
13277  * gnuchess is configured to stop thinking immediately on any user
13278  * input.  However, that gnuchess feature depends on the FIONREAD
13279  * ioctl, which does not work properly on some flavors of Unix.
13280  */
13281 void
13282 Attention(cps)
13283      ChessProgramState *cps;
13284 {
13285 #if ATTENTION
13286     if (!cps->useSigint) return;
13287     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13288     switch (gameMode) {
13289       case MachinePlaysWhite:
13290       case MachinePlaysBlack:
13291       case TwoMachinesPlay:
13292       case IcsPlayingWhite:
13293       case IcsPlayingBlack:
13294       case AnalyzeMode:
13295       case AnalyzeFile:
13296         /* Skip if we know it isn't thinking */
13297         if (!cps->maybeThinking) return;
13298         if (appData.debugMode)
13299           fprintf(debugFP, "Interrupting %s\n", cps->which);
13300         InterruptChildProcess(cps->pr);
13301         cps->maybeThinking = FALSE;
13302         break;
13303       default:
13304         break;
13305     }
13306 #endif /*ATTENTION*/
13307 }
13308
13309 int
13310 CheckFlags()
13311 {
13312     if (whiteTimeRemaining <= 0) {
13313         if (!whiteFlag) {
13314             whiteFlag = TRUE;
13315             if (appData.icsActive) {
13316                 if (appData.autoCallFlag &&
13317                     gameMode == IcsPlayingBlack && !blackFlag) {
13318                   SendToICS(ics_prefix);
13319                   SendToICS("flag\n");
13320                 }
13321             } else {
13322                 if (blackFlag) {
13323                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13324                 } else {
13325                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13326                     if (appData.autoCallFlag) {
13327                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13328                         return TRUE;
13329                     }
13330                 }
13331             }
13332         }
13333     }
13334     if (blackTimeRemaining <= 0) {
13335         if (!blackFlag) {
13336             blackFlag = TRUE;
13337             if (appData.icsActive) {
13338                 if (appData.autoCallFlag &&
13339                     gameMode == IcsPlayingWhite && !whiteFlag) {
13340                   SendToICS(ics_prefix);
13341                   SendToICS("flag\n");
13342                 }
13343             } else {
13344                 if (whiteFlag) {
13345                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13346                 } else {
13347                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13348                     if (appData.autoCallFlag) {
13349                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13350                         return TRUE;
13351                     }
13352                 }
13353             }
13354         }
13355     }
13356     return FALSE;
13357 }
13358
13359 void
13360 CheckTimeControl()
13361 {
13362     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13363         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13364
13365     /*
13366      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13367      */
13368     if ( !WhiteOnMove(forwardMostMove) )
13369         /* White made time control */
13370         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13371         /* [HGM] time odds: correct new time quota for time odds! */
13372                                             / WhitePlayer()->timeOdds;
13373       else
13374         /* Black made time control */
13375         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13376                                             / WhitePlayer()->other->timeOdds;
13377 }
13378
13379 void
13380 DisplayBothClocks()
13381 {
13382     int wom = gameMode == EditPosition ?
13383       !blackPlaysFirst : WhiteOnMove(currentMove);
13384     DisplayWhiteClock(whiteTimeRemaining, wom);
13385     DisplayBlackClock(blackTimeRemaining, !wom);
13386 }
13387
13388
13389 /* Timekeeping seems to be a portability nightmare.  I think everyone
13390    has ftime(), but I'm really not sure, so I'm including some ifdefs
13391    to use other calls if you don't.  Clocks will be less accurate if
13392    you have neither ftime nor gettimeofday.
13393 */
13394
13395 /* VS 2008 requires the #include outside of the function */
13396 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13397 #include <sys/timeb.h>
13398 #endif
13399
13400 /* Get the current time as a TimeMark */
13401 void
13402 GetTimeMark(tm)
13403      TimeMark *tm;
13404 {
13405 #if HAVE_GETTIMEOFDAY
13406
13407     struct timeval timeVal;
13408     struct timezone timeZone;
13409
13410     gettimeofday(&timeVal, &timeZone);
13411     tm->sec = (long) timeVal.tv_sec; 
13412     tm->ms = (int) (timeVal.tv_usec / 1000L);
13413
13414 #else /*!HAVE_GETTIMEOFDAY*/
13415 #if HAVE_FTIME
13416
13417 // include <sys/timeb.h> / moved to just above start of function
13418     struct timeb timeB;
13419
13420     ftime(&timeB);
13421     tm->sec = (long) timeB.time;
13422     tm->ms = (int) timeB.millitm;
13423
13424 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13425     tm->sec = (long) time(NULL);
13426     tm->ms = 0;
13427 #endif
13428 #endif
13429 }
13430
13431 /* Return the difference in milliseconds between two
13432    time marks.  We assume the difference will fit in a long!
13433 */
13434 long
13435 SubtractTimeMarks(tm2, tm1)
13436      TimeMark *tm2, *tm1;
13437 {
13438     return 1000L*(tm2->sec - tm1->sec) +
13439            (long) (tm2->ms - tm1->ms);
13440 }
13441
13442
13443 /*
13444  * Code to manage the game clocks.
13445  *
13446  * In tournament play, black starts the clock and then white makes a move.
13447  * We give the human user a slight advantage if he is playing white---the
13448  * clocks don't run until he makes his first move, so it takes zero time.
13449  * Also, we don't account for network lag, so we could get out of sync
13450  * with GNU Chess's clock -- but then, referees are always right.  
13451  */
13452
13453 static TimeMark tickStartTM;
13454 static long intendedTickLength;
13455
13456 long
13457 NextTickLength(timeRemaining)
13458      long timeRemaining;
13459 {
13460     long nominalTickLength, nextTickLength;
13461
13462     if (timeRemaining > 0L && timeRemaining <= 10000L)
13463       nominalTickLength = 100L;
13464     else
13465       nominalTickLength = 1000L;
13466     nextTickLength = timeRemaining % nominalTickLength;
13467     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13468
13469     return nextTickLength;
13470 }
13471
13472 /* Adjust clock one minute up or down */
13473 void
13474 AdjustClock(Boolean which, int dir)
13475 {
13476     if(which) blackTimeRemaining += 60000*dir;
13477     else      whiteTimeRemaining += 60000*dir;
13478     DisplayBothClocks();
13479 }
13480
13481 /* Stop clocks and reset to a fresh time control */
13482 void
13483 ResetClocks() 
13484 {
13485     (void) StopClockTimer();
13486     if (appData.icsActive) {
13487         whiteTimeRemaining = blackTimeRemaining = 0;
13488     } else if (searchTime) {
13489         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13490         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13491     } else { /* [HGM] correct new time quote for time odds */
13492         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13493         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13494     }
13495     if (whiteFlag || blackFlag) {
13496         DisplayTitle("");
13497         whiteFlag = blackFlag = FALSE;
13498     }
13499     DisplayBothClocks();
13500 }
13501
13502 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13503
13504 /* Decrement running clock by amount of time that has passed */
13505 void
13506 DecrementClocks()
13507 {
13508     long timeRemaining;
13509     long lastTickLength, fudge;
13510     TimeMark now;
13511
13512     if (!appData.clockMode) return;
13513     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13514         
13515     GetTimeMark(&now);
13516
13517     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13518
13519     /* Fudge if we woke up a little too soon */
13520     fudge = intendedTickLength - lastTickLength;
13521     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13522
13523     if (WhiteOnMove(forwardMostMove)) {
13524         if(whiteNPS >= 0) lastTickLength = 0;
13525         timeRemaining = whiteTimeRemaining -= lastTickLength;
13526         DisplayWhiteClock(whiteTimeRemaining - fudge,
13527                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13528     } else {
13529         if(blackNPS >= 0) lastTickLength = 0;
13530         timeRemaining = blackTimeRemaining -= lastTickLength;
13531         DisplayBlackClock(blackTimeRemaining - fudge,
13532                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13533     }
13534
13535     if (CheckFlags()) return;
13536         
13537     tickStartTM = now;
13538     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13539     StartClockTimer(intendedTickLength);
13540
13541     /* if the time remaining has fallen below the alarm threshold, sound the
13542      * alarm. if the alarm has sounded and (due to a takeback or time control
13543      * with increment) the time remaining has increased to a level above the
13544      * threshold, reset the alarm so it can sound again. 
13545      */
13546     
13547     if (appData.icsActive && appData.icsAlarm) {
13548
13549         /* make sure we are dealing with the user's clock */
13550         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13551                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13552            )) return;
13553
13554         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13555             alarmSounded = FALSE;
13556         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13557             PlayAlarmSound();
13558             alarmSounded = TRUE;
13559         }
13560     }
13561 }
13562
13563
13564 /* A player has just moved, so stop the previously running
13565    clock and (if in clock mode) start the other one.
13566    We redisplay both clocks in case we're in ICS mode, because
13567    ICS gives us an update to both clocks after every move.
13568    Note that this routine is called *after* forwardMostMove
13569    is updated, so the last fractional tick must be subtracted
13570    from the color that is *not* on move now.
13571 */
13572 void
13573 SwitchClocks()
13574 {
13575     long lastTickLength;
13576     TimeMark now;
13577     int flagged = FALSE;
13578
13579     GetTimeMark(&now);
13580
13581     if (StopClockTimer() && appData.clockMode) {
13582         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13583         if (WhiteOnMove(forwardMostMove)) {
13584             if(blackNPS >= 0) lastTickLength = 0;
13585             blackTimeRemaining -= lastTickLength;
13586            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13587 //         if(pvInfoList[forwardMostMove-1].time == -1)
13588                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13589                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13590         } else {
13591            if(whiteNPS >= 0) lastTickLength = 0;
13592            whiteTimeRemaining -= lastTickLength;
13593            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13594 //         if(pvInfoList[forwardMostMove-1].time == -1)
13595                  pvInfoList[forwardMostMove-1].time = 
13596                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13597         }
13598         flagged = CheckFlags();
13599     }
13600     CheckTimeControl();
13601
13602     if (flagged || !appData.clockMode) return;
13603
13604     switch (gameMode) {
13605       case MachinePlaysBlack:
13606       case MachinePlaysWhite:
13607       case BeginningOfGame:
13608         if (pausing) return;
13609         break;
13610
13611       case EditGame:
13612       case PlayFromGameFile:
13613       case IcsExamining:
13614         return;
13615
13616       default:
13617         break;
13618     }
13619
13620     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13621         if(WhiteOnMove(forwardMostMove))
13622              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13623         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13624     }
13625
13626     tickStartTM = now;
13627     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13628       whiteTimeRemaining : blackTimeRemaining);
13629     StartClockTimer(intendedTickLength);
13630 }
13631         
13632
13633 /* Stop both clocks */
13634 void
13635 StopClocks()
13636 {       
13637     long lastTickLength;
13638     TimeMark now;
13639
13640     if (!StopClockTimer()) return;
13641     if (!appData.clockMode) return;
13642
13643     GetTimeMark(&now);
13644
13645     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13646     if (WhiteOnMove(forwardMostMove)) {
13647         if(whiteNPS >= 0) lastTickLength = 0;
13648         whiteTimeRemaining -= lastTickLength;
13649         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13650     } else {
13651         if(blackNPS >= 0) lastTickLength = 0;
13652         blackTimeRemaining -= lastTickLength;
13653         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13654     }
13655     CheckFlags();
13656 }
13657         
13658 /* Start clock of player on move.  Time may have been reset, so
13659    if clock is already running, stop and restart it. */
13660 void
13661 StartClocks()
13662 {
13663     (void) StopClockTimer(); /* in case it was running already */
13664     DisplayBothClocks();
13665     if (CheckFlags()) return;
13666
13667     if (!appData.clockMode) return;
13668     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13669
13670     GetTimeMark(&tickStartTM);
13671     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13672       whiteTimeRemaining : blackTimeRemaining);
13673
13674    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13675     whiteNPS = blackNPS = -1; 
13676     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13677        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13678         whiteNPS = first.nps;
13679     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13680        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13681         blackNPS = first.nps;
13682     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13683         whiteNPS = second.nps;
13684     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13685         blackNPS = second.nps;
13686     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13687
13688     StartClockTimer(intendedTickLength);
13689 }
13690
13691 char *
13692 TimeString(ms)
13693      long ms;
13694 {
13695     long second, minute, hour, day;
13696     char *sign = "";
13697     static char buf[32];
13698     
13699     if (ms > 0 && ms <= 9900) {
13700       /* convert milliseconds to tenths, rounding up */
13701       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13702
13703       sprintf(buf, " %03.1f ", tenths/10.0);
13704       return buf;
13705     }
13706
13707     /* convert milliseconds to seconds, rounding up */
13708     /* use floating point to avoid strangeness of integer division
13709        with negative dividends on many machines */
13710     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13711
13712     if (second < 0) {
13713         sign = "-";
13714         second = -second;
13715     }
13716     
13717     day = second / (60 * 60 * 24);
13718     second = second % (60 * 60 * 24);
13719     hour = second / (60 * 60);
13720     second = second % (60 * 60);
13721     minute = second / 60;
13722     second = second % 60;
13723     
13724     if (day > 0)
13725       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13726               sign, day, hour, minute, second);
13727     else if (hour > 0)
13728       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13729     else
13730       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13731     
13732     return buf;
13733 }
13734
13735
13736 /*
13737  * This is necessary because some C libraries aren't ANSI C compliant yet.
13738  */
13739 char *
13740 StrStr(string, match)
13741      char *string, *match;
13742 {
13743     int i, length;
13744     
13745     length = strlen(match);
13746     
13747     for (i = strlen(string) - length; i >= 0; i--, string++)
13748       if (!strncmp(match, string, length))
13749         return string;
13750     
13751     return NULL;
13752 }
13753
13754 char *
13755 StrCaseStr(string, match)
13756      char *string, *match;
13757 {
13758     int i, j, length;
13759     
13760     length = strlen(match);
13761     
13762     for (i = strlen(string) - length; i >= 0; i--, string++) {
13763         for (j = 0; j < length; j++) {
13764             if (ToLower(match[j]) != ToLower(string[j]))
13765               break;
13766         }
13767         if (j == length) return string;
13768     }
13769
13770     return NULL;
13771 }
13772
13773 #ifndef _amigados
13774 int
13775 StrCaseCmp(s1, s2)
13776      char *s1, *s2;
13777 {
13778     char c1, c2;
13779     
13780     for (;;) {
13781         c1 = ToLower(*s1++);
13782         c2 = ToLower(*s2++);
13783         if (c1 > c2) return 1;
13784         if (c1 < c2) return -1;
13785         if (c1 == NULLCHAR) return 0;
13786     }
13787 }
13788
13789
13790 int
13791 ToLower(c)
13792      int c;
13793 {
13794     return isupper(c) ? tolower(c) : c;
13795 }
13796
13797
13798 int
13799 ToUpper(c)
13800      int c;
13801 {
13802     return islower(c) ? toupper(c) : c;
13803 }
13804 #endif /* !_amigados    */
13805
13806 char *
13807 StrSave(s)
13808      char *s;
13809 {
13810     char *ret;
13811
13812     if ((ret = (char *) malloc(strlen(s) + 1))) {
13813         strcpy(ret, s);
13814     }
13815     return ret;
13816 }
13817
13818 char *
13819 StrSavePtr(s, savePtr)
13820      char *s, **savePtr;
13821 {
13822     if (*savePtr) {
13823         free(*savePtr);
13824     }
13825     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13826         strcpy(*savePtr, s);
13827     }
13828     return(*savePtr);
13829 }
13830
13831 char *
13832 PGNDate()
13833 {
13834     time_t clock;
13835     struct tm *tm;
13836     char buf[MSG_SIZ];
13837
13838     clock = time((time_t *)NULL);
13839     tm = localtime(&clock);
13840     sprintf(buf, "%04d.%02d.%02d",
13841             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13842     return StrSave(buf);
13843 }
13844
13845
13846 char *
13847 PositionToFEN(move, overrideCastling)
13848      int move;
13849      char *overrideCastling;
13850 {
13851     int i, j, fromX, fromY, toX, toY;
13852     int whiteToPlay;
13853     char buf[128];
13854     char *p, *q;
13855     int emptycount;
13856     ChessSquare piece;
13857
13858     whiteToPlay = (gameMode == EditPosition) ?
13859       !blackPlaysFirst : (move % 2 == 0);
13860     p = buf;
13861
13862     /* Piece placement data */
13863     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13864         emptycount = 0;
13865         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13866             if (boards[move][i][j] == EmptySquare) {
13867                 emptycount++;
13868             } else { ChessSquare piece = boards[move][i][j];
13869                 if (emptycount > 0) {
13870                     if(emptycount<10) /* [HGM] can be >= 10 */
13871                         *p++ = '0' + emptycount;
13872                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13873                     emptycount = 0;
13874                 }
13875                 if(PieceToChar(piece) == '+') {
13876                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13877                     *p++ = '+';
13878                     piece = (ChessSquare)(DEMOTED piece);
13879                 } 
13880                 *p++ = PieceToChar(piece);
13881                 if(p[-1] == '~') {
13882                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13883                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13884                     *p++ = '~';
13885                 }
13886             }
13887         }
13888         if (emptycount > 0) {
13889             if(emptycount<10) /* [HGM] can be >= 10 */
13890                 *p++ = '0' + emptycount;
13891             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13892             emptycount = 0;
13893         }
13894         *p++ = '/';
13895     }
13896     *(p - 1) = ' ';
13897
13898     /* [HGM] print Crazyhouse or Shogi holdings */
13899     if( gameInfo.holdingsWidth ) {
13900         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13901         q = p;
13902         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13903             piece = boards[move][i][BOARD_WIDTH-1];
13904             if( piece != EmptySquare )
13905               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13906                   *p++ = PieceToChar(piece);
13907         }
13908         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13909             piece = boards[move][BOARD_HEIGHT-i-1][0];
13910             if( piece != EmptySquare )
13911               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13912                   *p++ = PieceToChar(piece);
13913         }
13914
13915         if( q == p ) *p++ = '-';
13916         *p++ = ']';
13917         *p++ = ' ';
13918     }
13919
13920     /* Active color */
13921     *p++ = whiteToPlay ? 'w' : 'b';
13922     *p++ = ' ';
13923
13924   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13925     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13926   } else {
13927   if(nrCastlingRights) {
13928      q = p;
13929      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13930        /* [HGM] write directly from rights */
13931            if(boards[move][CASTLING][2] != NoRights &&
13932               boards[move][CASTLING][0] != NoRights   )
13933                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13934            if(boards[move][CASTLING][2] != NoRights &&
13935               boards[move][CASTLING][1] != NoRights   )
13936                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13937            if(boards[move][CASTLING][5] != NoRights &&
13938               boards[move][CASTLING][3] != NoRights   )
13939                 *p++ = boards[move][CASTLING][3] + AAA;
13940            if(boards[move][CASTLING][5] != NoRights &&
13941               boards[move][CASTLING][4] != NoRights   )
13942                 *p++ = boards[move][CASTLING][4] + AAA;
13943      } else {
13944
13945         /* [HGM] write true castling rights */
13946         if( nrCastlingRights == 6 ) {
13947             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13948                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13949             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13950                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13951             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13952                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13953             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13954                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13955         }
13956      }
13957      if (q == p) *p++ = '-'; /* No castling rights */
13958      *p++ = ' ';
13959   }
13960
13961   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13962      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13963     /* En passant target square */
13964     if (move > backwardMostMove) {
13965         fromX = moveList[move - 1][0] - AAA;
13966         fromY = moveList[move - 1][1] - ONE;
13967         toX = moveList[move - 1][2] - AAA;
13968         toY = moveList[move - 1][3] - ONE;
13969         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13970             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13971             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13972             fromX == toX) {
13973             /* 2-square pawn move just happened */
13974             *p++ = toX + AAA;
13975             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13976         } else {
13977             *p++ = '-';
13978         }
13979     } else if(move == backwardMostMove) {
13980         // [HGM] perhaps we should always do it like this, and forget the above?
13981         if((signed char)boards[move][EP_STATUS] >= 0) {
13982             *p++ = boards[move][EP_STATUS] + AAA;
13983             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13984         } else {
13985             *p++ = '-';
13986         }
13987     } else {
13988         *p++ = '-';
13989     }
13990     *p++ = ' ';
13991   }
13992   }
13993
13994     /* [HGM] find reversible plies */
13995     {   int i = 0, j=move;
13996
13997         if (appData.debugMode) { int k;
13998             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13999             for(k=backwardMostMove; k<=forwardMostMove; k++)
14000                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14001
14002         }
14003
14004         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14005         if( j == backwardMostMove ) i += initialRulePlies;
14006         sprintf(p, "%d ", i);
14007         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14008     }
14009     /* Fullmove number */
14010     sprintf(p, "%d", (move / 2) + 1);
14011     
14012     return StrSave(buf);
14013 }
14014
14015 Boolean
14016 ParseFEN(board, blackPlaysFirst, fen)
14017     Board board;
14018      int *blackPlaysFirst;
14019      char *fen;
14020 {
14021     int i, j;
14022     char *p;
14023     int emptycount;
14024     ChessSquare piece;
14025
14026     p = fen;
14027
14028     /* [HGM] by default clear Crazyhouse holdings, if present */
14029     if(gameInfo.holdingsWidth) {
14030        for(i=0; i<BOARD_HEIGHT; i++) {
14031            board[i][0]             = EmptySquare; /* black holdings */
14032            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14033            board[i][1]             = (ChessSquare) 0; /* black counts */
14034            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14035        }
14036     }
14037
14038     /* Piece placement data */
14039     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14040         j = 0;
14041         for (;;) {
14042             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14043                 if (*p == '/') p++;
14044                 emptycount = gameInfo.boardWidth - j;
14045                 while (emptycount--)
14046                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14047                 break;
14048 #if(BOARD_FILES >= 10)
14049             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14050                 p++; emptycount=10;
14051                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14052                 while (emptycount--)
14053                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14054 #endif
14055             } else if (isdigit(*p)) {
14056                 emptycount = *p++ - '0';
14057                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14058                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14059                 while (emptycount--)
14060                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14061             } else if (*p == '+' || isalpha(*p)) {
14062                 if (j >= gameInfo.boardWidth) return FALSE;
14063                 if(*p=='+') {
14064                     piece = CharToPiece(*++p);
14065                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14066                     piece = (ChessSquare) (PROMOTED piece ); p++;
14067                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14068                 } else piece = CharToPiece(*p++);
14069
14070                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14071                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14072                     piece = (ChessSquare) (PROMOTED piece);
14073                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14074                     p++;
14075                 }
14076                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14077             } else {
14078                 return FALSE;
14079             }
14080         }
14081     }
14082     while (*p == '/' || *p == ' ') p++;
14083
14084     /* [HGM] look for Crazyhouse holdings here */
14085     while(*p==' ') p++;
14086     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14087         if(*p == '[') p++;
14088         if(*p == '-' ) *p++; /* empty holdings */ else {
14089             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14090             /* if we would allow FEN reading to set board size, we would   */
14091             /* have to add holdings and shift the board read so far here   */
14092             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14093                 *p++;
14094                 if((int) piece >= (int) BlackPawn ) {
14095                     i = (int)piece - (int)BlackPawn;
14096                     i = PieceToNumber((ChessSquare)i);
14097                     if( i >= gameInfo.holdingsSize ) return FALSE;
14098                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14099                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14100                 } else {
14101                     i = (int)piece - (int)WhitePawn;
14102                     i = PieceToNumber((ChessSquare)i);
14103                     if( i >= gameInfo.holdingsSize ) return FALSE;
14104                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14105                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14106                 }
14107             }
14108         }
14109         if(*p == ']') *p++;
14110     }
14111
14112     while(*p == ' ') p++;
14113
14114     /* Active color */
14115     switch (*p++) {
14116       case 'w':
14117         *blackPlaysFirst = FALSE;
14118         break;
14119       case 'b': 
14120         *blackPlaysFirst = TRUE;
14121         break;
14122       default:
14123         return FALSE;
14124     }
14125
14126     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14127     /* return the extra info in global variiables             */
14128
14129     /* set defaults in case FEN is incomplete */
14130     board[EP_STATUS] = EP_UNKNOWN;
14131     for(i=0; i<nrCastlingRights; i++ ) {
14132         board[CASTLING][i] =
14133             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14134     }   /* assume possible unless obviously impossible */
14135     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14136     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14137     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14138     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14139     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14140     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14141     FENrulePlies = 0;
14142
14143     while(*p==' ') p++;
14144     if(nrCastlingRights) {
14145       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14146           /* castling indicator present, so default becomes no castlings */
14147           for(i=0; i<nrCastlingRights; i++ ) {
14148                  board[CASTLING][i] = NoRights;
14149           }
14150       }
14151       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14152              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14153              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14154              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14155         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14156
14157         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14158             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14159             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14160         }
14161         switch(c) {
14162           case'K':
14163               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14164               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14165               board[CASTLING][2] = whiteKingFile;
14166               break;
14167           case'Q':
14168               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14169               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14170               board[CASTLING][2] = whiteKingFile;
14171               break;
14172           case'k':
14173               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14174               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14175               board[CASTLING][5] = blackKingFile;
14176               break;
14177           case'q':
14178               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14179               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14180               board[CASTLING][5] = blackKingFile;
14181           case '-':
14182               break;
14183           default: /* FRC castlings */
14184               if(c >= 'a') { /* black rights */
14185                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14186                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14187                   if(i == BOARD_RGHT) break;
14188                   board[CASTLING][5] = i;
14189                   c -= AAA;
14190                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14191                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14192                   if(c > i)
14193                       board[CASTLING][3] = c;
14194                   else
14195                       board[CASTLING][4] = c;
14196               } else { /* white rights */
14197                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14198                     if(board[0][i] == WhiteKing) break;
14199                   if(i == BOARD_RGHT) break;
14200                   board[CASTLING][2] = i;
14201                   c -= AAA - 'a' + 'A';
14202                   if(board[0][c] >= WhiteKing) break;
14203                   if(c > i)
14204                       board[CASTLING][0] = c;
14205                   else
14206                       board[CASTLING][1] = c;
14207               }
14208         }
14209       }
14210     if (appData.debugMode) {
14211         fprintf(debugFP, "FEN castling rights:");
14212         for(i=0; i<nrCastlingRights; i++)
14213         fprintf(debugFP, " %d", board[CASTLING][i]);
14214         fprintf(debugFP, "\n");
14215     }
14216
14217       while(*p==' ') p++;
14218     }
14219
14220     /* read e.p. field in games that know e.p. capture */
14221     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14222        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14223       if(*p=='-') {
14224         p++; board[EP_STATUS] = EP_NONE;
14225       } else {
14226          char c = *p++ - AAA;
14227
14228          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14229          if(*p >= '0' && *p <='9') *p++;
14230          board[EP_STATUS] = c;
14231       }
14232     }
14233
14234
14235     if(sscanf(p, "%d", &i) == 1) {
14236         FENrulePlies = i; /* 50-move ply counter */
14237         /* (The move number is still ignored)    */
14238     }
14239
14240     return TRUE;
14241 }
14242       
14243 void
14244 EditPositionPasteFEN(char *fen)
14245 {
14246   if (fen != NULL) {
14247     Board initial_position;
14248
14249     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14250       DisplayError(_("Bad FEN position in clipboard"), 0);
14251       return ;
14252     } else {
14253       int savedBlackPlaysFirst = blackPlaysFirst;
14254       EditPositionEvent();
14255       blackPlaysFirst = savedBlackPlaysFirst;
14256       CopyBoard(boards[0], initial_position);
14257       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14258       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14259       DisplayBothClocks();
14260       DrawPosition(FALSE, boards[currentMove]);
14261     }
14262   }
14263 }
14264
14265 static char cseq[12] = "\\   ";
14266
14267 Boolean set_cont_sequence(char *new_seq)
14268 {
14269     int len;
14270     Boolean ret;
14271
14272     // handle bad attempts to set the sequence
14273         if (!new_seq)
14274                 return 0; // acceptable error - no debug
14275
14276     len = strlen(new_seq);
14277     ret = (len > 0) && (len < sizeof(cseq));
14278     if (ret)
14279         strcpy(cseq, new_seq);
14280     else if (appData.debugMode)
14281         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14282     return ret;
14283 }
14284
14285 /*
14286     reformat a source message so words don't cross the width boundary.  internal
14287     newlines are not removed.  returns the wrapped size (no null character unless
14288     included in source message).  If dest is NULL, only calculate the size required
14289     for the dest buffer.  lp argument indicats line position upon entry, and it's
14290     passed back upon exit.
14291 */
14292 int wrap(char *dest, char *src, int count, int width, int *lp)
14293 {
14294     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14295
14296     cseq_len = strlen(cseq);
14297     old_line = line = *lp;
14298     ansi = len = clen = 0;
14299
14300     for (i=0; i < count; i++)
14301     {
14302         if (src[i] == '\033')
14303             ansi = 1;
14304
14305         // if we hit the width, back up
14306         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14307         {
14308             // store i & len in case the word is too long
14309             old_i = i, old_len = len;
14310
14311             // find the end of the last word
14312             while (i && src[i] != ' ' && src[i] != '\n')
14313             {
14314                 i--;
14315                 len--;
14316             }
14317
14318             // word too long?  restore i & len before splitting it
14319             if ((old_i-i+clen) >= width)
14320             {
14321                 i = old_i;
14322                 len = old_len;
14323             }
14324
14325             // extra space?
14326             if (i && src[i-1] == ' ')
14327                 len--;
14328
14329             if (src[i] != ' ' && src[i] != '\n')
14330             {
14331                 i--;
14332                 if (len)
14333                     len--;
14334             }
14335
14336             // now append the newline and continuation sequence
14337             if (dest)
14338                 dest[len] = '\n';
14339             len++;
14340             if (dest)
14341                 strncpy(dest+len, cseq, cseq_len);
14342             len += cseq_len;
14343             line = cseq_len;
14344             clen = cseq_len;
14345             continue;
14346         }
14347
14348         if (dest)
14349             dest[len] = src[i];
14350         len++;
14351         if (!ansi)
14352             line++;
14353         if (src[i] == '\n')
14354             line = 0;
14355         if (src[i] == 'm')
14356             ansi = 0;
14357     }
14358     if (dest && appData.debugMode)
14359     {
14360         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14361             count, width, line, len, *lp);
14362         show_bytes(debugFP, src, count);
14363         fprintf(debugFP, "\ndest: ");
14364         show_bytes(debugFP, dest, len);
14365         fprintf(debugFP, "\n");
14366     }
14367     *lp = dest ? line : old_line;
14368
14369     return len;
14370 }
14371
14372 // [HGM] vari: routines for shelving variations
14373
14374 void 
14375 PushTail(int firstMove, int lastMove)
14376 {
14377         int i, j, nrMoves = lastMove - firstMove;
14378
14379         if(appData.icsActive) { // only in local mode
14380                 forwardMostMove = currentMove; // mimic old ICS behavior
14381                 return;
14382         }
14383         if(storedGames >= MAX_VARIATIONS-1) return;
14384
14385         // push current tail of game on stack
14386         savedResult[storedGames] = gameInfo.result;
14387         savedDetails[storedGames] = gameInfo.resultDetails;
14388         gameInfo.resultDetails = NULL;
14389         savedFirst[storedGames] = firstMove;
14390         savedLast [storedGames] = lastMove;
14391         savedFramePtr[storedGames] = framePtr;
14392         framePtr -= nrMoves; // reserve space for the boards
14393         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14394             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14395             for(j=0; j<MOVE_LEN; j++)
14396                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14397             for(j=0; j<2*MOVE_LEN; j++)
14398                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14399             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14400             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14401             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14402             pvInfoList[firstMove+i-1].depth = 0;
14403             commentList[framePtr+i] = commentList[firstMove+i];
14404             commentList[firstMove+i] = NULL;
14405         }
14406
14407         storedGames++;
14408         forwardMostMove = currentMove; // truncte game so we can start variation
14409         if(storedGames == 1) GreyRevert(FALSE);
14410 }
14411
14412 Boolean
14413 PopTail(Boolean annotate)
14414 {
14415         int i, j, nrMoves;
14416         char buf[8000], moveBuf[20];
14417
14418         if(appData.icsActive) return FALSE; // only in local mode
14419         if(!storedGames) return FALSE; // sanity
14420
14421         storedGames--;
14422         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14423         nrMoves = savedLast[storedGames] - currentMove;
14424         if(annotate) {
14425                 int cnt = 10;
14426                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14427                 else strcpy(buf, "(");
14428                 for(i=currentMove; i<forwardMostMove; i++) {
14429                         if(WhiteOnMove(i))
14430                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14431                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14432                         strcat(buf, moveBuf);
14433                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14434                 }
14435                 strcat(buf, ")");
14436         }
14437         for(i=1; i<nrMoves; i++) { // copy last variation back
14438             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14439             for(j=0; j<MOVE_LEN; j++)
14440                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14441             for(j=0; j<2*MOVE_LEN; j++)
14442                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14443             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14444             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14445             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14446             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14447             commentList[currentMove+i] = commentList[framePtr+i];
14448             commentList[framePtr+i] = NULL;
14449         }
14450         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14451         framePtr = savedFramePtr[storedGames];
14452         gameInfo.result = savedResult[storedGames];
14453         if(gameInfo.resultDetails != NULL) {
14454             free(gameInfo.resultDetails);
14455       }
14456         gameInfo.resultDetails = savedDetails[storedGames];
14457         forwardMostMove = currentMove + nrMoves;
14458         if(storedGames == 0) GreyRevert(TRUE);
14459         return TRUE;
14460 }
14461
14462 void 
14463 CleanupTail()
14464 {       // remove all shelved variations
14465         int i;
14466         for(i=0; i<storedGames; i++) {
14467             if(savedDetails[i])
14468                 free(savedDetails[i]);
14469             savedDetails[i] = NULL;
14470         }
14471         for(i=framePtr; i<MAX_MOVES; i++) {
14472                 if(commentList[i]) free(commentList[i]);
14473                 commentList[i] = NULL;
14474         }
14475         framePtr = MAX_MOVES-1;
14476         storedGames = 0;
14477 }