Extensive bugfix of -autoKibitz
[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 #if ZIPPY
3159                     if (appData.zippyPlay) {
3160                         ZippyGameStart(whitename, blackname);
3161                     }
3162 #endif /*ZIPPY*/
3163                     continue;
3164                 }
3165
3166                 /* Game end messages */
3167                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3168                     ics_gamenum != gamenum) {
3169                     continue;
3170                 }
3171                 while (endtoken[0] == ' ') endtoken++;
3172                 switch (endtoken[0]) {
3173                   case '*':
3174                   default:
3175                     endtype = GameUnfinished;
3176                     break;
3177                   case '0':
3178                     endtype = BlackWins;
3179                     break;
3180                   case '1':
3181                     if (endtoken[1] == '/')
3182                       endtype = GameIsDrawn;
3183                     else
3184                       endtype = WhiteWins;
3185                     break;
3186                 }
3187                 GameEnds(endtype, why, GE_ICS);
3188 #if ZIPPY
3189                 if (appData.zippyPlay && first.initDone) {
3190                     ZippyGameEnd(endtype, why);
3191                     if (first.pr == NULL) {
3192                       /* Start the next process early so that we'll
3193                          be ready for the next challenge */
3194                       StartChessProgram(&first);
3195                     }
3196                     /* Send "new" early, in case this command takes
3197                        a long time to finish, so that we'll be ready
3198                        for the next challenge. */
3199                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3200                     Reset(TRUE, TRUE);
3201                 }
3202 #endif /*ZIPPY*/
3203                 continue;
3204             }
3205
3206             if (looking_at(buf, &i, "Removing game * from observation") ||
3207                 looking_at(buf, &i, "no longer observing game *") ||
3208                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3209                 if (gameMode == IcsObserving &&
3210                     atoi(star_match[0]) == ics_gamenum)
3211                   {
3212                       /* icsEngineAnalyze */
3213                       if (appData.icsEngineAnalyze) {
3214                             ExitAnalyzeMode();
3215                             ModeHighlight();
3216                       }
3217                       StopClocks();
3218                       gameMode = IcsIdle;
3219                       ics_gamenum = -1;
3220                       ics_user_moved = FALSE;
3221                   }
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i, "no longer examining game *")) {
3226                 if (gameMode == IcsExamining &&
3227                     atoi(star_match[0]) == ics_gamenum)
3228                   {
3229                       gameMode = IcsIdle;
3230                       ics_gamenum = -1;
3231                       ics_user_moved = FALSE;
3232                   }
3233                 continue;
3234             }
3235
3236             /* Advance leftover_start past any newlines we find,
3237                so only partial lines can get reparsed */
3238             if (looking_at(buf, &i, "\n")) {
3239                 prevColor = curColor;
3240                 if (curColor != ColorNormal) {
3241                     if (oldi > next_out) {
3242                         SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = oldi;
3244                     }
3245                     Colorize(ColorNormal, FALSE);
3246                     curColor = ColorNormal;
3247                 }
3248                 if (started == STARTED_BOARD) {
3249                     started = STARTED_NONE;
3250                     parse[parse_pos] = NULLCHAR;
3251                     ParseBoard12(parse);
3252                     ics_user_moved = 0;
3253
3254                     /* Send premove here */
3255                     if (appData.premove) {
3256                       char str[MSG_SIZ];
3257                       if (currentMove == 0 &&
3258                           gameMode == IcsPlayingWhite &&
3259                           appData.premoveWhite) {
3260                         sprintf(str, "%s\n", appData.premoveWhiteText);
3261                         if (appData.debugMode)
3262                           fprintf(debugFP, "Sending premove:\n");
3263                         SendToICS(str);
3264                       } else if (currentMove == 1 &&
3265                                  gameMode == IcsPlayingBlack &&
3266                                  appData.premoveBlack) {
3267                         sprintf(str, "%s\n", appData.premoveBlackText);
3268                         if (appData.debugMode)
3269                           fprintf(debugFP, "Sending premove:\n");
3270                         SendToICS(str);
3271                       } else if (gotPremove) {
3272                         gotPremove = 0;
3273                         ClearPremoveHighlights();
3274                         if (appData.debugMode)
3275                           fprintf(debugFP, "Sending premove:\n");
3276                           UserMoveEvent(premoveFromX, premoveFromY, 
3277                                         premoveToX, premoveToY, 
3278                                         premovePromoChar);
3279                       }
3280                     }
3281
3282                     /* Usually suppress following prompt */
3283                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3284                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3285                         if (looking_at(buf, &i, "*% ")) {
3286                             savingComment = FALSE;
3287                             suppressKibitz = 0;
3288                         }
3289                     }
3290                     next_out = i;
3291                 } else if (started == STARTED_HOLDINGS) {
3292                     int gamenum;
3293                     char new_piece[MSG_SIZ];
3294                     started = STARTED_NONE;
3295                     parse[parse_pos] = NULLCHAR;
3296                     if (appData.debugMode)
3297                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3298                                                         parse, currentMove);
3299                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3300                         gamenum == ics_gamenum) {
3301                         if (gameInfo.variant == VariantNormal) {
3302                           /* [HGM] We seem to switch variant during a game!
3303                            * Presumably no holdings were displayed, so we have
3304                            * to move the position two files to the right to
3305                            * create room for them!
3306                            */
3307                           VariantClass newVariant;
3308                           switch(gameInfo.boardWidth) { // base guess on board width
3309                                 case 9:  newVariant = VariantShogi; break;
3310                                 case 10: newVariant = VariantGreat; break;
3311                                 default: newVariant = VariantCrazyhouse; break;
3312                           }
3313                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3314                           /* Get a move list just to see the header, which
3315                              will tell us whether this is really bug or zh */
3316                           if (ics_getting_history == H_FALSE) {
3317                             ics_getting_history = H_REQUESTED;
3318                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3319                             SendToICS(str);
3320                           }
3321                         }
3322                         new_piece[0] = NULLCHAR;
3323                         sscanf(parse, "game %d white [%s black [%s <- %s",
3324                                &gamenum, white_holding, black_holding,
3325                                new_piece);
3326                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3327                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3328                         /* [HGM] copy holdings to board holdings area */
3329                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3330                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3331                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3332 #if ZIPPY
3333                         if (appData.zippyPlay && first.initDone) {
3334                             ZippyHoldings(white_holding, black_holding,
3335                                           new_piece);
3336                         }
3337 #endif /*ZIPPY*/
3338                         if (tinyLayout || smallLayout) {
3339                             char wh[16], bh[16];
3340                             PackHolding(wh, white_holding);
3341                             PackHolding(bh, black_holding);
3342                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3343                                     gameInfo.white, gameInfo.black);
3344                         } else {
3345                             sprintf(str, "%s [%s] vs. %s [%s]",
3346                                     gameInfo.white, white_holding,
3347                                     gameInfo.black, black_holding);
3348                         }
3349
3350                         DrawPosition(FALSE, boards[currentMove]);
3351                         DisplayTitle(str);
3352                     }
3353                     /* Suppress following prompt */
3354                     if (looking_at(buf, &i, "*% ")) {
3355                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3356                         savingComment = FALSE;
3357                         suppressKibitz = 0;
3358                     }
3359                     next_out = i;
3360                 }
3361                 continue;
3362             }
3363
3364             i++;                /* skip unparsed character and loop back */
3365         }
3366         
3367         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3368 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3369 //          SendToPlayer(&buf[next_out], i - next_out);
3370             started != STARTED_HOLDINGS && leftover_start > next_out) {
3371             SendToPlayer(&buf[next_out], leftover_start - next_out);
3372             next_out = i;
3373         }
3374         
3375         leftover_len = buf_len - leftover_start;
3376         /* if buffer ends with something we couldn't parse,
3377            reparse it after appending the next read */
3378         
3379     } else if (count == 0) {
3380         RemoveInputSource(isr);
3381         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3382     } else {
3383         DisplayFatalError(_("Error reading from ICS"), error, 1);
3384     }
3385 }
3386
3387
3388 /* Board style 12 looks like this:
3389    
3390    <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
3391    
3392  * The "<12> " is stripped before it gets to this routine.  The two
3393  * trailing 0's (flip state and clock ticking) are later addition, and
3394  * some chess servers may not have them, or may have only the first.
3395  * Additional trailing fields may be added in the future.  
3396  */
3397
3398 #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"
3399
3400 #define RELATION_OBSERVING_PLAYED    0
3401 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3402 #define RELATION_PLAYING_MYMOVE      1
3403 #define RELATION_PLAYING_NOTMYMOVE  -1
3404 #define RELATION_EXAMINING           2
3405 #define RELATION_ISOLATED_BOARD     -3
3406 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3407
3408 void
3409 ParseBoard12(string)
3410      char *string;
3411
3412     GameMode newGameMode;
3413     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3414     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3415     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3416     char to_play, board_chars[200];
3417     char move_str[500], str[500], elapsed_time[500];
3418     char black[32], white[32];
3419     Board board;
3420     int prevMove = currentMove;
3421     int ticking = 2;
3422     ChessMove moveType;
3423     int fromX, fromY, toX, toY;
3424     char promoChar;
3425     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3426     char *bookHit = NULL; // [HGM] book
3427     Boolean weird = FALSE, reqFlag = FALSE;
3428
3429     fromX = fromY = toX = toY = -1;
3430     
3431     newGame = FALSE;
3432
3433     if (appData.debugMode)
3434       fprintf(debugFP, _("Parsing board: %s\n"), string);
3435
3436     move_str[0] = NULLCHAR;
3437     elapsed_time[0] = NULLCHAR;
3438     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3439         int  i = 0, j;
3440         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3441             if(string[i] == ' ') { ranks++; files = 0; }
3442             else files++;
3443             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3444             i++;
3445         }
3446         for(j = 0; j <i; j++) board_chars[j] = string[j];
3447         board_chars[i] = '\0';
3448         string += i + 1;
3449     }
3450     n = sscanf(string, PATTERN, &to_play, &double_push,
3451                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3452                &gamenum, white, black, &relation, &basetime, &increment,
3453                &white_stren, &black_stren, &white_time, &black_time,
3454                &moveNum, str, elapsed_time, move_str, &ics_flip,
3455                &ticking);
3456
3457     if (n < 21) {
3458         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3459         DisplayError(str, 0);
3460         return;
3461     }
3462
3463     /* Convert the move number to internal form */
3464     moveNum = (moveNum - 1) * 2;
3465     if (to_play == 'B') moveNum++;
3466     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3467       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3468                         0, 1);
3469       return;
3470     }
3471     
3472     switch (relation) {
3473       case RELATION_OBSERVING_PLAYED:
3474       case RELATION_OBSERVING_STATIC:
3475         if (gamenum == -1) {
3476             /* Old ICC buglet */
3477             relation = RELATION_OBSERVING_STATIC;
3478         }
3479         newGameMode = IcsObserving;
3480         break;
3481       case RELATION_PLAYING_MYMOVE:
3482       case RELATION_PLAYING_NOTMYMOVE:
3483         newGameMode =
3484           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3485             IcsPlayingWhite : IcsPlayingBlack;
3486         break;
3487       case RELATION_EXAMINING:
3488         newGameMode = IcsExamining;
3489         break;
3490       case RELATION_ISOLATED_BOARD:
3491       default:
3492         /* Just display this board.  If user was doing something else,
3493            we will forget about it until the next board comes. */ 
3494         newGameMode = IcsIdle;
3495         break;
3496       case RELATION_STARTING_POSITION:
3497         newGameMode = gameMode;
3498         break;
3499     }
3500     
3501     /* Modify behavior for initial board display on move listing
3502        of wild games.
3503        */
3504     switch (ics_getting_history) {
3505       case H_FALSE:
3506       case H_REQUESTED:
3507         break;
3508       case H_GOT_REQ_HEADER:
3509       case H_GOT_UNREQ_HEADER:
3510         /* This is the initial position of the current game */
3511         gamenum = ics_gamenum;
3512         moveNum = 0;            /* old ICS bug workaround */
3513         if (to_play == 'B') {
3514           startedFromSetupPosition = TRUE;
3515           blackPlaysFirst = TRUE;
3516           moveNum = 1;
3517           if (forwardMostMove == 0) forwardMostMove = 1;
3518           if (backwardMostMove == 0) backwardMostMove = 1;
3519           if (currentMove == 0) currentMove = 1;
3520         }
3521         newGameMode = gameMode;
3522         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3523         break;
3524       case H_GOT_UNWANTED_HEADER:
3525         /* This is an initial board that we don't want */
3526         return;
3527       case H_GETTING_MOVES:
3528         /* Should not happen */
3529         DisplayError(_("Error gathering move list: extra board"), 0);
3530         ics_getting_history = H_FALSE;
3531         return;
3532     }
3533
3534    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3535                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3536      /* [HGM] We seem to have switched variant unexpectedly
3537       * Try to guess new variant from board size
3538       */
3539           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3540           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3541           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3542           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3543           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3544           if(!weird) newVariant = VariantNormal;
3545           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3546           /* Get a move list just to see the header, which
3547              will tell us whether this is really bug or zh */
3548           if (ics_getting_history == H_FALSE) {
3549             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3550             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3551             SendToICS(str);
3552           }
3553     }
3554     
3555     /* Take action if this is the first board of a new game, or of a
3556        different game than is currently being displayed.  */
3557     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3558         relation == RELATION_ISOLATED_BOARD) {
3559         
3560         /* Forget the old game and get the history (if any) of the new one */
3561         if (gameMode != BeginningOfGame) {
3562           Reset(TRUE, TRUE);
3563         }
3564         newGame = TRUE;
3565         if (appData.autoRaiseBoard) BoardToTop();
3566         prevMove = -3;
3567         if (gamenum == -1) {
3568             newGameMode = IcsIdle;
3569         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3570                    appData.getMoveList && !reqFlag) {
3571             /* Need to get game history */
3572             ics_getting_history = H_REQUESTED;
3573             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3574             SendToICS(str);
3575         }
3576         
3577         /* Initially flip the board to have black on the bottom if playing
3578            black or if the ICS flip flag is set, but let the user change
3579            it with the Flip View button. */
3580         flipView = appData.autoFlipView ? 
3581           (newGameMode == IcsPlayingBlack) || ics_flip :
3582           appData.flipView;
3583         
3584         /* Done with values from previous mode; copy in new ones */
3585         gameMode = newGameMode;
3586         ModeHighlight();
3587         ics_gamenum = gamenum;
3588         if (gamenum == gs_gamenum) {
3589             int klen = strlen(gs_kind);
3590             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3591             sprintf(str, "ICS %s", gs_kind);
3592             gameInfo.event = StrSave(str);
3593         } else {
3594             gameInfo.event = StrSave("ICS game");
3595         }
3596         gameInfo.site = StrSave(appData.icsHost);
3597         gameInfo.date = PGNDate();
3598         gameInfo.round = StrSave("-");
3599         gameInfo.white = StrSave(white);
3600         gameInfo.black = StrSave(black);
3601         timeControl = basetime * 60 * 1000;
3602         timeControl_2 = 0;
3603         timeIncrement = increment * 1000;
3604         movesPerSession = 0;
3605         gameInfo.timeControl = TimeControlTagValue();
3606         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3607   if (appData.debugMode) {
3608     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3609     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3610     setbuf(debugFP, NULL);
3611   }
3612
3613         gameInfo.outOfBook = NULL;
3614         
3615         /* Do we have the ratings? */
3616         if (strcmp(player1Name, white) == 0 &&
3617             strcmp(player2Name, black) == 0) {
3618             if (appData.debugMode)
3619               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3620                       player1Rating, player2Rating);
3621             gameInfo.whiteRating = player1Rating;
3622             gameInfo.blackRating = player2Rating;
3623         } else if (strcmp(player2Name, white) == 0 &&
3624                    strcmp(player1Name, black) == 0) {
3625             if (appData.debugMode)
3626               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3627                       player2Rating, player1Rating);
3628             gameInfo.whiteRating = player2Rating;
3629             gameInfo.blackRating = player1Rating;
3630         }
3631         player1Name[0] = player2Name[0] = NULLCHAR;
3632
3633         /* Silence shouts if requested */
3634         if (appData.quietPlay &&
3635             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3636             SendToICS(ics_prefix);
3637             SendToICS("set shout 0\n");
3638         }
3639     }
3640     
3641     /* Deal with midgame name changes */
3642     if (!newGame) {
3643         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3644             if (gameInfo.white) free(gameInfo.white);
3645             gameInfo.white = StrSave(white);
3646         }
3647         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3648             if (gameInfo.black) free(gameInfo.black);
3649             gameInfo.black = StrSave(black);
3650         }
3651     }
3652     
3653     /* Throw away game result if anything actually changes in examine mode */
3654     if (gameMode == IcsExamining && !newGame) {
3655         gameInfo.result = GameUnfinished;
3656         if (gameInfo.resultDetails != NULL) {
3657             free(gameInfo.resultDetails);
3658             gameInfo.resultDetails = NULL;
3659         }
3660     }
3661     
3662     /* In pausing && IcsExamining mode, we ignore boards coming
3663        in if they are in a different variation than we are. */
3664     if (pauseExamInvalid) return;
3665     if (pausing && gameMode == IcsExamining) {
3666         if (moveNum <= pauseExamForwardMostMove) {
3667             pauseExamInvalid = TRUE;
3668             forwardMostMove = pauseExamForwardMostMove;
3669             return;
3670         }
3671     }
3672     
3673   if (appData.debugMode) {
3674     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3675   }
3676     /* Parse the board */
3677     for (k = 0; k < ranks; k++) {
3678       for (j = 0; j < files; j++)
3679         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3680       if(gameInfo.holdingsWidth > 1) {
3681            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3682            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3683       }
3684     }
3685     CopyBoard(boards[moveNum], board);
3686     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3687     if (moveNum == 0) {
3688         startedFromSetupPosition =
3689           !CompareBoards(board, initialPosition);
3690         if(startedFromSetupPosition)
3691             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3692     }
3693
3694     /* [HGM] Set castling rights. Take the outermost Rooks,
3695        to make it also work for FRC opening positions. Note that board12
3696        is really defective for later FRC positions, as it has no way to
3697        indicate which Rook can castle if they are on the same side of King.
3698        For the initial position we grant rights to the outermost Rooks,
3699        and remember thos rights, and we then copy them on positions
3700        later in an FRC game. This means WB might not recognize castlings with
3701        Rooks that have moved back to their original position as illegal,
3702        but in ICS mode that is not its job anyway.
3703     */
3704     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3705     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3706
3707         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3708             if(board[0][i] == WhiteRook) j = i;
3709         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3710         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3711             if(board[0][i] == WhiteRook) j = i;
3712         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3713         for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3714             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3715         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3716         for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3717             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3718         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3719
3720         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3721         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3722             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3723         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3724             if(board[BOARD_HEIGHT-1][k] == bKing)
3725                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3726     } else { int r;
3727         r = boards[moveNum][CASTLING][0] = initialRights[0];
3728         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3729         r = boards[moveNum][CASTLING][1] = initialRights[1];
3730         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3731         r = boards[moveNum][CASTLING][3] = initialRights[3];
3732         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3733         r = boards[moveNum][CASTLING][4] = initialRights[4];
3734         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3735         /* wildcastle kludge: always assume King has rights */
3736         r = boards[moveNum][CASTLING][2] = initialRights[2];
3737         r = boards[moveNum][CASTLING][5] = initialRights[5];
3738     }
3739     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3740     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3741
3742     
3743     if (ics_getting_history == H_GOT_REQ_HEADER ||
3744         ics_getting_history == H_GOT_UNREQ_HEADER) {
3745         /* This was an initial position from a move list, not
3746            the current position */
3747         return;
3748     }
3749     
3750     /* Update currentMove and known move number limits */
3751     newMove = newGame || moveNum > forwardMostMove;
3752
3753     if (newGame) {
3754         forwardMostMove = backwardMostMove = currentMove = moveNum;
3755         if (gameMode == IcsExamining && moveNum == 0) {
3756           /* Workaround for ICS limitation: we are not told the wild
3757              type when starting to examine a game.  But if we ask for
3758              the move list, the move list header will tell us */
3759             ics_getting_history = H_REQUESTED;
3760             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3761             SendToICS(str);
3762         }
3763     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3764                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3765 #if ZIPPY
3766         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3767         /* [HGM] applied this also to an engine that is silently watching        */
3768         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3769             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3770             gameInfo.variant == currentlyInitializedVariant) {
3771           takeback = forwardMostMove - moveNum;
3772           for (i = 0; i < takeback; i++) {
3773             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3774             SendToProgram("undo\n", &first);
3775           }
3776         }
3777 #endif
3778
3779         forwardMostMove = moveNum;
3780         if (!pausing || currentMove > forwardMostMove)
3781           currentMove = forwardMostMove;
3782     } else {
3783         /* New part of history that is not contiguous with old part */ 
3784         if (pausing && gameMode == IcsExamining) {
3785             pauseExamInvalid = TRUE;
3786             forwardMostMove = pauseExamForwardMostMove;
3787             return;
3788         }
3789         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3790 #if ZIPPY
3791             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3792                 // [HGM] when we will receive the move list we now request, it will be
3793                 // fed to the engine from the first move on. So if the engine is not
3794                 // in the initial position now, bring it there.
3795                 InitChessProgram(&first, 0);
3796             }
3797 #endif
3798             ics_getting_history = H_REQUESTED;
3799             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3800             SendToICS(str);
3801         }
3802         forwardMostMove = backwardMostMove = currentMove = moveNum;
3803     }
3804     
3805     /* Update the clocks */
3806     if (strchr(elapsed_time, '.')) {
3807       /* Time is in ms */
3808       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3809       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3810     } else {
3811       /* Time is in seconds */
3812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3814     }
3815       
3816
3817 #if ZIPPY
3818     if (appData.zippyPlay && newGame &&
3819         gameMode != IcsObserving && gameMode != IcsIdle &&
3820         gameMode != IcsExamining)
3821       ZippyFirstBoard(moveNum, basetime, increment);
3822 #endif
3823     
3824     /* Put the move on the move list, first converting
3825        to canonical algebraic form. */
3826     if (moveNum > 0) {
3827   if (appData.debugMode) {
3828     if (appData.debugMode) { int f = forwardMostMove;
3829         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3830                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3831                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3832     }
3833     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3834     fprintf(debugFP, "moveNum = %d\n", moveNum);
3835     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3836     setbuf(debugFP, NULL);
3837   }
3838         if (moveNum <= backwardMostMove) {
3839             /* We don't know what the board looked like before
3840                this move.  Punt. */
3841             strcpy(parseList[moveNum - 1], move_str);
3842             strcat(parseList[moveNum - 1], " ");
3843             strcat(parseList[moveNum - 1], elapsed_time);
3844             moveList[moveNum - 1][0] = NULLCHAR;
3845         } else if (strcmp(move_str, "none") == 0) {
3846             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3847             /* Again, we don't know what the board looked like;
3848                this is really the start of the game. */
3849             parseList[moveNum - 1][0] = NULLCHAR;
3850             moveList[moveNum - 1][0] = NULLCHAR;
3851             backwardMostMove = moveNum;
3852             startedFromSetupPosition = TRUE;
3853             fromX = fromY = toX = toY = -1;
3854         } else {
3855           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3856           //                 So we parse the long-algebraic move string in stead of the SAN move
3857           int valid; char buf[MSG_SIZ], *prom;
3858
3859           // str looks something like "Q/a1-a2"; kill the slash
3860           if(str[1] == '/') 
3861                 sprintf(buf, "%c%s", str[0], str+2);
3862           else  strcpy(buf, str); // might be castling
3863           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3864                 strcat(buf, prom); // long move lacks promo specification!
3865           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3866                 if(appData.debugMode) 
3867                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3868                 strcpy(move_str, buf);
3869           }
3870           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3871                                 &fromX, &fromY, &toX, &toY, &promoChar)
3872                || ParseOneMove(buf, moveNum - 1, &moveType,
3873                                 &fromX, &fromY, &toX, &toY, &promoChar);
3874           // end of long SAN patch
3875           if (valid) {
3876             (void) CoordsToAlgebraic(boards[moveNum - 1],
3877                                      PosFlags(moveNum - 1),
3878                                      fromY, fromX, toY, toX, promoChar,
3879                                      parseList[moveNum-1]);
3880             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3881               case MT_NONE:
3882               case MT_STALEMATE:
3883               default:
3884                 break;
3885               case MT_CHECK:
3886                 if(gameInfo.variant != VariantShogi)
3887                     strcat(parseList[moveNum - 1], "+");
3888                 break;
3889               case MT_CHECKMATE:
3890               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3891                 strcat(parseList[moveNum - 1], "#");
3892                 break;
3893             }
3894             strcat(parseList[moveNum - 1], " ");
3895             strcat(parseList[moveNum - 1], elapsed_time);
3896             /* currentMoveString is set as a side-effect of ParseOneMove */
3897             strcpy(moveList[moveNum - 1], currentMoveString);
3898             strcat(moveList[moveNum - 1], "\n");
3899           } else {
3900             /* Move from ICS was illegal!?  Punt. */
3901   if (appData.debugMode) {
3902     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3903     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3904   }
3905             strcpy(parseList[moveNum - 1], move_str);
3906             strcat(parseList[moveNum - 1], " ");
3907             strcat(parseList[moveNum - 1], elapsed_time);
3908             moveList[moveNum - 1][0] = NULLCHAR;
3909             fromX = fromY = toX = toY = -1;
3910           }
3911         }
3912   if (appData.debugMode) {
3913     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3914     setbuf(debugFP, NULL);
3915   }
3916
3917 #if ZIPPY
3918         /* Send move to chess program (BEFORE animating it). */
3919         if (appData.zippyPlay && !newGame && newMove && 
3920            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3921
3922             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3923                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3924                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3925                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3926                             move_str);
3927                     DisplayError(str, 0);
3928                 } else {
3929                     if (first.sendTime) {
3930                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3931                     }
3932                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3933                     if (firstMove && !bookHit) {
3934                         firstMove = FALSE;
3935                         if (first.useColors) {
3936                           SendToProgram(gameMode == IcsPlayingWhite ?
3937                                         "white\ngo\n" :
3938                                         "black\ngo\n", &first);
3939                         } else {
3940                           SendToProgram("go\n", &first);
3941                         }
3942                         first.maybeThinking = TRUE;
3943                     }
3944                 }
3945             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3946               if (moveList[moveNum - 1][0] == NULLCHAR) {
3947                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3948                 DisplayError(str, 0);
3949               } else {
3950                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3951                 SendMoveToProgram(moveNum - 1, &first);
3952               }
3953             }
3954         }
3955 #endif
3956     }
3957
3958     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3959         /* If move comes from a remote source, animate it.  If it
3960            isn't remote, it will have already been animated. */
3961         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3962             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3963         }
3964         if (!pausing && appData.highlightLastMove) {
3965             SetHighlights(fromX, fromY, toX, toY);
3966         }
3967     }
3968     
3969     /* Start the clocks */
3970     whiteFlag = blackFlag = FALSE;
3971     appData.clockMode = !(basetime == 0 && increment == 0);
3972     if (ticking == 0) {
3973       ics_clock_paused = TRUE;
3974       StopClocks();
3975     } else if (ticking == 1) {
3976       ics_clock_paused = FALSE;
3977     }
3978     if (gameMode == IcsIdle ||
3979         relation == RELATION_OBSERVING_STATIC ||
3980         relation == RELATION_EXAMINING ||
3981         ics_clock_paused)
3982       DisplayBothClocks();
3983     else
3984       StartClocks();
3985     
3986     /* Display opponents and material strengths */
3987     if (gameInfo.variant != VariantBughouse &&
3988         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3989         if (tinyLayout || smallLayout) {
3990             if(gameInfo.variant == VariantNormal)
3991                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
3992                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3993                     basetime, increment);
3994             else
3995                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
3996                     gameInfo.white, white_stren, gameInfo.black, black_stren,
3997                     basetime, increment, (int) gameInfo.variant);
3998         } else {
3999             if(gameInfo.variant == VariantNormal)
4000                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4001                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4002                     basetime, increment);
4003             else
4004                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4005                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4006                     basetime, increment, VariantName(gameInfo.variant));
4007         }
4008         DisplayTitle(str);
4009   if (appData.debugMode) {
4010     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4011   }
4012     }
4013
4014    
4015     /* Display the board */
4016     if (!pausing && !appData.noGUI) {
4017       
4018       if (appData.premove)
4019           if (!gotPremove || 
4020              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4021              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4022               ClearPremoveHighlights();
4023
4024       DrawPosition(FALSE, boards[currentMove]);
4025       DisplayMove(moveNum - 1);
4026       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4027             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4028               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4029         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4030       }
4031     }
4032
4033     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4034 #if ZIPPY
4035     if(bookHit) { // [HGM] book: simulate book reply
4036         static char bookMove[MSG_SIZ]; // a bit generous?
4037
4038         programStats.nodes = programStats.depth = programStats.time = 
4039         programStats.score = programStats.got_only_move = 0;
4040         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4041
4042         strcpy(bookMove, "move ");
4043         strcat(bookMove, bookHit);
4044         HandleMachineMove(bookMove, &first);
4045     }
4046 #endif
4047 }
4048
4049 void
4050 GetMoveListEvent()
4051 {
4052     char buf[MSG_SIZ];
4053     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4054         ics_getting_history = H_REQUESTED;
4055         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4056         SendToICS(buf);
4057     }
4058 }
4059
4060 void
4061 AnalysisPeriodicEvent(force)
4062      int force;
4063 {
4064     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4065          && !force) || !appData.periodicUpdates)
4066       return;
4067
4068     /* Send . command to Crafty to collect stats */
4069     SendToProgram(".\n", &first);
4070
4071     /* Don't send another until we get a response (this makes
4072        us stop sending to old Crafty's which don't understand
4073        the "." command (sending illegal cmds resets node count & time,
4074        which looks bad)) */
4075     programStats.ok_to_send = 0;
4076 }
4077
4078 void ics_update_width(new_width)
4079         int new_width;
4080 {
4081         ics_printf("set width %d\n", new_width);
4082 }
4083
4084 void
4085 SendMoveToProgram(moveNum, cps)
4086      int moveNum;
4087      ChessProgramState *cps;
4088 {
4089     char buf[MSG_SIZ];
4090
4091     if (cps->useUsermove) {
4092       SendToProgram("usermove ", cps);
4093     }
4094     if (cps->useSAN) {
4095       char *space;
4096       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4097         int len = space - parseList[moveNum];
4098         memcpy(buf, parseList[moveNum], len);
4099         buf[len++] = '\n';
4100         buf[len] = NULLCHAR;
4101       } else {
4102         sprintf(buf, "%s\n", parseList[moveNum]);
4103       }
4104       SendToProgram(buf, cps);
4105     } else {
4106       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4107         AlphaRank(moveList[moveNum], 4);
4108         SendToProgram(moveList[moveNum], cps);
4109         AlphaRank(moveList[moveNum], 4); // and back
4110       } else
4111       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4112        * the engine. It would be nice to have a better way to identify castle 
4113        * moves here. */
4114       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4115                                                                          && cps->useOOCastle) {
4116         int fromX = moveList[moveNum][0] - AAA; 
4117         int fromY = moveList[moveNum][1] - ONE;
4118         int toX = moveList[moveNum][2] - AAA; 
4119         int toY = moveList[moveNum][3] - ONE;
4120         if((boards[moveNum][fromY][fromX] == WhiteKing 
4121             && boards[moveNum][toY][toX] == WhiteRook)
4122            || (boards[moveNum][fromY][fromX] == BlackKing 
4123                && boards[moveNum][toY][toX] == BlackRook)) {
4124           if(toX > fromX) SendToProgram("O-O\n", cps);
4125           else SendToProgram("O-O-O\n", cps);
4126         }
4127         else SendToProgram(moveList[moveNum], cps);
4128       }
4129       else SendToProgram(moveList[moveNum], cps);
4130       /* End of additions by Tord */
4131     }
4132
4133     /* [HGM] setting up the opening has brought engine in force mode! */
4134     /*       Send 'go' if we are in a mode where machine should play. */
4135     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4136         (gameMode == TwoMachinesPlay   ||
4137 #ifdef ZIPPY
4138          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4139 #endif
4140          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4141         SendToProgram("go\n", cps);
4142   if (appData.debugMode) {
4143     fprintf(debugFP, "(extra)\n");
4144   }
4145     }
4146     setboardSpoiledMachineBlack = 0;
4147 }
4148
4149 void
4150 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4151      ChessMove moveType;
4152      int fromX, fromY, toX, toY;
4153 {
4154     char user_move[MSG_SIZ];
4155
4156     switch (moveType) {
4157       default:
4158         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4159                 (int)moveType, fromX, fromY, toX, toY);
4160         DisplayError(user_move + strlen("say "), 0);
4161         break;
4162       case WhiteKingSideCastle:
4163       case BlackKingSideCastle:
4164       case WhiteQueenSideCastleWild:
4165       case BlackQueenSideCastleWild:
4166       /* PUSH Fabien */
4167       case WhiteHSideCastleFR:
4168       case BlackHSideCastleFR:
4169       /* POP Fabien */
4170         sprintf(user_move, "o-o\n");
4171         break;
4172       case WhiteQueenSideCastle:
4173       case BlackQueenSideCastle:
4174       case WhiteKingSideCastleWild:
4175       case BlackKingSideCastleWild:
4176       /* PUSH Fabien */
4177       case WhiteASideCastleFR:
4178       case BlackASideCastleFR:
4179       /* POP Fabien */
4180         sprintf(user_move, "o-o-o\n");
4181         break;
4182       case WhitePromotionQueen:
4183       case BlackPromotionQueen:
4184       case WhitePromotionRook:
4185       case BlackPromotionRook:
4186       case WhitePromotionBishop:
4187       case BlackPromotionBishop:
4188       case WhitePromotionKnight:
4189       case BlackPromotionKnight:
4190       case WhitePromotionKing:
4191       case BlackPromotionKing:
4192       case WhitePromotionChancellor:
4193       case BlackPromotionChancellor:
4194       case WhitePromotionArchbishop:
4195       case BlackPromotionArchbishop:
4196         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4197             sprintf(user_move, "%c%c%c%c=%c\n",
4198                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4199                 PieceToChar(WhiteFerz));
4200         else if(gameInfo.variant == VariantGreat)
4201             sprintf(user_move, "%c%c%c%c=%c\n",
4202                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4203                 PieceToChar(WhiteMan));
4204         else
4205             sprintf(user_move, "%c%c%c%c=%c\n",
4206                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4207                 PieceToChar(PromoPiece(moveType)));
4208         break;
4209       case WhiteDrop:
4210       case BlackDrop:
4211         sprintf(user_move, "%c@%c%c\n",
4212                 ToUpper(PieceToChar((ChessSquare) fromX)),
4213                 AAA + toX, ONE + toY);
4214         break;
4215       case NormalMove:
4216       case WhiteCapturesEnPassant:
4217       case BlackCapturesEnPassant:
4218       case IllegalMove:  /* could be a variant we don't quite understand */
4219         sprintf(user_move, "%c%c%c%c\n",
4220                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4221         break;
4222     }
4223     SendToICS(user_move);
4224     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4225         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4226 }
4227
4228 void
4229 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4230      int rf, ff, rt, ft;
4231      char promoChar;
4232      char move[7];
4233 {
4234     if (rf == DROP_RANK) {
4235         sprintf(move, "%c@%c%c\n",
4236                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4237     } else {
4238         if (promoChar == 'x' || promoChar == NULLCHAR) {
4239             sprintf(move, "%c%c%c%c\n",
4240                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4241         } else {
4242             sprintf(move, "%c%c%c%c%c\n",
4243                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4244         }
4245     }
4246 }
4247
4248 void
4249 ProcessICSInitScript(f)
4250      FILE *f;
4251 {
4252     char buf[MSG_SIZ];
4253
4254     while (fgets(buf, MSG_SIZ, f)) {
4255         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4256     }
4257
4258     fclose(f);
4259 }
4260
4261
4262 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4263 void
4264 AlphaRank(char *move, int n)
4265 {
4266 //    char *p = move, c; int x, y;
4267
4268     if (appData.debugMode) {
4269         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4270     }
4271
4272     if(move[1]=='*' && 
4273        move[2]>='0' && move[2]<='9' &&
4274        move[3]>='a' && move[3]<='x'    ) {
4275         move[1] = '@';
4276         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4277         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4278     } else
4279     if(move[0]>='0' && move[0]<='9' &&
4280        move[1]>='a' && move[1]<='x' &&
4281        move[2]>='0' && move[2]<='9' &&
4282        move[3]>='a' && move[3]<='x'    ) {
4283         /* input move, Shogi -> normal */
4284         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4285         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4286         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4287         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4288     } else
4289     if(move[1]=='@' &&
4290        move[3]>='0' && move[3]<='9' &&
4291        move[2]>='a' && move[2]<='x'    ) {
4292         move[1] = '*';
4293         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4295     } else
4296     if(
4297        move[0]>='a' && move[0]<='x' &&
4298        move[3]>='0' && move[3]<='9' &&
4299        move[2]>='a' && move[2]<='x'    ) {
4300          /* output move, normal -> Shogi */
4301         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4302         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4303         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4304         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4305         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4306     }
4307     if (appData.debugMode) {
4308         fprintf(debugFP, "   out = '%s'\n", move);
4309     }
4310 }
4311
4312 /* Parser for moves from gnuchess, ICS, or user typein box */
4313 Boolean
4314 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4315      char *move;
4316      int moveNum;
4317      ChessMove *moveType;
4318      int *fromX, *fromY, *toX, *toY;
4319      char *promoChar;
4320 {       
4321     if (appData.debugMode) {
4322         fprintf(debugFP, "move to parse: %s\n", move);
4323     }
4324     *moveType = yylexstr(moveNum, move);
4325
4326     switch (*moveType) {
4327       case WhitePromotionChancellor:
4328       case BlackPromotionChancellor:
4329       case WhitePromotionArchbishop:
4330       case BlackPromotionArchbishop:
4331       case WhitePromotionQueen:
4332       case BlackPromotionQueen:
4333       case WhitePromotionRook:
4334       case BlackPromotionRook:
4335       case WhitePromotionBishop:
4336       case BlackPromotionBishop:
4337       case WhitePromotionKnight:
4338       case BlackPromotionKnight:
4339       case WhitePromotionKing:
4340       case BlackPromotionKing:
4341       case NormalMove:
4342       case WhiteCapturesEnPassant:
4343       case BlackCapturesEnPassant:
4344       case WhiteKingSideCastle:
4345       case WhiteQueenSideCastle:
4346       case BlackKingSideCastle:
4347       case BlackQueenSideCastle:
4348       case WhiteKingSideCastleWild:
4349       case WhiteQueenSideCastleWild:
4350       case BlackKingSideCastleWild:
4351       case BlackQueenSideCastleWild:
4352       /* Code added by Tord: */
4353       case WhiteHSideCastleFR:
4354       case WhiteASideCastleFR:
4355       case BlackHSideCastleFR:
4356       case BlackASideCastleFR:
4357       /* End of code added by Tord */
4358       case IllegalMove:         /* bug or odd chess variant */
4359         *fromX = currentMoveString[0] - AAA;
4360         *fromY = currentMoveString[1] - ONE;
4361         *toX = currentMoveString[2] - AAA;
4362         *toY = currentMoveString[3] - ONE;
4363         *promoChar = currentMoveString[4];
4364         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4365             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4366     if (appData.debugMode) {
4367         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4368     }
4369             *fromX = *fromY = *toX = *toY = 0;
4370             return FALSE;
4371         }
4372         if (appData.testLegality) {
4373           return (*moveType != IllegalMove);
4374         } else {
4375           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4376                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4377         }
4378
4379       case WhiteDrop:
4380       case BlackDrop:
4381         *fromX = *moveType == WhiteDrop ?
4382           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4383           (int) CharToPiece(ToLower(currentMoveString[0]));
4384         *fromY = DROP_RANK;
4385         *toX = currentMoveString[2] - AAA;
4386         *toY = currentMoveString[3] - ONE;
4387         *promoChar = NULLCHAR;
4388         return TRUE;
4389
4390       case AmbiguousMove:
4391       case ImpossibleMove:
4392       case (ChessMove) 0:       /* end of file */
4393       case ElapsedTime:
4394       case Comment:
4395       case PGNTag:
4396       case NAG:
4397       case WhiteWins:
4398       case BlackWins:
4399       case GameIsDrawn:
4400       default:
4401     if (appData.debugMode) {
4402         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4403     }
4404         /* bug? */
4405         *fromX = *fromY = *toX = *toY = 0;
4406         *promoChar = NULLCHAR;
4407         return FALSE;
4408     }
4409 }
4410
4411
4412 void
4413 ParsePV(char *pv)
4414 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4415   int fromX, fromY, toX, toY; char promoChar;
4416   ChessMove moveType;
4417   Boolean valid;
4418   int nr = 0;
4419
4420   endPV = forwardMostMove;
4421   do {
4422     while(*pv == ' ') pv++;
4423     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4424     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4425 if(appData.debugMode){
4426 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4427 }
4428     if(!valid && nr == 0 &&
4429        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4430         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4431     }
4432     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4433     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4434     nr++;
4435     if(endPV+1 > framePtr) break; // no space, truncate
4436     if(!valid) break;
4437     endPV++;
4438     CopyBoard(boards[endPV], boards[endPV-1]);
4439     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4440     moveList[endPV-1][0] = fromX + AAA;
4441     moveList[endPV-1][1] = fromY + ONE;
4442     moveList[endPV-1][2] = toX + AAA;
4443     moveList[endPV-1][3] = toY + ONE;
4444     parseList[endPV-1][0] = NULLCHAR;
4445   } while(valid);
4446   currentMove = endPV;
4447   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4448   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4449                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4450   DrawPosition(TRUE, boards[currentMove]);
4451 }
4452
4453 static int lastX, lastY;
4454
4455 Boolean
4456 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4457 {
4458         int startPV;
4459
4460         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4461         lastX = x; lastY = y;
4462         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4463         startPV = index;
4464       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4465       index = startPV;
4466         while(buf[index] && buf[index] != '\n') index++;
4467         buf[index] = 0;
4468         ParsePV(buf+startPV);
4469         *start = startPV; *end = index-1;
4470         return TRUE;
4471 }
4472
4473 Boolean
4474 LoadPV(int x, int y)
4475 { // called on right mouse click to load PV
4476   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4477   lastX = x; lastY = y;
4478   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4479   return TRUE;
4480 }
4481
4482 void
4483 UnLoadPV()
4484 {
4485   if(endPV < 0) return;
4486   endPV = -1;
4487   currentMove = forwardMostMove;
4488   ClearPremoveHighlights();
4489   DrawPosition(TRUE, boards[currentMove]);
4490 }
4491
4492 void
4493 MovePV(int x, int y, int h)
4494 { // step through PV based on mouse coordinates (called on mouse move)
4495   int margin = h>>3, step = 0;
4496
4497   if(endPV < 0) return;
4498   // we must somehow check if right button is still down (might be released off board!)
4499   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4500   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4501   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4502   if(!step) return;
4503   lastX = x; lastY = y;
4504   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4505   currentMove += step;
4506   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4507   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4508                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4509   DrawPosition(FALSE, boards[currentMove]);
4510 }
4511
4512
4513 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4514 // All positions will have equal probability, but the current method will not provide a unique
4515 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4516 #define DARK 1
4517 #define LITE 2
4518 #define ANY 3
4519
4520 int squaresLeft[4];
4521 int piecesLeft[(int)BlackPawn];
4522 int seed, nrOfShuffles;
4523
4524 void GetPositionNumber()
4525 {       // sets global variable seed
4526         int i;
4527
4528         seed = appData.defaultFrcPosition;
4529         if(seed < 0) { // randomize based on time for negative FRC position numbers
4530                 for(i=0; i<50; i++) seed += random();
4531                 seed = random() ^ random() >> 8 ^ random() << 8;
4532                 if(seed<0) seed = -seed;
4533         }
4534 }
4535
4536 int put(Board board, int pieceType, int rank, int n, int shade)
4537 // put the piece on the (n-1)-th empty squares of the given shade
4538 {
4539         int i;
4540
4541         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4542                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4543                         board[rank][i] = (ChessSquare) pieceType;
4544                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4545                         squaresLeft[ANY]--;
4546                         piecesLeft[pieceType]--; 
4547                         return i;
4548                 }
4549         }
4550         return -1;
4551 }
4552
4553
4554 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4555 // calculate where the next piece goes, (any empty square), and put it there
4556 {
4557         int i;
4558
4559         i = seed % squaresLeft[shade];
4560         nrOfShuffles *= squaresLeft[shade];
4561         seed /= squaresLeft[shade];
4562         put(board, pieceType, rank, i, shade);
4563 }
4564
4565 void AddTwoPieces(Board board, int pieceType, int rank)
4566 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4567 {
4568         int i, n=squaresLeft[ANY], j=n-1, k;
4569
4570         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4571         i = seed % k;  // pick one
4572         nrOfShuffles *= k;
4573         seed /= k;
4574         while(i >= j) i -= j--;
4575         j = n - 1 - j; i += j;
4576         put(board, pieceType, rank, j, ANY);
4577         put(board, pieceType, rank, i, ANY);
4578 }
4579
4580 void SetUpShuffle(Board board, int number)
4581 {
4582         int i, p, first=1;
4583
4584         GetPositionNumber(); nrOfShuffles = 1;
4585
4586         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4587         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4588         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4589
4590         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4591
4592         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4593             p = (int) board[0][i];
4594             if(p < (int) BlackPawn) piecesLeft[p] ++;
4595             board[0][i] = EmptySquare;
4596         }
4597
4598         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4599             // shuffles restricted to allow normal castling put KRR first
4600             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4601                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4602             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4603                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4604             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4605                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4606             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4607                 put(board, WhiteRook, 0, 0, ANY);
4608             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4609         }
4610
4611         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4612             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4613             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4614                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4615                 while(piecesLeft[p] >= 2) {
4616                     AddOnePiece(board, p, 0, LITE);
4617                     AddOnePiece(board, p, 0, DARK);
4618                 }
4619                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4620             }
4621
4622         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4623             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4624             // but we leave King and Rooks for last, to possibly obey FRC restriction
4625             if(p == (int)WhiteRook) continue;
4626             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4627             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4628         }
4629
4630         // now everything is placed, except perhaps King (Unicorn) and Rooks
4631
4632         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4633             // Last King gets castling rights
4634             while(piecesLeft[(int)WhiteUnicorn]) {
4635                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4636                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4637             }
4638
4639             while(piecesLeft[(int)WhiteKing]) {
4640                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4641                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4642             }
4643
4644
4645         } else {
4646             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4647             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4648         }
4649
4650         // Only Rooks can be left; simply place them all
4651         while(piecesLeft[(int)WhiteRook]) {
4652                 i = put(board, WhiteRook, 0, 0, ANY);
4653                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4654                         if(first) {
4655                                 first=0;
4656                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4657                         }
4658                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4659                 }
4660         }
4661         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4662             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4663         }
4664
4665         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4666 }
4667
4668 int SetCharTable( char *table, const char * map )
4669 /* [HGM] moved here from winboard.c because of its general usefulness */
4670 /*       Basically a safe strcpy that uses the last character as King */
4671 {
4672     int result = FALSE; int NrPieces;
4673
4674     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4675                     && NrPieces >= 12 && !(NrPieces&1)) {
4676         int i; /* [HGM] Accept even length from 12 to 34 */
4677
4678         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4679         for( i=0; i<NrPieces/2-1; i++ ) {
4680             table[i] = map[i];
4681             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4682         }
4683         table[(int) WhiteKing]  = map[NrPieces/2-1];
4684         table[(int) BlackKing]  = map[NrPieces-1];
4685
4686         result = TRUE;
4687     }
4688
4689     return result;
4690 }
4691
4692 void Prelude(Board board)
4693 {       // [HGM] superchess: random selection of exo-pieces
4694         int i, j, k; ChessSquare p; 
4695         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4696
4697         GetPositionNumber(); // use FRC position number
4698
4699         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4700             SetCharTable(pieceToChar, appData.pieceToCharTable);
4701             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4702                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4703         }
4704
4705         j = seed%4;                 seed /= 4; 
4706         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4709         j = seed%3 + (seed%3 >= j); seed /= 3; 
4710         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4711         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4712         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4713         j = seed%3;                 seed /= 3; 
4714         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4715         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4716         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4717         j = seed%2 + (seed%2 >= j); seed /= 2; 
4718         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4719         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4720         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4721         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4722         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4723         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4724         put(board, exoPieces[0],    0, 0, ANY);
4725         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4726 }
4727
4728 void
4729 InitPosition(redraw)
4730      int redraw;
4731 {
4732     ChessSquare (* pieces)[BOARD_FILES];
4733     int i, j, pawnRow, overrule,
4734     oldx = gameInfo.boardWidth,
4735     oldy = gameInfo.boardHeight,
4736     oldh = gameInfo.holdingsWidth,
4737     oldv = gameInfo.variant;
4738
4739     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4740
4741     /* [AS] Initialize pv info list [HGM] and game status */
4742     {
4743         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4744             pvInfoList[i].depth = 0;
4745             boards[i][EP_STATUS] = EP_NONE;
4746             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4747         }
4748
4749         initialRulePlies = 0; /* 50-move counter start */
4750
4751         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4752         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4753     }
4754
4755     
4756     /* [HGM] logic here is completely changed. In stead of full positions */
4757     /* the initialized data only consist of the two backranks. The switch */
4758     /* selects which one we will use, which is than copied to the Board   */
4759     /* initialPosition, which for the rest is initialized by Pawns and    */
4760     /* empty squares. This initial position is then copied to boards[0],  */
4761     /* possibly after shuffling, so that it remains available.            */
4762
4763     gameInfo.holdingsWidth = 0; /* default board sizes */
4764     gameInfo.boardWidth    = 8;
4765     gameInfo.boardHeight   = 8;
4766     gameInfo.holdingsSize  = 0;
4767     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4768     for(i=0; i<BOARD_FILES-2; i++)
4769       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4770     initialPosition[EP_STATUS] = EP_NONE;
4771     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4772
4773     switch (gameInfo.variant) {
4774     case VariantFischeRandom:
4775       shuffleOpenings = TRUE;
4776     default:
4777       pieces = FIDEArray;
4778       break;
4779     case VariantShatranj:
4780       pieces = ShatranjArray;
4781       nrCastlingRights = 0;
4782       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4783       break;
4784     case VariantTwoKings:
4785       pieces = twoKingsArray;
4786       break;
4787     case VariantCapaRandom:
4788       shuffleOpenings = TRUE;
4789     case VariantCapablanca:
4790       pieces = CapablancaArray;
4791       gameInfo.boardWidth = 10;
4792       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4793       break;
4794     case VariantGothic:
4795       pieces = GothicArray;
4796       gameInfo.boardWidth = 10;
4797       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4798       break;
4799     case VariantJanus:
4800       pieces = JanusArray;
4801       gameInfo.boardWidth = 10;
4802       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4803       nrCastlingRights = 6;
4804         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4805         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4806         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4807         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4808         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4809         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4810       break;
4811     case VariantFalcon:
4812       pieces = FalconArray;
4813       gameInfo.boardWidth = 10;
4814       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4815       break;
4816     case VariantXiangqi:
4817       pieces = XiangqiArray;
4818       gameInfo.boardWidth  = 9;
4819       gameInfo.boardHeight = 10;
4820       nrCastlingRights = 0;
4821       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4822       break;
4823     case VariantShogi:
4824       pieces = ShogiArray;
4825       gameInfo.boardWidth  = 9;
4826       gameInfo.boardHeight = 9;
4827       gameInfo.holdingsSize = 7;
4828       nrCastlingRights = 0;
4829       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4830       break;
4831     case VariantCourier:
4832       pieces = CourierArray;
4833       gameInfo.boardWidth  = 12;
4834       nrCastlingRights = 0;
4835       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4836       break;
4837     case VariantKnightmate:
4838       pieces = KnightmateArray;
4839       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4840       break;
4841     case VariantFairy:
4842       pieces = fairyArray;
4843       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4844       break;
4845     case VariantGreat:
4846       pieces = GreatArray;
4847       gameInfo.boardWidth = 10;
4848       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4849       gameInfo.holdingsSize = 8;
4850       break;
4851     case VariantSuper:
4852       pieces = FIDEArray;
4853       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4854       gameInfo.holdingsSize = 8;
4855       startedFromSetupPosition = TRUE;
4856       break;
4857     case VariantCrazyhouse:
4858     case VariantBughouse:
4859       pieces = FIDEArray;
4860       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4861       gameInfo.holdingsSize = 5;
4862       break;
4863     case VariantWildCastle:
4864       pieces = FIDEArray;
4865       /* !!?shuffle with kings guaranteed to be on d or e file */
4866       shuffleOpenings = 1;
4867       break;
4868     case VariantNoCastle:
4869       pieces = FIDEArray;
4870       nrCastlingRights = 0;
4871       /* !!?unconstrained back-rank shuffle */
4872       shuffleOpenings = 1;
4873       break;
4874     }
4875
4876     overrule = 0;
4877     if(appData.NrFiles >= 0) {
4878         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4879         gameInfo.boardWidth = appData.NrFiles;
4880     }
4881     if(appData.NrRanks >= 0) {
4882         gameInfo.boardHeight = appData.NrRanks;
4883     }
4884     if(appData.holdingsSize >= 0) {
4885         i = appData.holdingsSize;
4886         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4887         gameInfo.holdingsSize = i;
4888     }
4889     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4890     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4891         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4892
4893     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4894     if(pawnRow < 1) pawnRow = 1;
4895
4896     /* User pieceToChar list overrules defaults */
4897     if(appData.pieceToCharTable != NULL)
4898         SetCharTable(pieceToChar, appData.pieceToCharTable);
4899
4900     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4901
4902         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4903             s = (ChessSquare) 0; /* account holding counts in guard band */
4904         for( i=0; i<BOARD_HEIGHT; i++ )
4905             initialPosition[i][j] = s;
4906
4907         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4908         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4909         initialPosition[pawnRow][j] = WhitePawn;
4910         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4911         if(gameInfo.variant == VariantXiangqi) {
4912             if(j&1) {
4913                 initialPosition[pawnRow][j] = 
4914                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4915                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4916                    initialPosition[2][j] = WhiteCannon;
4917                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4918                 }
4919             }
4920         }
4921         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4922     }
4923     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4924
4925             j=BOARD_LEFT+1;
4926             initialPosition[1][j] = WhiteBishop;
4927             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4928             j=BOARD_RGHT-2;
4929             initialPosition[1][j] = WhiteRook;
4930             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4931     }
4932
4933     if( nrCastlingRights == -1) {
4934         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4935         /*       This sets default castling rights from none to normal corners   */
4936         /* Variants with other castling rights must set them themselves above    */
4937         nrCastlingRights = 6;
4938        
4939         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4940         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4941         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4942         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4943         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4944         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4945      }
4946
4947      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4948      if(gameInfo.variant == VariantGreat) { // promotion commoners
4949         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4950         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4951         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4952         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4953      }
4954   if (appData.debugMode) {
4955     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4956   }
4957     if(shuffleOpenings) {
4958         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4959         startedFromSetupPosition = TRUE;
4960     }
4961     if(startedFromPositionFile) {
4962       /* [HGM] loadPos: use PositionFile for every new game */
4963       CopyBoard(initialPosition, filePosition);
4964       for(i=0; i<nrCastlingRights; i++)
4965           initialRights[i] = filePosition[CASTLING][i];
4966       startedFromSetupPosition = TRUE;
4967     }
4968
4969     CopyBoard(boards[0], initialPosition);
4970
4971     if(oldx != gameInfo.boardWidth ||
4972        oldy != gameInfo.boardHeight ||
4973        oldh != gameInfo.holdingsWidth
4974 #ifdef GOTHIC
4975        || oldv == VariantGothic ||        // For licensing popups
4976        gameInfo.variant == VariantGothic
4977 #endif
4978 #ifdef FALCON
4979        || oldv == VariantFalcon ||
4980        gameInfo.variant == VariantFalcon
4981 #endif
4982                                          )
4983             InitDrawingSizes(-2 ,0);
4984
4985     if (redraw)
4986       DrawPosition(TRUE, boards[currentMove]);
4987 }
4988
4989 void
4990 SendBoard(cps, moveNum)
4991      ChessProgramState *cps;
4992      int moveNum;
4993 {
4994     char message[MSG_SIZ];
4995     
4996     if (cps->useSetboard) {
4997       char* fen = PositionToFEN(moveNum, cps->fenOverride);
4998       sprintf(message, "setboard %s\n", fen);
4999       SendToProgram(message, cps);
5000       free(fen);
5001
5002     } else {
5003       ChessSquare *bp;
5004       int i, j;
5005       /* Kludge to set black to move, avoiding the troublesome and now
5006        * deprecated "black" command.
5007        */
5008       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5009
5010       SendToProgram("edit\n", cps);
5011       SendToProgram("#\n", cps);
5012       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5013         bp = &boards[moveNum][i][BOARD_LEFT];
5014         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5015           if ((int) *bp < (int) BlackPawn) {
5016             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5017                     AAA + j, ONE + i);
5018             if(message[0] == '+' || message[0] == '~') {
5019                 sprintf(message, "%c%c%c+\n",
5020                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5021                         AAA + j, ONE + i);
5022             }
5023             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5024                 message[1] = BOARD_RGHT   - 1 - j + '1';
5025                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5026             }
5027             SendToProgram(message, cps);
5028           }
5029         }
5030       }
5031     
5032       SendToProgram("c\n", cps);
5033       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5034         bp = &boards[moveNum][i][BOARD_LEFT];
5035         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5036           if (((int) *bp != (int) EmptySquare)
5037               && ((int) *bp >= (int) BlackPawn)) {
5038             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5039                     AAA + j, ONE + i);
5040             if(message[0] == '+' || message[0] == '~') {
5041                 sprintf(message, "%c%c%c+\n",
5042                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5043                         AAA + j, ONE + i);
5044             }
5045             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5046                 message[1] = BOARD_RGHT   - 1 - j + '1';
5047                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5048             }
5049             SendToProgram(message, cps);
5050           }
5051         }
5052       }
5053     
5054       SendToProgram(".\n", cps);
5055     }
5056     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5057 }
5058
5059 int
5060 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5061 {
5062     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5063     /* [HGM] add Shogi promotions */
5064     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5065     ChessSquare piece;
5066     ChessMove moveType;
5067     Boolean premove;
5068
5069     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5070     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5071
5072     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5073       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5074         return FALSE;
5075
5076     piece = boards[currentMove][fromY][fromX];
5077     if(gameInfo.variant == VariantShogi) {
5078         promotionZoneSize = 3;
5079         highestPromotingPiece = (int)WhiteFerz;
5080     }
5081
5082     // next weed out all moves that do not touch the promotion zone at all
5083     if((int)piece >= BlackPawn) {
5084         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5085              return FALSE;
5086         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5087     } else {
5088         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5089            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5090     }
5091
5092     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5093
5094     // weed out mandatory Shogi promotions
5095     if(gameInfo.variant == VariantShogi) {
5096         if(piece >= BlackPawn) {
5097             if(toY == 0 && piece == BlackPawn ||
5098                toY == 0 && piece == BlackQueen ||
5099                toY <= 1 && piece == BlackKnight) {
5100                 *promoChoice = '+';
5101                 return FALSE;
5102             }
5103         } else {
5104             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5105                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5106                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5107                 *promoChoice = '+';
5108                 return FALSE;
5109             }
5110         }
5111     }
5112
5113     // weed out obviously illegal Pawn moves
5114     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5115         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5116         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5117         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5118         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5119         // note we are not allowed to test for valid (non-)capture, due to premove
5120     }
5121
5122     // we either have a choice what to promote to, or (in Shogi) whether to promote
5123     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5124         *promoChoice = PieceToChar(BlackFerz);  // no choice
5125         return FALSE;
5126     }
5127     if(appData.alwaysPromoteToQueen) { // predetermined
5128         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5129              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5130         else *promoChoice = PieceToChar(BlackQueen);
5131         return FALSE;
5132     }
5133
5134     // suppress promotion popup on illegal moves that are not premoves
5135     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5136               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5137     if(appData.testLegality && !premove) {
5138         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5139                         fromY, fromX, toY, toX, NULLCHAR);
5140         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5141            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5142             return FALSE;
5143     }
5144
5145     return TRUE;
5146 }
5147
5148 int
5149 InPalace(row, column)
5150      int row, column;
5151 {   /* [HGM] for Xiangqi */
5152     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5153          column < (BOARD_WIDTH + 4)/2 &&
5154          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5155     return FALSE;
5156 }
5157
5158 int
5159 PieceForSquare (x, y)
5160      int x;
5161      int y;
5162 {
5163   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5164      return -1;
5165   else
5166      return boards[currentMove][y][x];
5167 }
5168
5169 int
5170 OKToStartUserMove(x, y)
5171      int x, y;
5172 {
5173     ChessSquare from_piece;
5174     int white_piece;
5175
5176     if (matchMode) return FALSE;
5177     if (gameMode == EditPosition) return TRUE;
5178
5179     if (x >= 0 && y >= 0)
5180       from_piece = boards[currentMove][y][x];
5181     else
5182       from_piece = EmptySquare;
5183
5184     if (from_piece == EmptySquare) return FALSE;
5185
5186     white_piece = (int)from_piece >= (int)WhitePawn &&
5187       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5188
5189     switch (gameMode) {
5190       case PlayFromGameFile:
5191       case AnalyzeFile:
5192       case TwoMachinesPlay:
5193       case EndOfGame:
5194         return FALSE;
5195
5196       case IcsObserving:
5197       case IcsIdle:
5198         return FALSE;
5199
5200       case MachinePlaysWhite:
5201       case IcsPlayingBlack:
5202         if (appData.zippyPlay) return FALSE;
5203         if (white_piece) {
5204             DisplayMoveError(_("You are playing Black"));
5205             return FALSE;
5206         }
5207         break;
5208
5209       case MachinePlaysBlack:
5210       case IcsPlayingWhite:
5211         if (appData.zippyPlay) return FALSE;
5212         if (!white_piece) {
5213             DisplayMoveError(_("You are playing White"));
5214             return FALSE;
5215         }
5216         break;
5217
5218       case EditGame:
5219         if (!white_piece && WhiteOnMove(currentMove)) {
5220             DisplayMoveError(_("It is White's turn"));
5221             return FALSE;
5222         }           
5223         if (white_piece && !WhiteOnMove(currentMove)) {
5224             DisplayMoveError(_("It is Black's turn"));
5225             return FALSE;
5226         }           
5227         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5228             /* Editing correspondence game history */
5229             /* Could disallow this or prompt for confirmation */
5230             cmailOldMove = -1;
5231         }
5232         break;
5233
5234       case BeginningOfGame:
5235         if (appData.icsActive) return FALSE;
5236         if (!appData.noChessProgram) {
5237             if (!white_piece) {
5238                 DisplayMoveError(_("You are playing White"));
5239                 return FALSE;
5240             }
5241         }
5242         break;
5243         
5244       case Training:
5245         if (!white_piece && WhiteOnMove(currentMove)) {
5246             DisplayMoveError(_("It is White's turn"));
5247             return FALSE;
5248         }           
5249         if (white_piece && !WhiteOnMove(currentMove)) {
5250             DisplayMoveError(_("It is Black's turn"));
5251             return FALSE;
5252         }           
5253         break;
5254
5255       default:
5256       case IcsExamining:
5257         break;
5258     }
5259     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5260         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5261         && gameMode != AnalyzeFile && gameMode != Training) {
5262         DisplayMoveError(_("Displayed position is not current"));
5263         return FALSE;
5264     }
5265     return TRUE;
5266 }
5267
5268 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5269 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5270 int lastLoadGameUseList = FALSE;
5271 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5272 ChessMove lastLoadGameStart = (ChessMove) 0;
5273
5274 ChessMove
5275 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5276      int fromX, fromY, toX, toY;
5277      int promoChar;
5278      Boolean captureOwn;
5279 {
5280     ChessMove moveType;
5281     ChessSquare pdown, pup;
5282
5283     /* Check if the user is playing in turn.  This is complicated because we
5284        let the user "pick up" a piece before it is his turn.  So the piece he
5285        tried to pick up may have been captured by the time he puts it down!
5286        Therefore we use the color the user is supposed to be playing in this
5287        test, not the color of the piece that is currently on the starting
5288        square---except in EditGame mode, where the user is playing both
5289        sides; fortunately there the capture race can't happen.  (It can
5290        now happen in IcsExamining mode, but that's just too bad.  The user
5291        will get a somewhat confusing message in that case.)
5292        */
5293
5294     switch (gameMode) {
5295       case PlayFromGameFile:
5296       case AnalyzeFile:
5297       case TwoMachinesPlay:
5298       case EndOfGame:
5299       case IcsObserving:
5300       case IcsIdle:
5301         /* We switched into a game mode where moves are not accepted,
5302            perhaps while the mouse button was down. */
5303         return ImpossibleMove;
5304
5305       case MachinePlaysWhite:
5306         /* User is moving for Black */
5307         if (WhiteOnMove(currentMove)) {
5308             DisplayMoveError(_("It is White's turn"));
5309             return ImpossibleMove;
5310         }
5311         break;
5312
5313       case MachinePlaysBlack:
5314         /* User is moving for White */
5315         if (!WhiteOnMove(currentMove)) {
5316             DisplayMoveError(_("It is Black's turn"));
5317             return ImpossibleMove;
5318         }
5319         break;
5320
5321       case EditGame:
5322       case IcsExamining:
5323       case BeginningOfGame:
5324       case AnalyzeMode:
5325       case Training:
5326         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5327             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5328             /* User is moving for Black */
5329             if (WhiteOnMove(currentMove)) {
5330                 DisplayMoveError(_("It is White's turn"));
5331                 return ImpossibleMove;
5332             }
5333         } else {
5334             /* User is moving for White */
5335             if (!WhiteOnMove(currentMove)) {
5336                 DisplayMoveError(_("It is Black's turn"));
5337                 return ImpossibleMove;
5338             }
5339         }
5340         break;
5341
5342       case IcsPlayingBlack:
5343         /* User is moving for Black */
5344         if (WhiteOnMove(currentMove)) {
5345             if (!appData.premove) {
5346                 DisplayMoveError(_("It is White's turn"));
5347             } else if (toX >= 0 && toY >= 0) {
5348                 premoveToX = toX;
5349                 premoveToY = toY;
5350                 premoveFromX = fromX;
5351                 premoveFromY = fromY;
5352                 premovePromoChar = promoChar;
5353                 gotPremove = 1;
5354                 if (appData.debugMode) 
5355                     fprintf(debugFP, "Got premove: fromX %d,"
5356                             "fromY %d, toX %d, toY %d\n",
5357                             fromX, fromY, toX, toY);
5358             }
5359             return ImpossibleMove;
5360         }
5361         break;
5362
5363       case IcsPlayingWhite:
5364         /* User is moving for White */
5365         if (!WhiteOnMove(currentMove)) {
5366             if (!appData.premove) {
5367                 DisplayMoveError(_("It is Black's turn"));
5368             } else if (toX >= 0 && toY >= 0) {
5369                 premoveToX = toX;
5370                 premoveToY = toY;
5371                 premoveFromX = fromX;
5372                 premoveFromY = fromY;
5373                 premovePromoChar = promoChar;
5374                 gotPremove = 1;
5375                 if (appData.debugMode) 
5376                     fprintf(debugFP, "Got premove: fromX %d,"
5377                             "fromY %d, toX %d, toY %d\n",
5378                             fromX, fromY, toX, toY);
5379             }
5380             return ImpossibleMove;
5381         }
5382         break;
5383
5384       default:
5385         break;
5386
5387       case EditPosition:
5388         /* EditPosition, empty square, or different color piece;
5389            click-click move is possible */
5390         if (toX == -2 || toY == -2) {
5391             boards[0][fromY][fromX] = EmptySquare;
5392             return AmbiguousMove;
5393         } else if (toX >= 0 && toY >= 0) {
5394             boards[0][toY][toX] = boards[0][fromY][fromX];
5395             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5396                 if(boards[0][fromY][0] != EmptySquare) {
5397                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5398                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5399                 }
5400             } else
5401             if(fromX == BOARD_RGHT+1) {
5402                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5403                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5404                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5405                 }
5406             } else
5407             boards[0][fromY][fromX] = EmptySquare;
5408             return AmbiguousMove;
5409         }
5410         return ImpossibleMove;
5411     }
5412
5413     if(toX < 0 || toY < 0) return ImpossibleMove;
5414     pdown = boards[currentMove][fromY][fromX];
5415     pup = boards[currentMove][toY][toX];
5416
5417     /* [HGM] If move started in holdings, it means a drop */
5418     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5419          if( pup != EmptySquare ) return ImpossibleMove;
5420          if(appData.testLegality) {
5421              /* it would be more logical if LegalityTest() also figured out
5422               * which drops are legal. For now we forbid pawns on back rank.
5423               * Shogi is on its own here...
5424               */
5425              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5426                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5427                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5428          }
5429          return WhiteDrop; /* Not needed to specify white or black yet */
5430     }
5431
5432     userOfferedDraw = FALSE;
5433         
5434     /* [HGM] always test for legality, to get promotion info */
5435     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5436                                          fromY, fromX, toY, toX, promoChar);
5437     /* [HGM] but possibly ignore an IllegalMove result */
5438     if (appData.testLegality) {
5439         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5440             DisplayMoveError(_("Illegal move"));
5441             return ImpossibleMove;
5442         }
5443     }
5444
5445     return moveType;
5446     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5447        function is made into one that returns an OK move type if FinishMove
5448        should be called. This to give the calling driver routine the
5449        opportunity to finish the userMove input with a promotion popup,
5450        without bothering the user with this for invalid or illegal moves */
5451
5452 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5453 }
5454
5455 /* Common tail of UserMoveEvent and DropMenuEvent */
5456 int
5457 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5458      ChessMove moveType;
5459      int fromX, fromY, toX, toY;
5460      /*char*/int promoChar;
5461 {
5462     char *bookHit = 0;
5463
5464     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5465         // [HGM] superchess: suppress promotions to non-available piece
5466         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5467         if(WhiteOnMove(currentMove)) {
5468             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5469         } else {
5470             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5471         }
5472     }
5473
5474     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5475        move type in caller when we know the move is a legal promotion */
5476     if(moveType == NormalMove && promoChar)
5477         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5478
5479     /* [HGM] convert drag-and-drop piece drops to standard form */
5480     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5481          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5482            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5483                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5484            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5485            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5486            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5487            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5488          fromY = DROP_RANK;
5489     }
5490
5491     /* [HGM] <popupFix> The following if has been moved here from
5492        UserMoveEvent(). Because it seemed to belong here (why not allow
5493        piece drops in training games?), and because it can only be
5494        performed after it is known to what we promote. */
5495     if (gameMode == Training) {
5496       /* compare the move played on the board to the next move in the
5497        * game. If they match, display the move and the opponent's response. 
5498        * If they don't match, display an error message.
5499        */
5500       int saveAnimate;
5501       Board testBoard;
5502       CopyBoard(testBoard, boards[currentMove]);
5503       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5504
5505       if (CompareBoards(testBoard, boards[currentMove+1])) {
5506         ForwardInner(currentMove+1);
5507
5508         /* Autoplay the opponent's response.
5509          * if appData.animate was TRUE when Training mode was entered,
5510          * the response will be animated.
5511          */
5512         saveAnimate = appData.animate;
5513         appData.animate = animateTraining;
5514         ForwardInner(currentMove+1);
5515         appData.animate = saveAnimate;
5516
5517         /* check for the end of the game */
5518         if (currentMove >= forwardMostMove) {
5519           gameMode = PlayFromGameFile;
5520           ModeHighlight();
5521           SetTrainingModeOff();
5522           DisplayInformation(_("End of game"));
5523         }
5524       } else {
5525         DisplayError(_("Incorrect move"), 0);
5526       }
5527       return 1;
5528     }
5529
5530   /* Ok, now we know that the move is good, so we can kill
5531      the previous line in Analysis Mode */
5532   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5533                                 && currentMove < forwardMostMove) {
5534     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5535   }
5536
5537   /* If we need the chess program but it's dead, restart it */
5538   ResurrectChessProgram();
5539
5540   /* A user move restarts a paused game*/
5541   if (pausing)
5542     PauseEvent();
5543
5544   thinkOutput[0] = NULLCHAR;
5545
5546   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5547
5548   if (gameMode == BeginningOfGame) {
5549     if (appData.noChessProgram) {
5550       gameMode = EditGame;
5551       SetGameInfo();
5552     } else {
5553       char buf[MSG_SIZ];
5554       gameMode = MachinePlaysBlack;
5555       StartClocks();
5556       SetGameInfo();
5557       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5558       DisplayTitle(buf);
5559       if (first.sendName) {
5560         sprintf(buf, "name %s\n", gameInfo.white);
5561         SendToProgram(buf, &first);
5562       }
5563       StartClocks();
5564     }
5565     ModeHighlight();
5566   }
5567
5568   /* Relay move to ICS or chess engine */
5569   if (appData.icsActive) {
5570     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5571         gameMode == IcsExamining) {
5572       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5573       ics_user_moved = 1;
5574     }
5575   } else {
5576     if (first.sendTime && (gameMode == BeginningOfGame ||
5577                            gameMode == MachinePlaysWhite ||
5578                            gameMode == MachinePlaysBlack)) {
5579       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5580     }
5581     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5582          // [HGM] book: if program might be playing, let it use book
5583         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5584         first.maybeThinking = TRUE;
5585     } else SendMoveToProgram(forwardMostMove-1, &first);
5586     if (currentMove == cmailOldMove + 1) {
5587       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5588     }
5589   }
5590
5591   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5592
5593   switch (gameMode) {
5594   case EditGame:
5595     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5596     case MT_NONE:
5597     case MT_CHECK:
5598       break;
5599     case MT_CHECKMATE:
5600     case MT_STAINMATE:
5601       if (WhiteOnMove(currentMove)) {
5602         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5603       } else {
5604         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5605       }
5606       break;
5607     case MT_STALEMATE:
5608       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5609       break;
5610     }
5611     break;
5612     
5613   case MachinePlaysBlack:
5614   case MachinePlaysWhite:
5615     /* disable certain menu options while machine is thinking */
5616     SetMachineThinkingEnables();
5617     break;
5618
5619   default:
5620     break;
5621   }
5622
5623   if(bookHit) { // [HGM] book: simulate book reply
5624         static char bookMove[MSG_SIZ]; // a bit generous?
5625
5626         programStats.nodes = programStats.depth = programStats.time = 
5627         programStats.score = programStats.got_only_move = 0;
5628         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5629
5630         strcpy(bookMove, "move ");
5631         strcat(bookMove, bookHit);
5632         HandleMachineMove(bookMove, &first);
5633   }
5634   return 1;
5635 }
5636
5637 void
5638 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5639      int fromX, fromY, toX, toY;
5640      int promoChar;
5641 {
5642     /* [HGM] This routine was added to allow calling of its two logical
5643        parts from other modules in the old way. Before, UserMoveEvent()
5644        automatically called FinishMove() if the move was OK, and returned
5645        otherwise. I separated the two, in order to make it possible to
5646        slip a promotion popup in between. But that it always needs two
5647        calls, to the first part, (now called UserMoveTest() ), and to
5648        FinishMove if the first part succeeded. Calls that do not need
5649        to do anything in between, can call this routine the old way. 
5650     */
5651     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5652 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5653     if(moveType == AmbiguousMove)
5654         DrawPosition(FALSE, boards[currentMove]);
5655     else if(moveType != ImpossibleMove && moveType != Comment)
5656         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5657 }
5658
5659 void
5660 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5661      Board board;
5662      int flags;
5663      ChessMove kind;
5664      int rf, ff, rt, ft;
5665      VOIDSTAR closure;
5666 {
5667     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5668     Markers *m = (Markers *) closure;
5669     if(rf == fromY && ff == fromX)
5670         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5671                          || kind == WhiteCapturesEnPassant
5672                          || kind == BlackCapturesEnPassant);
5673     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5674 }
5675
5676 void
5677 MarkTargetSquares(int clear)
5678 {
5679   int x, y;
5680   if(!appData.markers || !appData.highlightDragging || 
5681      !appData.testLegality || gameMode == EditPosition) return;
5682   if(clear) {
5683     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5684   } else {
5685     int capt = 0;
5686     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5687     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5688       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5689       if(capt)
5690       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5691     }
5692   }
5693   DrawPosition(TRUE, NULL);
5694 }
5695
5696 void LeftClick(ClickType clickType, int xPix, int yPix)
5697 {
5698     int x, y;
5699     Boolean saveAnimate;
5700     static int second = 0, promotionChoice = 0;
5701     char promoChoice = NULLCHAR;
5702
5703     if (clickType == Press) ErrorPopDown();
5704     MarkTargetSquares(1);
5705
5706     x = EventToSquare(xPix, BOARD_WIDTH);
5707     y = EventToSquare(yPix, BOARD_HEIGHT);
5708     if (!flipView && y >= 0) {
5709         y = BOARD_HEIGHT - 1 - y;
5710     }
5711     if (flipView && x >= 0) {
5712         x = BOARD_WIDTH - 1 - x;
5713     }
5714
5715     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5716         if(clickType == Release) return; // ignore upclick of click-click destination
5717         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5718         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5719         if(gameInfo.holdingsWidth && 
5720                 (WhiteOnMove(currentMove) 
5721                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5722                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5723             // click in right holdings, for determining promotion piece
5724             ChessSquare p = boards[currentMove][y][x];
5725             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5726             if(p != EmptySquare) {
5727                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5728                 fromX = fromY = -1;
5729                 return;
5730             }
5731         }
5732         DrawPosition(FALSE, boards[currentMove]);
5733         return;
5734     }
5735
5736     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5737     if(clickType == Press
5738             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5739               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5740               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5741         return;
5742
5743     if (fromX == -1) {
5744         if (clickType == Press) {
5745             /* First square */
5746             if (OKToStartUserMove(x, y)) {
5747                 fromX = x;
5748                 fromY = y;
5749                 second = 0;
5750                 MarkTargetSquares(0);
5751                 DragPieceBegin(xPix, yPix);
5752                 if (appData.highlightDragging) {
5753                     SetHighlights(x, y, -1, -1);
5754                 }
5755             }
5756         }
5757         return;
5758     }
5759
5760     /* fromX != -1 */
5761     if (clickType == Press && gameMode != EditPosition) {
5762         ChessSquare fromP;
5763         ChessSquare toP;
5764         int frc;
5765
5766         // ignore off-board to clicks
5767         if(y < 0 || x < 0) return;
5768
5769         /* Check if clicking again on the same color piece */
5770         fromP = boards[currentMove][fromY][fromX];
5771         toP = boards[currentMove][y][x];
5772         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5773         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5774              WhitePawn <= toP && toP <= WhiteKing &&
5775              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5776              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5777             (BlackPawn <= fromP && fromP <= BlackKing && 
5778              BlackPawn <= toP && toP <= BlackKing &&
5779              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5780              !(fromP == BlackKing && toP == BlackRook && frc))) {
5781             /* Clicked again on same color piece -- changed his mind */
5782             second = (x == fromX && y == fromY);
5783             if (appData.highlightDragging) {
5784                 SetHighlights(x, y, -1, -1);
5785             } else {
5786                 ClearHighlights();
5787             }
5788             if (OKToStartUserMove(x, y)) {
5789                 fromX = x;
5790                 fromY = y;
5791                 MarkTargetSquares(0);
5792                 DragPieceBegin(xPix, yPix);
5793             }
5794             return;
5795         }
5796         // ignore clicks on holdings
5797         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5798     }
5799
5800     if (clickType == Release && x == fromX && y == fromY) {
5801         DragPieceEnd(xPix, yPix);
5802         if (appData.animateDragging) {
5803             /* Undo animation damage if any */
5804             DrawPosition(FALSE, NULL);
5805         }
5806         if (second) {
5807             /* Second up/down in same square; just abort move */
5808             second = 0;
5809             fromX = fromY = -1;
5810             ClearHighlights();
5811             gotPremove = 0;
5812             ClearPremoveHighlights();
5813         } else {
5814             /* First upclick in same square; start click-click mode */
5815             SetHighlights(x, y, -1, -1);
5816         }
5817         return;
5818     }
5819
5820     /* we now have a different from- and (possibly off-board) to-square */
5821     /* Completed move */
5822     toX = x;
5823     toY = y;
5824     saveAnimate = appData.animate;
5825     if (clickType == Press) {
5826         /* Finish clickclick move */
5827         if (appData.animate || appData.highlightLastMove) {
5828             SetHighlights(fromX, fromY, toX, toY);
5829         } else {
5830             ClearHighlights();
5831         }
5832     } else {
5833         /* Finish drag move */
5834         if (appData.highlightLastMove) {
5835             SetHighlights(fromX, fromY, toX, toY);
5836         } else {
5837             ClearHighlights();
5838         }
5839         DragPieceEnd(xPix, yPix);
5840         /* Don't animate move and drag both */
5841         appData.animate = FALSE;
5842     }
5843
5844     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5845     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5846         ChessSquare piece = boards[currentMove][fromY][fromX];
5847         if(gameMode == EditPosition && piece != EmptySquare &&
5848            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5849             int n;
5850              
5851             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5852                 n = PieceToNumber(piece - (int)BlackPawn);
5853                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5854                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5855                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5856             } else
5857             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5858                 n = PieceToNumber(piece);
5859                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5860                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5861                 boards[currentMove][n][BOARD_WIDTH-2]++;
5862             }
5863             boards[currentMove][fromY][fromX] = EmptySquare;
5864         }
5865         ClearHighlights();
5866         fromX = fromY = -1;
5867         DrawPosition(TRUE, boards[currentMove]);
5868         return;
5869     }
5870
5871     // off-board moves should not be highlighted
5872     if(x < 0 || x < 0) ClearHighlights();
5873
5874     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5875         SetHighlights(fromX, fromY, toX, toY);
5876         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5877             // [HGM] super: promotion to captured piece selected from holdings
5878             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5879             promotionChoice = TRUE;
5880             // kludge follows to temporarily execute move on display, without promoting yet
5881             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5882             boards[currentMove][toY][toX] = p;
5883             DrawPosition(FALSE, boards[currentMove]);
5884             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5885             boards[currentMove][toY][toX] = q;
5886             DisplayMessage("Click in holdings to choose piece", "");
5887             return;
5888         }
5889         PromotionPopUp();
5890     } else {
5891         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5892         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5893         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5894         fromX = fromY = -1;
5895     }
5896     appData.animate = saveAnimate;
5897     if (appData.animate || appData.animateDragging) {
5898         /* Undo animation damage if needed */
5899         DrawPosition(FALSE, NULL);
5900     }
5901 }
5902
5903 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5904 {
5905 //    char * hint = lastHint;
5906     FrontEndProgramStats stats;
5907
5908     stats.which = cps == &first ? 0 : 1;
5909     stats.depth = cpstats->depth;
5910     stats.nodes = cpstats->nodes;
5911     stats.score = cpstats->score;
5912     stats.time = cpstats->time;
5913     stats.pv = cpstats->movelist;
5914     stats.hint = lastHint;
5915     stats.an_move_index = 0;
5916     stats.an_move_count = 0;
5917
5918     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5919         stats.hint = cpstats->move_name;
5920         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5921         stats.an_move_count = cpstats->nr_moves;
5922     }
5923
5924     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5925
5926     SetProgramStats( &stats );
5927 }
5928
5929 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5930 {   // [HGM] book: this routine intercepts moves to simulate book replies
5931     char *bookHit = NULL;
5932
5933     //first determine if the incoming move brings opponent into his book
5934     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5935         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5936     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5937     if(bookHit != NULL && !cps->bookSuspend) {
5938         // make sure opponent is not going to reply after receiving move to book position
5939         SendToProgram("force\n", cps);
5940         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5941     }
5942     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5943     // now arrange restart after book miss
5944     if(bookHit) {
5945         // after a book hit we never send 'go', and the code after the call to this routine
5946         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5947         char buf[MSG_SIZ];
5948         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5949         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5950         SendToProgram(buf, cps);
5951         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5952     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5953         SendToProgram("go\n", cps);
5954         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5955     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5956         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5957             SendToProgram("go\n", cps); 
5958         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5959     }
5960     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5961 }
5962
5963 char *savedMessage;
5964 ChessProgramState *savedState;
5965 void DeferredBookMove(void)
5966 {
5967         if(savedState->lastPing != savedState->lastPong)
5968                     ScheduleDelayedEvent(DeferredBookMove, 10);
5969         else
5970         HandleMachineMove(savedMessage, savedState);
5971 }
5972
5973 void
5974 HandleMachineMove(message, cps)
5975      char *message;
5976      ChessProgramState *cps;
5977 {
5978     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5979     char realname[MSG_SIZ];
5980     int fromX, fromY, toX, toY;
5981     ChessMove moveType;
5982     char promoChar;
5983     char *p;
5984     int machineWhite;
5985     char *bookHit;
5986
5987     cps->userError = 0;
5988
5989 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5990     /*
5991      * Kludge to ignore BEL characters
5992      */
5993     while (*message == '\007') message++;
5994
5995     /*
5996      * [HGM] engine debug message: ignore lines starting with '#' character
5997      */
5998     if(cps->debug && *message == '#') return;
5999
6000     /*
6001      * Look for book output
6002      */
6003     if (cps == &first && bookRequested) {
6004         if (message[0] == '\t' || message[0] == ' ') {
6005             /* Part of the book output is here; append it */
6006             strcat(bookOutput, message);
6007             strcat(bookOutput, "  \n");
6008             return;
6009         } else if (bookOutput[0] != NULLCHAR) {
6010             /* All of book output has arrived; display it */
6011             char *p = bookOutput;
6012             while (*p != NULLCHAR) {
6013                 if (*p == '\t') *p = ' ';
6014                 p++;
6015             }
6016             DisplayInformation(bookOutput);
6017             bookRequested = FALSE;
6018             /* Fall through to parse the current output */
6019         }
6020     }
6021
6022     /*
6023      * Look for machine move.
6024      */
6025     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6026         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6027     {
6028         /* This method is only useful on engines that support ping */
6029         if (cps->lastPing != cps->lastPong) {
6030           if (gameMode == BeginningOfGame) {
6031             /* Extra move from before last new; ignore */
6032             if (appData.debugMode) {
6033                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6034             }
6035           } else {
6036             if (appData.debugMode) {
6037                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6038                         cps->which, gameMode);
6039             }
6040
6041             SendToProgram("undo\n", cps);
6042           }
6043           return;
6044         }
6045
6046         switch (gameMode) {
6047           case BeginningOfGame:
6048             /* Extra move from before last reset; ignore */
6049             if (appData.debugMode) {
6050                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6051             }
6052             return;
6053
6054           case EndOfGame:
6055           case IcsIdle:
6056           default:
6057             /* Extra move after we tried to stop.  The mode test is
6058                not a reliable way of detecting this problem, but it's
6059                the best we can do on engines that don't support ping.
6060             */
6061             if (appData.debugMode) {
6062                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6063                         cps->which, gameMode);
6064             }
6065             SendToProgram("undo\n", cps);
6066             return;
6067
6068           case MachinePlaysWhite:
6069           case IcsPlayingWhite:
6070             machineWhite = TRUE;
6071             break;
6072
6073           case MachinePlaysBlack:
6074           case IcsPlayingBlack:
6075             machineWhite = FALSE;
6076             break;
6077
6078           case TwoMachinesPlay:
6079             machineWhite = (cps->twoMachinesColor[0] == 'w');
6080             break;
6081         }
6082         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6083             if (appData.debugMode) {
6084                 fprintf(debugFP,
6085                         "Ignoring move out of turn by %s, gameMode %d"
6086                         ", forwardMost %d\n",
6087                         cps->which, gameMode, forwardMostMove);
6088             }
6089             return;
6090         }
6091
6092     if (appData.debugMode) { int f = forwardMostMove;
6093         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6094                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6095                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6096     }
6097         if(cps->alphaRank) AlphaRank(machineMove, 4);
6098         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6099                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6100             /* Machine move could not be parsed; ignore it. */
6101             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6102                     machineMove, cps->which);
6103             DisplayError(buf1, 0);
6104             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6105                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6106             if (gameMode == TwoMachinesPlay) {
6107               GameEnds(machineWhite ? BlackWins : WhiteWins,
6108                        buf1, GE_XBOARD);
6109             }
6110             return;
6111         }
6112
6113         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6114         /* So we have to redo legality test with true e.p. status here,  */
6115         /* to make sure an illegal e.p. capture does not slip through,   */
6116         /* to cause a forfeit on a justified illegal-move complaint      */
6117         /* of the opponent.                                              */
6118         if( gameMode==TwoMachinesPlay && appData.testLegality
6119             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6120                                                               ) {
6121            ChessMove moveType;
6122            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6123                              fromY, fromX, toY, toX, promoChar);
6124             if (appData.debugMode) {
6125                 int i;
6126                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6127                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6128                 fprintf(debugFP, "castling rights\n");
6129             }
6130             if(moveType == IllegalMove) {
6131                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6132                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6133                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6134                            buf1, GE_XBOARD);
6135                 return;
6136            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6137            /* [HGM] Kludge to handle engines that send FRC-style castling
6138               when they shouldn't (like TSCP-Gothic) */
6139            switch(moveType) {
6140              case WhiteASideCastleFR:
6141              case BlackASideCastleFR:
6142                toX+=2;
6143                currentMoveString[2]++;
6144                break;
6145              case WhiteHSideCastleFR:
6146              case BlackHSideCastleFR:
6147                toX--;
6148                currentMoveString[2]--;
6149                break;
6150              default: ; // nothing to do, but suppresses warning of pedantic compilers
6151            }
6152         }
6153         hintRequested = FALSE;
6154         lastHint[0] = NULLCHAR;
6155         bookRequested = FALSE;
6156         /* Program may be pondering now */
6157         cps->maybeThinking = TRUE;
6158         if (cps->sendTime == 2) cps->sendTime = 1;
6159         if (cps->offeredDraw) cps->offeredDraw--;
6160
6161 #if ZIPPY
6162         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6163             first.initDone) {
6164           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6165           ics_user_moved = 1;
6166           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6167                 char buf[3*MSG_SIZ];
6168
6169                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6170                         programStats.score / 100.,
6171                         programStats.depth,
6172                         programStats.time / 100.,
6173                         (unsigned int)programStats.nodes,
6174                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6175                         programStats.movelist);
6176                 SendToICS(buf);
6177 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6178           }
6179         }
6180 #endif
6181         /* currentMoveString is set as a side-effect of ParseOneMove */
6182         strcpy(machineMove, currentMoveString);
6183         strcat(machineMove, "\n");
6184         strcpy(moveList[forwardMostMove], machineMove);
6185
6186         /* [AS] Save move info and clear stats for next move */
6187         pvInfoList[ forwardMostMove ].score = programStats.score;
6188         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6189         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6190         ClearProgramStats();
6191         thinkOutput[0] = NULLCHAR;
6192         hiddenThinkOutputState = 0;
6193
6194         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6195
6196         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6197         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6198             int count = 0;
6199
6200             while( count < adjudicateLossPlies ) {
6201                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6202
6203                 if( count & 1 ) {
6204                     score = -score; /* Flip score for winning side */
6205                 }
6206
6207                 if( score > adjudicateLossThreshold ) {
6208                     break;
6209                 }
6210
6211                 count++;
6212             }
6213
6214             if( count >= adjudicateLossPlies ) {
6215                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6216
6217                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6218                     "Xboard adjudication", 
6219                     GE_XBOARD );
6220
6221                 return;
6222             }
6223         }
6224
6225         if( gameMode == TwoMachinesPlay ) {
6226           // [HGM] some adjudications useful with buggy engines
6227             int k, count = 0; static int bare = 1;
6228           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6229
6230
6231             if( appData.testLegality )
6232             {   /* [HGM] Some more adjudications for obstinate engines */
6233                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6234                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6235                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6236                 static int moveCount = 6;
6237                 ChessMove result;
6238                 char *reason = NULL;
6239
6240                 /* Count what is on board. */
6241                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6242                 {   ChessSquare p = boards[forwardMostMove][i][j];
6243                     int m=i;
6244
6245                     switch((int) p)
6246                     {   /* count B,N,R and other of each side */
6247                         case WhiteKing:
6248                         case BlackKing:
6249                              NrK++; break; // [HGM] atomic: count Kings
6250                         case WhiteKnight:
6251                              NrWN++; break;
6252                         case WhiteBishop:
6253                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6254                              bishopsColor |= 1 << ((i^j)&1);
6255                              NrWB++; break;
6256                         case BlackKnight:
6257                              NrBN++; break;
6258                         case BlackBishop:
6259                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6260                              bishopsColor |= 1 << ((i^j)&1);
6261                              NrBB++; break;
6262                         case WhiteRook:
6263                              NrWR++; break;
6264                         case BlackRook:
6265                              NrBR++; break;
6266                         case WhiteQueen:
6267                              NrWQ++; break;
6268                         case BlackQueen:
6269                              NrBQ++; break;
6270                         case EmptySquare: 
6271                              break;
6272                         case BlackPawn:
6273                              m = 7-i;
6274                         case WhitePawn:
6275                              PawnAdvance += m; NrPawns++;
6276                     }
6277                     NrPieces += (p != EmptySquare);
6278                     NrW += ((int)p < (int)BlackPawn);
6279                     if(gameInfo.variant == VariantXiangqi && 
6280                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6281                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6282                         NrW -= ((int)p < (int)BlackPawn);
6283                     }
6284                 }
6285
6286                 /* Some material-based adjudications that have to be made before stalemate test */
6287                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6288                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6289                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6290                      if(appData.checkMates) {
6291                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6292                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6293                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6294                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6295                          return;
6296                      }
6297                 }
6298
6299                 /* Bare King in Shatranj (loses) or Losers (wins) */
6300                 if( NrW == 1 || NrPieces - NrW == 1) {
6301                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6302                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6303                      if(appData.checkMates) {
6304                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6305                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6307                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6308                          return;
6309                      }
6310                   } else
6311                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6312                   {    /* bare King */
6313                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6314                         if(appData.checkMates) {
6315                             /* but only adjudicate if adjudication enabled */
6316                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6317                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6319                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6320                             return;
6321                         }
6322                   }
6323                 } else bare = 1;
6324
6325
6326             // don't wait for engine to announce game end if we can judge ourselves
6327             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6328               case MT_CHECK:
6329                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6330                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6331                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6332                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6333                             checkCnt++;
6334                         if(checkCnt >= 2) {
6335                             reason = "Xboard adjudication: 3rd check";
6336                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6337                             break;
6338                         }
6339                     }
6340                 }
6341               case MT_NONE:
6342               default:
6343                 break;
6344               case MT_STALEMATE:
6345               case MT_STAINMATE:
6346                 reason = "Xboard adjudication: Stalemate";
6347                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6348                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6349                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6350                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6351                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6352                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6353                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6354                                                                         EP_CHECKMATE : EP_WINS);
6355                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6356                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6357                 }
6358                 break;
6359               case MT_CHECKMATE:
6360                 reason = "Xboard adjudication: Checkmate";
6361                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6362                 break;
6363             }
6364
6365                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6366                     case EP_STALEMATE:
6367                         result = GameIsDrawn; break;
6368                     case EP_CHECKMATE:
6369                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6370                     case EP_WINS:
6371                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6372                     default:
6373                         result = (ChessMove) 0;
6374                 }
6375                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6376                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6377                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6378                     GameEnds( result, reason, GE_XBOARD );
6379                     return;
6380                 }
6381
6382                 /* Next absolutely insufficient mating material. */
6383                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6384                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6385                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6386                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6387                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6388
6389                      /* always flag draws, for judging claims */
6390                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6391
6392                      if(appData.materialDraws) {
6393                          /* but only adjudicate them if adjudication enabled */
6394                          SendToProgram("force\n", cps->other); // suppress reply
6395                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6396                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6397                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6398                          return;
6399                      }
6400                 }
6401
6402                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6403                 if(NrPieces == 4 && 
6404                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6405                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6406                    || NrWN==2 || NrBN==2     /* KNNK */
6407                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6408                   ) ) {
6409                      if(--moveCount < 0 && appData.trivialDraws)
6410                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6411                           SendToProgram("force\n", cps->other); // suppress reply
6412                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6413                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6414                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6415                           return;
6416                      }
6417                 } else moveCount = 6;
6418             }
6419           }
6420           
6421           if (appData.debugMode) { int i;
6422             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6423                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6424                     appData.drawRepeats);
6425             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6426               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6427             
6428           }
6429
6430                 /* Check for rep-draws */
6431                 count = 0;
6432                 for(k = forwardMostMove-2;
6433                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6434                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6435                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6436                     k-=2)
6437                 {   int rights=0;
6438                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6439                         /* compare castling rights */
6440                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6441                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6442                                 rights++; /* King lost rights, while rook still had them */
6443                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6444                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6445                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6446                                    rights++; /* but at least one rook lost them */
6447                         }
6448                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6449                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6450                                 rights++; 
6451                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6452                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6453                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6454                                    rights++;
6455                         }
6456                         if( rights == 0 && ++count > appData.drawRepeats-2
6457                             && appData.drawRepeats > 1) {
6458                              /* adjudicate after user-specified nr of repeats */
6459                              SendToProgram("force\n", cps->other); // suppress reply
6460                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6461                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6462                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6463                                 // [HGM] xiangqi: check for forbidden perpetuals
6464                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6465                                 for(m=forwardMostMove; m>k; m-=2) {
6466                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6467                                         ourPerpetual = 0; // the current mover did not always check
6468                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6469                                         hisPerpetual = 0; // the opponent did not always check
6470                                 }
6471                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6472                                                                         ourPerpetual, hisPerpetual);
6473                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6474                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6475                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6476                                     return;
6477                                 }
6478                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6479                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6480                                 // Now check for perpetual chases
6481                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6482                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6483                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6484                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6485                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6486                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6487                                         return;
6488                                     }
6489                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6490                                         break; // Abort repetition-checking loop.
6491                                 }
6492                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6493                              }
6494                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6495                              return;
6496                         }
6497                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6498                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6499                     }
6500                 }
6501
6502                 /* Now we test for 50-move draws. Determine ply count */
6503                 count = forwardMostMove;
6504                 /* look for last irreversble move */
6505                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6506                     count--;
6507                 /* if we hit starting position, add initial plies */
6508                 if( count == backwardMostMove )
6509                     count -= initialRulePlies;
6510                 count = forwardMostMove - count; 
6511                 if( count >= 100)
6512                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6513                          /* this is used to judge if draw claims are legal */
6514                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6515                          SendToProgram("force\n", cps->other); // suppress reply
6516                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6517                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6518                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6519                          return;
6520                 }
6521
6522                 /* if draw offer is pending, treat it as a draw claim
6523                  * when draw condition present, to allow engines a way to
6524                  * claim draws before making their move to avoid a race
6525                  * condition occurring after their move
6526                  */
6527                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6528                          char *p = NULL;
6529                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6530                              p = "Draw claim: 50-move rule";
6531                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6532                              p = "Draw claim: 3-fold repetition";
6533                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6534                              p = "Draw claim: insufficient mating material";
6535                          if( p != NULL ) {
6536                              SendToProgram("force\n", cps->other); // suppress reply
6537                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6538                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6539                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540                              return;
6541                          }
6542                 }
6543
6544
6545                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6546                     SendToProgram("force\n", cps->other); // suppress reply
6547                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6548                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6549
6550                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6551
6552                     return;
6553                 }
6554         }
6555
6556         bookHit = NULL;
6557         if (gameMode == TwoMachinesPlay) {
6558             /* [HGM] relaying draw offers moved to after reception of move */
6559             /* and interpreting offer as claim if it brings draw condition */
6560             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6561                 SendToProgram("draw\n", cps->other);
6562             }
6563             if (cps->other->sendTime) {
6564                 SendTimeRemaining(cps->other,
6565                                   cps->other->twoMachinesColor[0] == 'w');
6566             }
6567             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6568             if (firstMove && !bookHit) {
6569                 firstMove = FALSE;
6570                 if (cps->other->useColors) {
6571                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6572                 }
6573                 SendToProgram("go\n", cps->other);
6574             }
6575             cps->other->maybeThinking = TRUE;
6576         }
6577
6578         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6579         
6580         if (!pausing && appData.ringBellAfterMoves) {
6581             RingBell();
6582         }
6583
6584         /* 
6585          * Reenable menu items that were disabled while
6586          * machine was thinking
6587          */
6588         if (gameMode != TwoMachinesPlay)
6589             SetUserThinkingEnables();
6590
6591         // [HGM] book: after book hit opponent has received move and is now in force mode
6592         // force the book reply into it, and then fake that it outputted this move by jumping
6593         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6594         if(bookHit) {
6595                 static char bookMove[MSG_SIZ]; // a bit generous?
6596
6597                 strcpy(bookMove, "move ");
6598                 strcat(bookMove, bookHit);
6599                 message = bookMove;
6600                 cps = cps->other;
6601                 programStats.nodes = programStats.depth = programStats.time = 
6602                 programStats.score = programStats.got_only_move = 0;
6603                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6604
6605                 if(cps->lastPing != cps->lastPong) {
6606                     savedMessage = message; // args for deferred call
6607                     savedState = cps;
6608                     ScheduleDelayedEvent(DeferredBookMove, 10);
6609                     return;
6610                 }
6611                 goto FakeBookMove;
6612         }
6613
6614         return;
6615     }
6616
6617     /* Set special modes for chess engines.  Later something general
6618      *  could be added here; for now there is just one kludge feature,
6619      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6620      *  when "xboard" is given as an interactive command.
6621      */
6622     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6623         cps->useSigint = FALSE;
6624         cps->useSigterm = FALSE;
6625     }
6626     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6627       ParseFeatures(message+8, cps);
6628       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6629     }
6630
6631     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6632      * want this, I was asked to put it in, and obliged.
6633      */
6634     if (!strncmp(message, "setboard ", 9)) {
6635         Board initial_position;
6636
6637         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6638
6639         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6640             DisplayError(_("Bad FEN received from engine"), 0);
6641             return ;
6642         } else {
6643            Reset(TRUE, FALSE);
6644            CopyBoard(boards[0], initial_position);
6645            initialRulePlies = FENrulePlies;
6646            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6647            else gameMode = MachinePlaysBlack;                 
6648            DrawPosition(FALSE, boards[currentMove]);
6649         }
6650         return;
6651     }
6652
6653     /*
6654      * Look for communication commands
6655      */
6656     if (!strncmp(message, "telluser ", 9)) {
6657         DisplayNote(message + 9);
6658         return;
6659     }
6660     if (!strncmp(message, "tellusererror ", 14)) {
6661         cps->userError = 1;
6662         DisplayError(message + 14, 0);
6663         return;
6664     }
6665     if (!strncmp(message, "tellopponent ", 13)) {
6666       if (appData.icsActive) {
6667         if (loggedOn) {
6668           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6669           SendToICS(buf1);
6670         }
6671       } else {
6672         DisplayNote(message + 13);
6673       }
6674       return;
6675     }
6676     if (!strncmp(message, "tellothers ", 11)) {
6677       if (appData.icsActive) {
6678         if (loggedOn) {
6679           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6680           SendToICS(buf1);
6681         }
6682       }
6683       return;
6684     }
6685     if (!strncmp(message, "tellall ", 8)) {
6686       if (appData.icsActive) {
6687         if (loggedOn) {
6688           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6689           SendToICS(buf1);
6690         }
6691       } else {
6692         DisplayNote(message + 8);
6693       }
6694       return;
6695     }
6696     if (strncmp(message, "warning", 7) == 0) {
6697         /* Undocumented feature, use tellusererror in new code */
6698         DisplayError(message, 0);
6699         return;
6700     }
6701     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6702         strcpy(realname, cps->tidy);
6703         strcat(realname, " query");
6704         AskQuestion(realname, buf2, buf1, cps->pr);
6705         return;
6706     }
6707     /* Commands from the engine directly to ICS.  We don't allow these to be 
6708      *  sent until we are logged on. Crafty kibitzes have been known to 
6709      *  interfere with the login process.
6710      */
6711     if (loggedOn) {
6712         if (!strncmp(message, "tellics ", 8)) {
6713             SendToICS(message + 8);
6714             SendToICS("\n");
6715             return;
6716         }
6717         if (!strncmp(message, "tellicsnoalias ", 15)) {
6718             SendToICS(ics_prefix);
6719             SendToICS(message + 15);
6720             SendToICS("\n");
6721             return;
6722         }
6723         /* The following are for backward compatibility only */
6724         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6725             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6726             SendToICS(ics_prefix);
6727             SendToICS(message);
6728             SendToICS("\n");
6729             return;
6730         }
6731     }
6732     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6733         return;
6734     }
6735     /*
6736      * If the move is illegal, cancel it and redraw the board.
6737      * Also deal with other error cases.  Matching is rather loose
6738      * here to accommodate engines written before the spec.
6739      */
6740     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6741         strncmp(message, "Error", 5) == 0) {
6742         if (StrStr(message, "name") || 
6743             StrStr(message, "rating") || StrStr(message, "?") ||
6744             StrStr(message, "result") || StrStr(message, "board") ||
6745             StrStr(message, "bk") || StrStr(message, "computer") ||
6746             StrStr(message, "variant") || StrStr(message, "hint") ||
6747             StrStr(message, "random") || StrStr(message, "depth") ||
6748             StrStr(message, "accepted")) {
6749             return;
6750         }
6751         if (StrStr(message, "protover")) {
6752           /* Program is responding to input, so it's apparently done
6753              initializing, and this error message indicates it is
6754              protocol version 1.  So we don't need to wait any longer
6755              for it to initialize and send feature commands. */
6756           FeatureDone(cps, 1);
6757           cps->protocolVersion = 1;
6758           return;
6759         }
6760         cps->maybeThinking = FALSE;
6761
6762         if (StrStr(message, "draw")) {
6763             /* Program doesn't have "draw" command */
6764             cps->sendDrawOffers = 0;
6765             return;
6766         }
6767         if (cps->sendTime != 1 &&
6768             (StrStr(message, "time") || StrStr(message, "otim"))) {
6769           /* Program apparently doesn't have "time" or "otim" command */
6770           cps->sendTime = 0;
6771           return;
6772         }
6773         if (StrStr(message, "analyze")) {
6774             cps->analysisSupport = FALSE;
6775             cps->analyzing = FALSE;
6776             Reset(FALSE, TRUE);
6777             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6778             DisplayError(buf2, 0);
6779             return;
6780         }
6781         if (StrStr(message, "(no matching move)st")) {
6782           /* Special kludge for GNU Chess 4 only */
6783           cps->stKludge = TRUE;
6784           SendTimeControl(cps, movesPerSession, timeControl,
6785                           timeIncrement, appData.searchDepth,
6786                           searchTime);
6787           return;
6788         }
6789         if (StrStr(message, "(no matching move)sd")) {
6790           /* Special kludge for GNU Chess 4 only */
6791           cps->sdKludge = TRUE;
6792           SendTimeControl(cps, movesPerSession, timeControl,
6793                           timeIncrement, appData.searchDepth,
6794                           searchTime);
6795           return;
6796         }
6797         if (!StrStr(message, "llegal")) {
6798             return;
6799         }
6800         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6801             gameMode == IcsIdle) return;
6802         if (forwardMostMove <= backwardMostMove) return;
6803         if (pausing) PauseEvent();
6804       if(appData.forceIllegal) {
6805             // [HGM] illegal: machine refused move; force position after move into it
6806           SendToProgram("force\n", cps);
6807           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6808                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6809                 // when black is to move, while there might be nothing on a2 or black
6810                 // might already have the move. So send the board as if white has the move.
6811                 // But first we must change the stm of the engine, as it refused the last move
6812                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6813                 if(WhiteOnMove(forwardMostMove)) {
6814                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6815                     SendBoard(cps, forwardMostMove); // kludgeless board
6816                 } else {
6817                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6818                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6819                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6820                 }
6821           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6822             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6823                  gameMode == TwoMachinesPlay)
6824               SendToProgram("go\n", cps);
6825             return;
6826       } else
6827         if (gameMode == PlayFromGameFile) {
6828             /* Stop reading this game file */
6829             gameMode = EditGame;
6830             ModeHighlight();
6831         }
6832         currentMove = --forwardMostMove;
6833         DisplayMove(currentMove-1); /* before DisplayMoveError */
6834         SwitchClocks();
6835         DisplayBothClocks();
6836         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6837                 parseList[currentMove], cps->which);
6838         DisplayMoveError(buf1);
6839         DrawPosition(FALSE, boards[currentMove]);
6840
6841         /* [HGM] illegal-move claim should forfeit game when Xboard */
6842         /* only passes fully legal moves                            */
6843         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6844             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6845                                 "False illegal-move claim", GE_XBOARD );
6846         }
6847         return;
6848     }
6849     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6850         /* Program has a broken "time" command that
6851            outputs a string not ending in newline.
6852            Don't use it. */
6853         cps->sendTime = 0;
6854     }
6855     
6856     /*
6857      * If chess program startup fails, exit with an error message.
6858      * Attempts to recover here are futile.
6859      */
6860     if ((StrStr(message, "unknown host") != NULL)
6861         || (StrStr(message, "No remote directory") != NULL)
6862         || (StrStr(message, "not found") != NULL)
6863         || (StrStr(message, "No such file") != NULL)
6864         || (StrStr(message, "can't alloc") != NULL)
6865         || (StrStr(message, "Permission denied") != NULL)) {
6866
6867         cps->maybeThinking = FALSE;
6868         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6869                 cps->which, cps->program, cps->host, message);
6870         RemoveInputSource(cps->isr);
6871         DisplayFatalError(buf1, 0, 1);
6872         return;
6873     }
6874     
6875     /* 
6876      * Look for hint output
6877      */
6878     if (sscanf(message, "Hint: %s", buf1) == 1) {
6879         if (cps == &first && hintRequested) {
6880             hintRequested = FALSE;
6881             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6882                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6883                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6884                                     PosFlags(forwardMostMove),
6885                                     fromY, fromX, toY, toX, promoChar, buf1);
6886                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6887                 DisplayInformation(buf2);
6888             } else {
6889                 /* Hint move could not be parsed!? */
6890               snprintf(buf2, sizeof(buf2),
6891                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6892                         buf1, cps->which);
6893                 DisplayError(buf2, 0);
6894             }
6895         } else {
6896             strcpy(lastHint, buf1);
6897         }
6898         return;
6899     }
6900
6901     /*
6902      * Ignore other messages if game is not in progress
6903      */
6904     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6905         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6906
6907     /*
6908      * look for win, lose, draw, or draw offer
6909      */
6910     if (strncmp(message, "1-0", 3) == 0) {
6911         char *p, *q, *r = "";
6912         p = strchr(message, '{');
6913         if (p) {
6914             q = strchr(p, '}');
6915             if (q) {
6916                 *q = NULLCHAR;
6917                 r = p + 1;
6918             }
6919         }
6920         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6921         return;
6922     } else if (strncmp(message, "0-1", 3) == 0) {
6923         char *p, *q, *r = "";
6924         p = strchr(message, '{');
6925         if (p) {
6926             q = strchr(p, '}');
6927             if (q) {
6928                 *q = NULLCHAR;
6929                 r = p + 1;
6930             }
6931         }
6932         /* Kludge for Arasan 4.1 bug */
6933         if (strcmp(r, "Black resigns") == 0) {
6934             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6935             return;
6936         }
6937         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6938         return;
6939     } else if (strncmp(message, "1/2", 3) == 0) {
6940         char *p, *q, *r = "";
6941         p = strchr(message, '{');
6942         if (p) {
6943             q = strchr(p, '}');
6944             if (q) {
6945                 *q = NULLCHAR;
6946                 r = p + 1;
6947             }
6948         }
6949             
6950         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6951         return;
6952
6953     } else if (strncmp(message, "White resign", 12) == 0) {
6954         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6955         return;
6956     } else if (strncmp(message, "Black resign", 12) == 0) {
6957         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6958         return;
6959     } else if (strncmp(message, "White matches", 13) == 0 ||
6960                strncmp(message, "Black matches", 13) == 0   ) {
6961         /* [HGM] ignore GNUShogi noises */
6962         return;
6963     } else if (strncmp(message, "White", 5) == 0 &&
6964                message[5] != '(' &&
6965                StrStr(message, "Black") == NULL) {
6966         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6967         return;
6968     } else if (strncmp(message, "Black", 5) == 0 &&
6969                message[5] != '(') {
6970         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6971         return;
6972     } else if (strcmp(message, "resign") == 0 ||
6973                strcmp(message, "computer resigns") == 0) {
6974         switch (gameMode) {
6975           case MachinePlaysBlack:
6976           case IcsPlayingBlack:
6977             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6978             break;
6979           case MachinePlaysWhite:
6980           case IcsPlayingWhite:
6981             GameEnds(BlackWins, "White resigns", GE_ENGINE);
6982             break;
6983           case TwoMachinesPlay:
6984             if (cps->twoMachinesColor[0] == 'w')
6985               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6986             else
6987               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6988             break;
6989           default:
6990             /* can't happen */
6991             break;
6992         }
6993         return;
6994     } else if (strncmp(message, "opponent mates", 14) == 0) {
6995         switch (gameMode) {
6996           case MachinePlaysBlack:
6997           case IcsPlayingBlack:
6998             GameEnds(WhiteWins, "White mates", GE_ENGINE);
6999             break;
7000           case MachinePlaysWhite:
7001           case IcsPlayingWhite:
7002             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7003             break;
7004           case TwoMachinesPlay:
7005             if (cps->twoMachinesColor[0] == 'w')
7006               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7007             else
7008               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7009             break;
7010           default:
7011             /* can't happen */
7012             break;
7013         }
7014         return;
7015     } else if (strncmp(message, "computer mates", 14) == 0) {
7016         switch (gameMode) {
7017           case MachinePlaysBlack:
7018           case IcsPlayingBlack:
7019             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7020             break;
7021           case MachinePlaysWhite:
7022           case IcsPlayingWhite:
7023             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7024             break;
7025           case TwoMachinesPlay:
7026             if (cps->twoMachinesColor[0] == 'w')
7027               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7028             else
7029               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7030             break;
7031           default:
7032             /* can't happen */
7033             break;
7034         }
7035         return;
7036     } else if (strncmp(message, "checkmate", 9) == 0) {
7037         if (WhiteOnMove(forwardMostMove)) {
7038             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7039         } else {
7040             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7041         }
7042         return;
7043     } else if (strstr(message, "Draw") != NULL ||
7044                strstr(message, "game is a draw") != NULL) {
7045         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7046         return;
7047     } else if (strstr(message, "offer") != NULL &&
7048                strstr(message, "draw") != NULL) {
7049 #if ZIPPY
7050         if (appData.zippyPlay && first.initDone) {
7051             /* Relay offer to ICS */
7052             SendToICS(ics_prefix);
7053             SendToICS("draw\n");
7054         }
7055 #endif
7056         cps->offeredDraw = 2; /* valid until this engine moves twice */
7057         if (gameMode == TwoMachinesPlay) {
7058             if (cps->other->offeredDraw) {
7059                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7060             /* [HGM] in two-machine mode we delay relaying draw offer      */
7061             /* until after we also have move, to see if it is really claim */
7062             }
7063         } else if (gameMode == MachinePlaysWhite ||
7064                    gameMode == MachinePlaysBlack) {
7065           if (userOfferedDraw) {
7066             DisplayInformation(_("Machine accepts your draw offer"));
7067             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7068           } else {
7069             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7070           }
7071         }
7072     }
7073
7074     
7075     /*
7076      * Look for thinking output
7077      */
7078     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7079           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7080                                 ) {
7081         int plylev, mvleft, mvtot, curscore, time;
7082         char mvname[MOVE_LEN];
7083         u64 nodes; // [DM]
7084         char plyext;
7085         int ignore = FALSE;
7086         int prefixHint = FALSE;
7087         mvname[0] = NULLCHAR;
7088
7089         switch (gameMode) {
7090           case MachinePlaysBlack:
7091           case IcsPlayingBlack:
7092             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7093             break;
7094           case MachinePlaysWhite:
7095           case IcsPlayingWhite:
7096             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7097             break;
7098           case AnalyzeMode:
7099           case AnalyzeFile:
7100             break;
7101           case IcsObserving: /* [DM] icsEngineAnalyze */
7102             if (!appData.icsEngineAnalyze) ignore = TRUE;
7103             break;
7104           case TwoMachinesPlay:
7105             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7106                 ignore = TRUE;
7107             }
7108             break;
7109           default:
7110             ignore = TRUE;
7111             break;
7112         }
7113
7114         if (!ignore) {
7115             buf1[0] = NULLCHAR;
7116             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7117                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7118
7119                 if (plyext != ' ' && plyext != '\t') {
7120                     time *= 100;
7121                 }
7122
7123                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7124                 if( cps->scoreIsAbsolute && 
7125                     ( gameMode == MachinePlaysBlack ||
7126                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7127                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7128                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7129                      !WhiteOnMove(currentMove)
7130                     ) )
7131                 {
7132                     curscore = -curscore;
7133                 }
7134
7135
7136                 programStats.depth = plylev;
7137                 programStats.nodes = nodes;
7138                 programStats.time = time;
7139                 programStats.score = curscore;
7140                 programStats.got_only_move = 0;
7141
7142                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7143                         int ticklen;
7144
7145                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7146                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7147                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7148                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7149                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7150                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7151                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7152                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7153                 }
7154
7155                 /* Buffer overflow protection */
7156                 if (buf1[0] != NULLCHAR) {
7157                     if (strlen(buf1) >= sizeof(programStats.movelist)
7158                         && appData.debugMode) {
7159                         fprintf(debugFP,
7160                                 "PV is too long; using the first %u bytes.\n",
7161                                 (unsigned) sizeof(programStats.movelist) - 1);
7162                     }
7163
7164                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7165                 } else {
7166                     sprintf(programStats.movelist, " no PV\n");
7167                 }
7168
7169                 if (programStats.seen_stat) {
7170                     programStats.ok_to_send = 1;
7171                 }
7172
7173                 if (strchr(programStats.movelist, '(') != NULL) {
7174                     programStats.line_is_book = 1;
7175                     programStats.nr_moves = 0;
7176                     programStats.moves_left = 0;
7177                 } else {
7178                     programStats.line_is_book = 0;
7179                 }
7180
7181                 SendProgramStatsToFrontend( cps, &programStats );
7182
7183                 /* 
7184                     [AS] Protect the thinkOutput buffer from overflow... this
7185                     is only useful if buf1 hasn't overflowed first!
7186                 */
7187                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7188                         plylev, 
7189                         (gameMode == TwoMachinesPlay ?
7190                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7191                         ((double) curscore) / 100.0,
7192                         prefixHint ? lastHint : "",
7193                         prefixHint ? " " : "" );
7194
7195                 if( buf1[0] != NULLCHAR ) {
7196                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7197
7198                     if( strlen(buf1) > max_len ) {
7199                         if( appData.debugMode) {
7200                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7201                         }
7202                         buf1[max_len+1] = '\0';
7203                     }
7204
7205                     strcat( thinkOutput, buf1 );
7206                 }
7207
7208                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7209                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7210                     DisplayMove(currentMove - 1);
7211                 }
7212                 return;
7213
7214             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7215                 /* crafty (9.25+) says "(only move) <move>"
7216                  * if there is only 1 legal move
7217                  */
7218                 sscanf(p, "(only move) %s", buf1);
7219                 sprintf(thinkOutput, "%s (only move)", buf1);
7220                 sprintf(programStats.movelist, "%s (only move)", buf1);
7221                 programStats.depth = 1;
7222                 programStats.nr_moves = 1;
7223                 programStats.moves_left = 1;
7224                 programStats.nodes = 1;
7225                 programStats.time = 1;
7226                 programStats.got_only_move = 1;
7227
7228                 /* Not really, but we also use this member to
7229                    mean "line isn't going to change" (Crafty
7230                    isn't searching, so stats won't change) */
7231                 programStats.line_is_book = 1;
7232
7233                 SendProgramStatsToFrontend( cps, &programStats );
7234                 
7235                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7236                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7237                     DisplayMove(currentMove - 1);
7238                 }
7239                 return;
7240             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7241                               &time, &nodes, &plylev, &mvleft,
7242                               &mvtot, mvname) >= 5) {
7243                 /* The stat01: line is from Crafty (9.29+) in response
7244                    to the "." command */
7245                 programStats.seen_stat = 1;
7246                 cps->maybeThinking = TRUE;
7247
7248                 if (programStats.got_only_move || !appData.periodicUpdates)
7249                   return;
7250
7251                 programStats.depth = plylev;
7252                 programStats.time = time;
7253                 programStats.nodes = nodes;
7254                 programStats.moves_left = mvleft;
7255                 programStats.nr_moves = mvtot;
7256                 strcpy(programStats.move_name, mvname);
7257                 programStats.ok_to_send = 1;
7258                 programStats.movelist[0] = '\0';
7259
7260                 SendProgramStatsToFrontend( cps, &programStats );
7261
7262                 return;
7263
7264             } else if (strncmp(message,"++",2) == 0) {
7265                 /* Crafty 9.29+ outputs this */
7266                 programStats.got_fail = 2;
7267                 return;
7268
7269             } else if (strncmp(message,"--",2) == 0) {
7270                 /* Crafty 9.29+ outputs this */
7271                 programStats.got_fail = 1;
7272                 return;
7273
7274             } else if (thinkOutput[0] != NULLCHAR &&
7275                        strncmp(message, "    ", 4) == 0) {
7276                 unsigned message_len;
7277
7278                 p = message;
7279                 while (*p && *p == ' ') p++;
7280
7281                 message_len = strlen( p );
7282
7283                 /* [AS] Avoid buffer overflow */
7284                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7285                     strcat(thinkOutput, " ");
7286                     strcat(thinkOutput, p);
7287                 }
7288
7289                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7290                     strcat(programStats.movelist, " ");
7291                     strcat(programStats.movelist, p);
7292                 }
7293
7294                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7295                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7296                     DisplayMove(currentMove - 1);
7297                 }
7298                 return;
7299             }
7300         }
7301         else {
7302             buf1[0] = NULLCHAR;
7303
7304             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7305                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7306             {
7307                 ChessProgramStats cpstats;
7308
7309                 if (plyext != ' ' && plyext != '\t') {
7310                     time *= 100;
7311                 }
7312
7313                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7314                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7315                     curscore = -curscore;
7316                 }
7317
7318                 cpstats.depth = plylev;
7319                 cpstats.nodes = nodes;
7320                 cpstats.time = time;
7321                 cpstats.score = curscore;
7322                 cpstats.got_only_move = 0;
7323                 cpstats.movelist[0] = '\0';
7324
7325                 if (buf1[0] != NULLCHAR) {
7326                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7327                 }
7328
7329                 cpstats.ok_to_send = 0;
7330                 cpstats.line_is_book = 0;
7331                 cpstats.nr_moves = 0;
7332                 cpstats.moves_left = 0;
7333
7334                 SendProgramStatsToFrontend( cps, &cpstats );
7335             }
7336         }
7337     }
7338 }
7339
7340
7341 /* Parse a game score from the character string "game", and
7342    record it as the history of the current game.  The game
7343    score is NOT assumed to start from the standard position. 
7344    The display is not updated in any way.
7345    */
7346 void
7347 ParseGameHistory(game)
7348      char *game;
7349 {
7350     ChessMove moveType;
7351     int fromX, fromY, toX, toY, boardIndex;
7352     char promoChar;
7353     char *p, *q;
7354     char buf[MSG_SIZ];
7355
7356     if (appData.debugMode)
7357       fprintf(debugFP, "Parsing game history: %s\n", game);
7358
7359     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7360     gameInfo.site = StrSave(appData.icsHost);
7361     gameInfo.date = PGNDate();
7362     gameInfo.round = StrSave("-");
7363
7364     /* Parse out names of players */
7365     while (*game == ' ') game++;
7366     p = buf;
7367     while (*game != ' ') *p++ = *game++;
7368     *p = NULLCHAR;
7369     gameInfo.white = StrSave(buf);
7370     while (*game == ' ') game++;
7371     p = buf;
7372     while (*game != ' ' && *game != '\n') *p++ = *game++;
7373     *p = NULLCHAR;
7374     gameInfo.black = StrSave(buf);
7375
7376     /* Parse moves */
7377     boardIndex = blackPlaysFirst ? 1 : 0;
7378     yynewstr(game);
7379     for (;;) {
7380         yyboardindex = boardIndex;
7381         moveType = (ChessMove) yylex();
7382         switch (moveType) {
7383           case IllegalMove:             /* maybe suicide chess, etc. */
7384   if (appData.debugMode) {
7385     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7386     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7387     setbuf(debugFP, NULL);
7388   }
7389           case WhitePromotionChancellor:
7390           case BlackPromotionChancellor:
7391           case WhitePromotionArchbishop:
7392           case BlackPromotionArchbishop:
7393           case WhitePromotionQueen:
7394           case BlackPromotionQueen:
7395           case WhitePromotionRook:
7396           case BlackPromotionRook:
7397           case WhitePromotionBishop:
7398           case BlackPromotionBishop:
7399           case WhitePromotionKnight:
7400           case BlackPromotionKnight:
7401           case WhitePromotionKing:
7402           case BlackPromotionKing:
7403           case NormalMove:
7404           case WhiteCapturesEnPassant:
7405           case BlackCapturesEnPassant:
7406           case WhiteKingSideCastle:
7407           case WhiteQueenSideCastle:
7408           case BlackKingSideCastle:
7409           case BlackQueenSideCastle:
7410           case WhiteKingSideCastleWild:
7411           case WhiteQueenSideCastleWild:
7412           case BlackKingSideCastleWild:
7413           case BlackQueenSideCastleWild:
7414           /* PUSH Fabien */
7415           case WhiteHSideCastleFR:
7416           case WhiteASideCastleFR:
7417           case BlackHSideCastleFR:
7418           case BlackASideCastleFR:
7419           /* POP Fabien */
7420             fromX = currentMoveString[0] - AAA;
7421             fromY = currentMoveString[1] - ONE;
7422             toX = currentMoveString[2] - AAA;
7423             toY = currentMoveString[3] - ONE;
7424             promoChar = currentMoveString[4];
7425             break;
7426           case WhiteDrop:
7427           case BlackDrop:
7428             fromX = moveType == WhiteDrop ?
7429               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7430             (int) CharToPiece(ToLower(currentMoveString[0]));
7431             fromY = DROP_RANK;
7432             toX = currentMoveString[2] - AAA;
7433             toY = currentMoveString[3] - ONE;
7434             promoChar = NULLCHAR;
7435             break;
7436           case AmbiguousMove:
7437             /* bug? */
7438             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7439   if (appData.debugMode) {
7440     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7441     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7442     setbuf(debugFP, NULL);
7443   }
7444             DisplayError(buf, 0);
7445             return;
7446           case ImpossibleMove:
7447             /* bug? */
7448             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7449   if (appData.debugMode) {
7450     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7451     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7452     setbuf(debugFP, NULL);
7453   }
7454             DisplayError(buf, 0);
7455             return;
7456           case (ChessMove) 0:   /* end of file */
7457             if (boardIndex < backwardMostMove) {
7458                 /* Oops, gap.  How did that happen? */
7459                 DisplayError(_("Gap in move list"), 0);
7460                 return;
7461             }
7462             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7463             if (boardIndex > forwardMostMove) {
7464                 forwardMostMove = boardIndex;
7465             }
7466             return;
7467           case ElapsedTime:
7468             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7469                 strcat(parseList[boardIndex-1], " ");
7470                 strcat(parseList[boardIndex-1], yy_text);
7471             }
7472             continue;
7473           case Comment:
7474           case PGNTag:
7475           case NAG:
7476           default:
7477             /* ignore */
7478             continue;
7479           case WhiteWins:
7480           case BlackWins:
7481           case GameIsDrawn:
7482           case GameUnfinished:
7483             if (gameMode == IcsExamining) {
7484                 if (boardIndex < backwardMostMove) {
7485                     /* Oops, gap.  How did that happen? */
7486                     return;
7487                 }
7488                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7489                 return;
7490             }
7491             gameInfo.result = moveType;
7492             p = strchr(yy_text, '{');
7493             if (p == NULL) p = strchr(yy_text, '(');
7494             if (p == NULL) {
7495                 p = yy_text;
7496                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7497             } else {
7498                 q = strchr(p, *p == '{' ? '}' : ')');
7499                 if (q != NULL) *q = NULLCHAR;
7500                 p++;
7501             }
7502             gameInfo.resultDetails = StrSave(p);
7503             continue;
7504         }
7505         if (boardIndex >= forwardMostMove &&
7506             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7507             backwardMostMove = blackPlaysFirst ? 1 : 0;
7508             return;
7509         }
7510         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7511                                  fromY, fromX, toY, toX, promoChar,
7512                                  parseList[boardIndex]);
7513         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7514         /* currentMoveString is set as a side-effect of yylex */
7515         strcpy(moveList[boardIndex], currentMoveString);
7516         strcat(moveList[boardIndex], "\n");
7517         boardIndex++;
7518         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7519         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7520           case MT_NONE:
7521           case MT_STALEMATE:
7522           default:
7523             break;
7524           case MT_CHECK:
7525             if(gameInfo.variant != VariantShogi)
7526                 strcat(parseList[boardIndex - 1], "+");
7527             break;
7528           case MT_CHECKMATE:
7529           case MT_STAINMATE:
7530             strcat(parseList[boardIndex - 1], "#");
7531             break;
7532         }
7533     }
7534 }
7535
7536
7537 /* Apply a move to the given board  */
7538 void
7539 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7540      int fromX, fromY, toX, toY;
7541      int promoChar;
7542      Board board;
7543 {
7544   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7545
7546     /* [HGM] compute & store e.p. status and castling rights for new position */
7547     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7548     { int i;
7549
7550       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7551       oldEP = (signed char)board[EP_STATUS];
7552       board[EP_STATUS] = EP_NONE;
7553
7554       if( board[toY][toX] != EmptySquare ) 
7555            board[EP_STATUS] = EP_CAPTURE;  
7556
7557       if( board[fromY][fromX] == WhitePawn ) {
7558            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7559                board[EP_STATUS] = EP_PAWN_MOVE;
7560            if( toY-fromY==2) {
7561                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7562                         gameInfo.variant != VariantBerolina || toX < fromX)
7563                       board[EP_STATUS] = toX | berolina;
7564                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7565                         gameInfo.variant != VariantBerolina || toX > fromX) 
7566                       board[EP_STATUS] = toX;
7567            }
7568       } else 
7569       if( board[fromY][fromX] == BlackPawn ) {
7570            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7571                board[EP_STATUS] = EP_PAWN_MOVE; 
7572            if( toY-fromY== -2) {
7573                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7574                         gameInfo.variant != VariantBerolina || toX < fromX)
7575                       board[EP_STATUS] = toX | berolina;
7576                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7577                         gameInfo.variant != VariantBerolina || toX > fromX) 
7578                       board[EP_STATUS] = toX;
7579            }
7580        }
7581
7582        for(i=0; i<nrCastlingRights; i++) {
7583            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7584               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7585              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7586        }
7587
7588     }
7589
7590   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7591   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7592        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7593          
7594   if (fromX == toX && fromY == toY) return;
7595
7596   if (fromY == DROP_RANK) {
7597         /* must be first */
7598         piece = board[toY][toX] = (ChessSquare) fromX;
7599   } else {
7600      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7601      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7602      if(gameInfo.variant == VariantKnightmate)
7603          king += (int) WhiteUnicorn - (int) WhiteKing;
7604
7605     /* Code added by Tord: */
7606     /* FRC castling assumed when king captures friendly rook. */
7607     if (board[fromY][fromX] == WhiteKing &&
7608              board[toY][toX] == WhiteRook) {
7609       board[fromY][fromX] = EmptySquare;
7610       board[toY][toX] = EmptySquare;
7611       if(toX > fromX) {
7612         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7613       } else {
7614         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7615       }
7616     } else if (board[fromY][fromX] == BlackKing &&
7617                board[toY][toX] == BlackRook) {
7618       board[fromY][fromX] = EmptySquare;
7619       board[toY][toX] = EmptySquare;
7620       if(toX > fromX) {
7621         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7622       } else {
7623         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7624       }
7625     /* End of code added by Tord */
7626
7627     } else if (board[fromY][fromX] == king
7628         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7629         && toY == fromY && toX > fromX+1) {
7630         board[fromY][fromX] = EmptySquare;
7631         board[toY][toX] = king;
7632         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7633         board[fromY][BOARD_RGHT-1] = EmptySquare;
7634     } else if (board[fromY][fromX] == king
7635         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7636                && toY == fromY && toX < fromX-1) {
7637         board[fromY][fromX] = EmptySquare;
7638         board[toY][toX] = king;
7639         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7640         board[fromY][BOARD_LEFT] = EmptySquare;
7641     } else if (board[fromY][fromX] == WhitePawn
7642                && toY == BOARD_HEIGHT-1
7643                && gameInfo.variant != VariantXiangqi
7644                ) {
7645         /* white pawn promotion */
7646         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7647         if (board[toY][toX] == EmptySquare) {
7648             board[toY][toX] = WhiteQueen;
7649         }
7650         if(gameInfo.variant==VariantBughouse ||
7651            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7652             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7653         board[fromY][fromX] = EmptySquare;
7654     } else if ((fromY == BOARD_HEIGHT-4)
7655                && (toX != fromX)
7656                && gameInfo.variant != VariantXiangqi
7657                && gameInfo.variant != VariantBerolina
7658                && (board[fromY][fromX] == WhitePawn)
7659                && (board[toY][toX] == EmptySquare)) {
7660         board[fromY][fromX] = EmptySquare;
7661         board[toY][toX] = WhitePawn;
7662         captured = board[toY - 1][toX];
7663         board[toY - 1][toX] = EmptySquare;
7664     } else if ((fromY == BOARD_HEIGHT-4)
7665                && (toX == fromX)
7666                && gameInfo.variant == VariantBerolina
7667                && (board[fromY][fromX] == WhitePawn)
7668                && (board[toY][toX] == EmptySquare)) {
7669         board[fromY][fromX] = EmptySquare;
7670         board[toY][toX] = WhitePawn;
7671         if(oldEP & EP_BEROLIN_A) {
7672                 captured = board[fromY][fromX-1];
7673                 board[fromY][fromX-1] = EmptySquare;
7674         }else{  captured = board[fromY][fromX+1];
7675                 board[fromY][fromX+1] = EmptySquare;
7676         }
7677     } else if (board[fromY][fromX] == king
7678         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7679                && toY == fromY && toX > fromX+1) {
7680         board[fromY][fromX] = EmptySquare;
7681         board[toY][toX] = king;
7682         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7683         board[fromY][BOARD_RGHT-1] = EmptySquare;
7684     } else if (board[fromY][fromX] == king
7685         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7686                && toY == fromY && toX < fromX-1) {
7687         board[fromY][fromX] = EmptySquare;
7688         board[toY][toX] = king;
7689         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7690         board[fromY][BOARD_LEFT] = EmptySquare;
7691     } else if (fromY == 7 && fromX == 3
7692                && board[fromY][fromX] == BlackKing
7693                && toY == 7 && toX == 5) {
7694         board[fromY][fromX] = EmptySquare;
7695         board[toY][toX] = BlackKing;
7696         board[fromY][7] = EmptySquare;
7697         board[toY][4] = BlackRook;
7698     } else if (fromY == 7 && fromX == 3
7699                && board[fromY][fromX] == BlackKing
7700                && toY == 7 && toX == 1) {
7701         board[fromY][fromX] = EmptySquare;
7702         board[toY][toX] = BlackKing;
7703         board[fromY][0] = EmptySquare;
7704         board[toY][2] = BlackRook;
7705     } else if (board[fromY][fromX] == BlackPawn
7706                && toY == 0
7707                && gameInfo.variant != VariantXiangqi
7708                ) {
7709         /* black pawn promotion */
7710         board[0][toX] = CharToPiece(ToLower(promoChar));
7711         if (board[0][toX] == EmptySquare) {
7712             board[0][toX] = BlackQueen;
7713         }
7714         if(gameInfo.variant==VariantBughouse ||
7715            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7716             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7717         board[fromY][fromX] = EmptySquare;
7718     } else if ((fromY == 3)
7719                && (toX != fromX)
7720                && gameInfo.variant != VariantXiangqi
7721                && gameInfo.variant != VariantBerolina
7722                && (board[fromY][fromX] == BlackPawn)
7723                && (board[toY][toX] == EmptySquare)) {
7724         board[fromY][fromX] = EmptySquare;
7725         board[toY][toX] = BlackPawn;
7726         captured = board[toY + 1][toX];
7727         board[toY + 1][toX] = EmptySquare;
7728     } else if ((fromY == 3)
7729                && (toX == fromX)
7730                && gameInfo.variant == VariantBerolina
7731                && (board[fromY][fromX] == BlackPawn)
7732                && (board[toY][toX] == EmptySquare)) {
7733         board[fromY][fromX] = EmptySquare;
7734         board[toY][toX] = BlackPawn;
7735         if(oldEP & EP_BEROLIN_A) {
7736                 captured = board[fromY][fromX-1];
7737                 board[fromY][fromX-1] = EmptySquare;
7738         }else{  captured = board[fromY][fromX+1];
7739                 board[fromY][fromX+1] = EmptySquare;
7740         }
7741     } else {
7742         board[toY][toX] = board[fromY][fromX];
7743         board[fromY][fromX] = EmptySquare;
7744     }
7745
7746     /* [HGM] now we promote for Shogi, if needed */
7747     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7748         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7749   }
7750
7751     if (gameInfo.holdingsWidth != 0) {
7752
7753       /* !!A lot more code needs to be written to support holdings  */
7754       /* [HGM] OK, so I have written it. Holdings are stored in the */
7755       /* penultimate board files, so they are automaticlly stored   */
7756       /* in the game history.                                       */
7757       if (fromY == DROP_RANK) {
7758         /* Delete from holdings, by decreasing count */
7759         /* and erasing image if necessary            */
7760         p = (int) fromX;
7761         if(p < (int) BlackPawn) { /* white drop */
7762              p -= (int)WhitePawn;
7763                  p = PieceToNumber((ChessSquare)p);
7764              if(p >= gameInfo.holdingsSize) p = 0;
7765              if(--board[p][BOARD_WIDTH-2] <= 0)
7766                   board[p][BOARD_WIDTH-1] = EmptySquare;
7767              if((int)board[p][BOARD_WIDTH-2] < 0)
7768                         board[p][BOARD_WIDTH-2] = 0;
7769         } else {                  /* black drop */
7770              p -= (int)BlackPawn;
7771                  p = PieceToNumber((ChessSquare)p);
7772              if(p >= gameInfo.holdingsSize) p = 0;
7773              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7774                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7775              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7776                         board[BOARD_HEIGHT-1-p][1] = 0;
7777         }
7778       }
7779       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7780           && gameInfo.variant != VariantBughouse        ) {
7781         /* [HGM] holdings: Add to holdings, if holdings exist */
7782         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7783                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7784                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7785         }
7786         p = (int) captured;
7787         if (p >= (int) BlackPawn) {
7788           p -= (int)BlackPawn;
7789           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7790                   /* in Shogi restore piece to its original  first */
7791                   captured = (ChessSquare) (DEMOTED captured);
7792                   p = DEMOTED p;
7793           }
7794           p = PieceToNumber((ChessSquare)p);
7795           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7796           board[p][BOARD_WIDTH-2]++;
7797           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7798         } else {
7799           p -= (int)WhitePawn;
7800           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7801                   captured = (ChessSquare) (DEMOTED captured);
7802                   p = DEMOTED p;
7803           }
7804           p = PieceToNumber((ChessSquare)p);
7805           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7806           board[BOARD_HEIGHT-1-p][1]++;
7807           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7808         }
7809       }
7810     } else if (gameInfo.variant == VariantAtomic) {
7811       if (captured != EmptySquare) {
7812         int y, x;
7813         for (y = toY-1; y <= toY+1; y++) {
7814           for (x = toX-1; x <= toX+1; x++) {
7815             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7816                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7817               board[y][x] = EmptySquare;
7818             }
7819           }
7820         }
7821         board[toY][toX] = EmptySquare;
7822       }
7823     }
7824     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7825         /* [HGM] Shogi promotions */
7826         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7827     }
7828
7829     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7830                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7831         // [HGM] superchess: take promotion piece out of holdings
7832         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7833         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7834             if(!--board[k][BOARD_WIDTH-2])
7835                 board[k][BOARD_WIDTH-1] = EmptySquare;
7836         } else {
7837             if(!--board[BOARD_HEIGHT-1-k][1])
7838                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7839         }
7840     }
7841
7842 }
7843
7844 /* Updates forwardMostMove */
7845 void
7846 MakeMove(fromX, fromY, toX, toY, promoChar)
7847      int fromX, fromY, toX, toY;
7848      int promoChar;
7849 {
7850 //    forwardMostMove++; // [HGM] bare: moved downstream
7851
7852     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7853         int timeLeft; static int lastLoadFlag=0; int king, piece;
7854         piece = boards[forwardMostMove][fromY][fromX];
7855         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7856         if(gameInfo.variant == VariantKnightmate)
7857             king += (int) WhiteUnicorn - (int) WhiteKing;
7858         if(forwardMostMove == 0) {
7859             if(blackPlaysFirst) 
7860                 fprintf(serverMoves, "%s;", second.tidy);
7861             fprintf(serverMoves, "%s;", first.tidy);
7862             if(!blackPlaysFirst) 
7863                 fprintf(serverMoves, "%s;", second.tidy);
7864         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7865         lastLoadFlag = loadFlag;
7866         // print base move
7867         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7868         // print castling suffix
7869         if( toY == fromY && piece == king ) {
7870             if(toX-fromX > 1)
7871                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7872             if(fromX-toX >1)
7873                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7874         }
7875         // e.p. suffix
7876         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7877              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7878              boards[forwardMostMove][toY][toX] == EmptySquare
7879              && fromX != toX )
7880                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7881         // promotion suffix
7882         if(promoChar != NULLCHAR)
7883                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7884         if(!loadFlag) {
7885             fprintf(serverMoves, "/%d/%d",
7886                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7887             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7888             else                      timeLeft = blackTimeRemaining/1000;
7889             fprintf(serverMoves, "/%d", timeLeft);
7890         }
7891         fflush(serverMoves);
7892     }
7893
7894     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7895       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7896                         0, 1);
7897       return;
7898     }
7899     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7900     if (commentList[forwardMostMove+1] != NULL) {
7901         free(commentList[forwardMostMove+1]);
7902         commentList[forwardMostMove+1] = NULL;
7903     }
7904     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7905     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7906     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7907     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7908     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7909     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7910     gameInfo.result = GameUnfinished;
7911     if (gameInfo.resultDetails != NULL) {
7912         free(gameInfo.resultDetails);
7913         gameInfo.resultDetails = NULL;
7914     }
7915     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7916                               moveList[forwardMostMove - 1]);
7917     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7918                              PosFlags(forwardMostMove - 1),
7919                              fromY, fromX, toY, toX, promoChar,
7920                              parseList[forwardMostMove - 1]);
7921     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7922       case MT_NONE:
7923       case MT_STALEMATE:
7924       default:
7925         break;
7926       case MT_CHECK:
7927         if(gameInfo.variant != VariantShogi)
7928             strcat(parseList[forwardMostMove - 1], "+");
7929         break;
7930       case MT_CHECKMATE:
7931       case MT_STAINMATE:
7932         strcat(parseList[forwardMostMove - 1], "#");
7933         break;
7934     }
7935     if (appData.debugMode) {
7936         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7937     }
7938
7939 }
7940
7941 /* Updates currentMove if not pausing */
7942 void
7943 ShowMove(fromX, fromY, toX, toY)
7944 {
7945     int instant = (gameMode == PlayFromGameFile) ?
7946         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7947     if(appData.noGUI) return;
7948     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7949         if (!instant) {
7950             if (forwardMostMove == currentMove + 1) {
7951                 AnimateMove(boards[forwardMostMove - 1],
7952                             fromX, fromY, toX, toY);
7953             }
7954             if (appData.highlightLastMove) {
7955                 SetHighlights(fromX, fromY, toX, toY);
7956             }
7957         }
7958         currentMove = forwardMostMove;
7959     }
7960
7961     if (instant) return;
7962
7963     DisplayMove(currentMove - 1);
7964     DrawPosition(FALSE, boards[currentMove]);
7965     DisplayBothClocks();
7966     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7967 }
7968
7969 void SendEgtPath(ChessProgramState *cps)
7970 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7971         char buf[MSG_SIZ], name[MSG_SIZ], *p;
7972
7973         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7974
7975         while(*p) {
7976             char c, *q = name+1, *r, *s;
7977
7978             name[0] = ','; // extract next format name from feature and copy with prefixed ','
7979             while(*p && *p != ',') *q++ = *p++;
7980             *q++ = ':'; *q = 0;
7981             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
7982                 strcmp(name, ",nalimov:") == 0 ) {
7983                 // take nalimov path from the menu-changeable option first, if it is defined
7984                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7985                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
7986             } else
7987             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7988                 (s = StrStr(appData.egtFormats, name)) != NULL) {
7989                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7990                 s = r = StrStr(s, ":") + 1; // beginning of path info
7991                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7992                 c = *r; *r = 0;             // temporarily null-terminate path info
7993                     *--q = 0;               // strip of trailig ':' from name
7994                     sprintf(buf, "egtpath %s %s\n", name+1, s);
7995                 *r = c;
7996                 SendToProgram(buf,cps);     // send egtbpath command for this format
7997             }
7998             if(*p == ',') p++; // read away comma to position for next format name
7999         }
8000 }
8001
8002 void
8003 InitChessProgram(cps, setup)
8004      ChessProgramState *cps;
8005      int setup; /* [HGM] needed to setup FRC opening position */
8006 {
8007     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8008     if (appData.noChessProgram) return;
8009     hintRequested = FALSE;
8010     bookRequested = FALSE;
8011
8012     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8013     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8014     if(cps->memSize) { /* [HGM] memory */
8015         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8016         SendToProgram(buf, cps);
8017     }
8018     SendEgtPath(cps); /* [HGM] EGT */
8019     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8020         sprintf(buf, "cores %d\n", appData.smpCores);
8021         SendToProgram(buf, cps);
8022     }
8023
8024     SendToProgram(cps->initString, cps);
8025     if (gameInfo.variant != VariantNormal &&
8026         gameInfo.variant != VariantLoadable
8027         /* [HGM] also send variant if board size non-standard */
8028         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8029                                             ) {
8030       char *v = VariantName(gameInfo.variant);
8031       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8032         /* [HGM] in protocol 1 we have to assume all variants valid */
8033         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8034         DisplayFatalError(buf, 0, 1);
8035         return;
8036       }
8037
8038       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8039       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8040       if( gameInfo.variant == VariantXiangqi )
8041            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8042       if( gameInfo.variant == VariantShogi )
8043            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8044       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8045            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8046       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8047                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8048            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8049       if( gameInfo.variant == VariantCourier )
8050            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8051       if( gameInfo.variant == VariantSuper )
8052            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8053       if( gameInfo.variant == VariantGreat )
8054            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8055
8056       if(overruled) {
8057            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8058                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8059            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8060            if(StrStr(cps->variants, b) == NULL) { 
8061                // specific sized variant not known, check if general sizing allowed
8062                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8063                    if(StrStr(cps->variants, "boardsize") == NULL) {
8064                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8065                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8066                        DisplayFatalError(buf, 0, 1);
8067                        return;
8068                    }
8069                    /* [HGM] here we really should compare with the maximum supported board size */
8070                }
8071            }
8072       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8073       sprintf(buf, "variant %s\n", b);
8074       SendToProgram(buf, cps);
8075     }
8076     currentlyInitializedVariant = gameInfo.variant;
8077
8078     /* [HGM] send opening position in FRC to first engine */
8079     if(setup) {
8080           SendToProgram("force\n", cps);
8081           SendBoard(cps, 0);
8082           /* engine is now in force mode! Set flag to wake it up after first move. */
8083           setboardSpoiledMachineBlack = 1;
8084     }
8085
8086     if (cps->sendICS) {
8087       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8088       SendToProgram(buf, cps);
8089     }
8090     cps->maybeThinking = FALSE;
8091     cps->offeredDraw = 0;
8092     if (!appData.icsActive) {
8093         SendTimeControl(cps, movesPerSession, timeControl,
8094                         timeIncrement, appData.searchDepth,
8095                         searchTime);
8096     }
8097     if (appData.showThinking 
8098         // [HGM] thinking: four options require thinking output to be sent
8099         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8100                                 ) {
8101         SendToProgram("post\n", cps);
8102     }
8103     SendToProgram("hard\n", cps);
8104     if (!appData.ponderNextMove) {
8105         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8106            it without being sure what state we are in first.  "hard"
8107            is not a toggle, so that one is OK.
8108          */
8109         SendToProgram("easy\n", cps);
8110     }
8111     if (cps->usePing) {
8112       sprintf(buf, "ping %d\n", ++cps->lastPing);
8113       SendToProgram(buf, cps);
8114     }
8115     cps->initDone = TRUE;
8116 }   
8117
8118
8119 void
8120 StartChessProgram(cps)
8121      ChessProgramState *cps;
8122 {
8123     char buf[MSG_SIZ];
8124     int err;
8125
8126     if (appData.noChessProgram) return;
8127     cps->initDone = FALSE;
8128
8129     if (strcmp(cps->host, "localhost") == 0) {
8130         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8131     } else if (*appData.remoteShell == NULLCHAR) {
8132         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8133     } else {
8134         if (*appData.remoteUser == NULLCHAR) {
8135           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8136                     cps->program);
8137         } else {
8138           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8139                     cps->host, appData.remoteUser, cps->program);
8140         }
8141         err = StartChildProcess(buf, "", &cps->pr);
8142     }
8143     
8144     if (err != 0) {
8145         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8146         DisplayFatalError(buf, err, 1);
8147         cps->pr = NoProc;
8148         cps->isr = NULL;
8149         return;
8150     }
8151     
8152     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8153     if (cps->protocolVersion > 1) {
8154       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8155       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8156       cps->comboCnt = 0;  //                and values of combo boxes
8157       SendToProgram(buf, cps);
8158     } else {
8159       SendToProgram("xboard\n", cps);
8160     }
8161 }
8162
8163
8164 void
8165 TwoMachinesEventIfReady P((void))
8166 {
8167   if (first.lastPing != first.lastPong) {
8168     DisplayMessage("", _("Waiting for first chess program"));
8169     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8170     return;
8171   }
8172   if (second.lastPing != second.lastPong) {
8173     DisplayMessage("", _("Waiting for second chess program"));
8174     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8175     return;
8176   }
8177   ThawUI();
8178   TwoMachinesEvent();
8179 }
8180
8181 void
8182 NextMatchGame P((void))
8183 {
8184     int index; /* [HGM] autoinc: step load index during match */
8185     Reset(FALSE, TRUE);
8186     if (*appData.loadGameFile != NULLCHAR) {
8187         index = appData.loadGameIndex;
8188         if(index < 0) { // [HGM] autoinc
8189             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8190             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8191         } 
8192         LoadGameFromFile(appData.loadGameFile,
8193                          index,
8194                          appData.loadGameFile, FALSE);
8195     } else if (*appData.loadPositionFile != NULLCHAR) {
8196         index = appData.loadPositionIndex;
8197         if(index < 0) { // [HGM] autoinc
8198             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8199             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8200         } 
8201         LoadPositionFromFile(appData.loadPositionFile,
8202                              index,
8203                              appData.loadPositionFile);
8204     }
8205     TwoMachinesEventIfReady();
8206 }
8207
8208 void UserAdjudicationEvent( int result )
8209 {
8210     ChessMove gameResult = GameIsDrawn;
8211
8212     if( result > 0 ) {
8213         gameResult = WhiteWins;
8214     }
8215     else if( result < 0 ) {
8216         gameResult = BlackWins;
8217     }
8218
8219     if( gameMode == TwoMachinesPlay ) {
8220         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8221     }
8222 }
8223
8224
8225 // [HGM] save: calculate checksum of game to make games easily identifiable
8226 int StringCheckSum(char *s)
8227 {
8228         int i = 0;
8229         if(s==NULL) return 0;
8230         while(*s) i = i*259 + *s++;
8231         return i;
8232 }
8233
8234 int GameCheckSum()
8235 {
8236         int i, sum=0;
8237         for(i=backwardMostMove; i<forwardMostMove; i++) {
8238                 sum += pvInfoList[i].depth;
8239                 sum += StringCheckSum(parseList[i]);
8240                 sum += StringCheckSum(commentList[i]);
8241                 sum *= 261;
8242         }
8243         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8244         return sum + StringCheckSum(commentList[i]);
8245 } // end of save patch
8246
8247 void
8248 GameEnds(result, resultDetails, whosays)
8249      ChessMove result;
8250      char *resultDetails;
8251      int whosays;
8252 {
8253     GameMode nextGameMode;
8254     int isIcsGame;
8255     char buf[MSG_SIZ];
8256
8257     if(endingGame) return; /* [HGM] crash: forbid recursion */
8258     endingGame = 1;
8259
8260     if (appData.debugMode) {
8261       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8262               result, resultDetails ? resultDetails : "(null)", whosays);
8263     }
8264
8265     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8266         /* If we are playing on ICS, the server decides when the
8267            game is over, but the engine can offer to draw, claim 
8268            a draw, or resign. 
8269          */
8270 #if ZIPPY
8271         if (appData.zippyPlay && first.initDone) {
8272             if (result == GameIsDrawn) {
8273                 /* In case draw still needs to be claimed */
8274                 SendToICS(ics_prefix);
8275                 SendToICS("draw\n");
8276             } else if (StrCaseStr(resultDetails, "resign")) {
8277                 SendToICS(ics_prefix);
8278                 SendToICS("resign\n");
8279             }
8280         }
8281 #endif
8282         endingGame = 0; /* [HGM] crash */
8283         return;
8284     }
8285
8286     /* If we're loading the game from a file, stop */
8287     if (whosays == GE_FILE) {
8288       (void) StopLoadGameTimer();
8289       gameFileFP = NULL;
8290     }
8291
8292     /* Cancel draw offers */
8293     first.offeredDraw = second.offeredDraw = 0;
8294
8295     /* If this is an ICS game, only ICS can really say it's done;
8296        if not, anyone can. */
8297     isIcsGame = (gameMode == IcsPlayingWhite || 
8298                  gameMode == IcsPlayingBlack || 
8299                  gameMode == IcsObserving    || 
8300                  gameMode == IcsExamining);
8301
8302     if (!isIcsGame || whosays == GE_ICS) {
8303         /* OK -- not an ICS game, or ICS said it was done */
8304         StopClocks();
8305         if (!isIcsGame && !appData.noChessProgram) 
8306           SetUserThinkingEnables();
8307     
8308         /* [HGM] if a machine claims the game end we verify this claim */
8309         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8310             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8311                 char claimer;
8312                 ChessMove trueResult = (ChessMove) -1;
8313
8314                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8315                                             first.twoMachinesColor[0] :
8316                                             second.twoMachinesColor[0] ;
8317
8318                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8319                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8320                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8321                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8322                 } else
8323                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8324                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8325                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8326                 } else
8327                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8328                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8329                 }
8330
8331                 // now verify win claims, but not in drop games, as we don't understand those yet
8332                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8333                                                  || gameInfo.variant == VariantGreat) &&
8334                     (result == WhiteWins && claimer == 'w' ||
8335                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8336                       if (appData.debugMode) {
8337                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8338                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8339                       }
8340                       if(result != trueResult) {
8341                               sprintf(buf, "False win claim: '%s'", resultDetails);
8342                               result = claimer == 'w' ? BlackWins : WhiteWins;
8343                               resultDetails = buf;
8344                       }
8345                 } else
8346                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8347                     && (forwardMostMove <= backwardMostMove ||
8348                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8349                         (claimer=='b')==(forwardMostMove&1))
8350                                                                                   ) {
8351                       /* [HGM] verify: draws that were not flagged are false claims */
8352                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8353                       result = claimer == 'w' ? BlackWins : WhiteWins;
8354                       resultDetails = buf;
8355                 }
8356                 /* (Claiming a loss is accepted no questions asked!) */
8357             }
8358             /* [HGM] bare: don't allow bare King to win */
8359             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8360                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8361                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8362                && result != GameIsDrawn)
8363             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8364                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8365                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8366                         if(p >= 0 && p <= (int)WhiteKing) k++;
8367                 }
8368                 if (appData.debugMode) {
8369                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8370                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8371                 }
8372                 if(k <= 1) {
8373                         result = GameIsDrawn;
8374                         sprintf(buf, "%s but bare king", resultDetails);
8375                         resultDetails = buf;
8376                 }
8377             }
8378         }
8379
8380
8381         if(serverMoves != NULL && !loadFlag) { char c = '=';
8382             if(result==WhiteWins) c = '+';
8383             if(result==BlackWins) c = '-';
8384             if(resultDetails != NULL)
8385                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8386         }
8387         if (resultDetails != NULL) {
8388             gameInfo.result = result;
8389             gameInfo.resultDetails = StrSave(resultDetails);
8390
8391             /* display last move only if game was not loaded from file */
8392             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8393                 DisplayMove(currentMove - 1);
8394     
8395             if (forwardMostMove != 0) {
8396                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8397                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8398                                                                 ) {
8399                     if (*appData.saveGameFile != NULLCHAR) {
8400                         SaveGameToFile(appData.saveGameFile, TRUE);
8401                     } else if (appData.autoSaveGames) {
8402                         AutoSaveGame();
8403                     }
8404                     if (*appData.savePositionFile != NULLCHAR) {
8405                         SavePositionToFile(appData.savePositionFile);
8406                     }
8407                 }
8408             }
8409
8410             /* Tell program how game ended in case it is learning */
8411             /* [HGM] Moved this to after saving the PGN, just in case */
8412             /* engine died and we got here through time loss. In that */
8413             /* case we will get a fatal error writing the pipe, which */
8414             /* would otherwise lose us the PGN.                       */
8415             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8416             /* output during GameEnds should never be fatal anymore   */
8417             if (gameMode == MachinePlaysWhite ||
8418                 gameMode == MachinePlaysBlack ||
8419                 gameMode == TwoMachinesPlay ||
8420                 gameMode == IcsPlayingWhite ||
8421                 gameMode == IcsPlayingBlack ||
8422                 gameMode == BeginningOfGame) {
8423                 char buf[MSG_SIZ];
8424                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8425                         resultDetails);
8426                 if (first.pr != NoProc) {
8427                     SendToProgram(buf, &first);
8428                 }
8429                 if (second.pr != NoProc &&
8430                     gameMode == TwoMachinesPlay) {
8431                     SendToProgram(buf, &second);
8432                 }
8433             }
8434         }
8435
8436         if (appData.icsActive) {
8437             if (appData.quietPlay &&
8438                 (gameMode == IcsPlayingWhite ||
8439                  gameMode == IcsPlayingBlack)) {
8440                 SendToICS(ics_prefix);
8441                 SendToICS("set shout 1\n");
8442             }
8443             nextGameMode = IcsIdle;
8444             ics_user_moved = FALSE;
8445             /* clean up premove.  It's ugly when the game has ended and the
8446              * premove highlights are still on the board.
8447              */
8448             if (gotPremove) {
8449               gotPremove = FALSE;
8450               ClearPremoveHighlights();
8451               DrawPosition(FALSE, boards[currentMove]);
8452             }
8453             if (whosays == GE_ICS) {
8454                 switch (result) {
8455                 case WhiteWins:
8456                     if (gameMode == IcsPlayingWhite)
8457                         PlayIcsWinSound();
8458                     else if(gameMode == IcsPlayingBlack)
8459                         PlayIcsLossSound();
8460                     break;
8461                 case BlackWins:
8462                     if (gameMode == IcsPlayingBlack)
8463                         PlayIcsWinSound();
8464                     else if(gameMode == IcsPlayingWhite)
8465                         PlayIcsLossSound();
8466                     break;
8467                 case GameIsDrawn:
8468                     PlayIcsDrawSound();
8469                     break;
8470                 default:
8471                     PlayIcsUnfinishedSound();
8472                 }
8473             }
8474         } else if (gameMode == EditGame ||
8475                    gameMode == PlayFromGameFile || 
8476                    gameMode == AnalyzeMode || 
8477                    gameMode == AnalyzeFile) {
8478             nextGameMode = gameMode;
8479         } else {
8480             nextGameMode = EndOfGame;
8481         }
8482         pausing = FALSE;
8483         ModeHighlight();
8484     } else {
8485         nextGameMode = gameMode;
8486     }
8487
8488     if (appData.noChessProgram) {
8489         gameMode = nextGameMode;
8490         ModeHighlight();
8491         endingGame = 0; /* [HGM] crash */
8492         return;
8493     }
8494
8495     if (first.reuse) {
8496         /* Put first chess program into idle state */
8497         if (first.pr != NoProc &&
8498             (gameMode == MachinePlaysWhite ||
8499              gameMode == MachinePlaysBlack ||
8500              gameMode == TwoMachinesPlay ||
8501              gameMode == IcsPlayingWhite ||
8502              gameMode == IcsPlayingBlack ||
8503              gameMode == BeginningOfGame)) {
8504             SendToProgram("force\n", &first);
8505             if (first.usePing) {
8506               char buf[MSG_SIZ];
8507               sprintf(buf, "ping %d\n", ++first.lastPing);
8508               SendToProgram(buf, &first);
8509             }
8510         }
8511     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8512         /* Kill off first chess program */
8513         if (first.isr != NULL)
8514           RemoveInputSource(first.isr);
8515         first.isr = NULL;
8516     
8517         if (first.pr != NoProc) {
8518             ExitAnalyzeMode();
8519             DoSleep( appData.delayBeforeQuit );
8520             SendToProgram("quit\n", &first);
8521             DoSleep( appData.delayAfterQuit );
8522             DestroyChildProcess(first.pr, first.useSigterm);
8523         }
8524         first.pr = NoProc;
8525     }
8526     if (second.reuse) {
8527         /* Put second chess program into idle state */
8528         if (second.pr != NoProc &&
8529             gameMode == TwoMachinesPlay) {
8530             SendToProgram("force\n", &second);
8531             if (second.usePing) {
8532               char buf[MSG_SIZ];
8533               sprintf(buf, "ping %d\n", ++second.lastPing);
8534               SendToProgram(buf, &second);
8535             }
8536         }
8537     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8538         /* Kill off second chess program */
8539         if (second.isr != NULL)
8540           RemoveInputSource(second.isr);
8541         second.isr = NULL;
8542     
8543         if (second.pr != NoProc) {
8544             DoSleep( appData.delayBeforeQuit );
8545             SendToProgram("quit\n", &second);
8546             DoSleep( appData.delayAfterQuit );
8547             DestroyChildProcess(second.pr, second.useSigterm);
8548         }
8549         second.pr = NoProc;
8550     }
8551
8552     if (matchMode && gameMode == TwoMachinesPlay) {
8553         switch (result) {
8554         case WhiteWins:
8555           if (first.twoMachinesColor[0] == 'w') {
8556             first.matchWins++;
8557           } else {
8558             second.matchWins++;
8559           }
8560           break;
8561         case BlackWins:
8562           if (first.twoMachinesColor[0] == 'b') {
8563             first.matchWins++;
8564           } else {
8565             second.matchWins++;
8566           }
8567           break;
8568         default:
8569           break;
8570         }
8571         if (matchGame < appData.matchGames) {
8572             char *tmp;
8573             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8574                 tmp = first.twoMachinesColor;
8575                 first.twoMachinesColor = second.twoMachinesColor;
8576                 second.twoMachinesColor = tmp;
8577             }
8578             gameMode = nextGameMode;
8579             matchGame++;
8580             if(appData.matchPause>10000 || appData.matchPause<10)
8581                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8582             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8583             endingGame = 0; /* [HGM] crash */
8584             return;
8585         } else {
8586             char buf[MSG_SIZ];
8587             gameMode = nextGameMode;
8588             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8589                     first.tidy, second.tidy,
8590                     first.matchWins, second.matchWins,
8591                     appData.matchGames - (first.matchWins + second.matchWins));
8592             DisplayFatalError(buf, 0, 0);
8593         }
8594     }
8595     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8596         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8597       ExitAnalyzeMode();
8598     gameMode = nextGameMode;
8599     ModeHighlight();
8600     endingGame = 0;  /* [HGM] crash */
8601 }
8602
8603 /* Assumes program was just initialized (initString sent).
8604    Leaves program in force mode. */
8605 void
8606 FeedMovesToProgram(cps, upto) 
8607      ChessProgramState *cps;
8608      int upto;
8609 {
8610     int i;
8611     
8612     if (appData.debugMode)
8613       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8614               startedFromSetupPosition ? "position and " : "",
8615               backwardMostMove, upto, cps->which);
8616     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8617         // [HGM] variantswitch: make engine aware of new variant
8618         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8619                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8620         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8621         SendToProgram(buf, cps);
8622         currentlyInitializedVariant = gameInfo.variant;
8623     }
8624     SendToProgram("force\n", cps);
8625     if (startedFromSetupPosition) {
8626         SendBoard(cps, backwardMostMove);
8627     if (appData.debugMode) {
8628         fprintf(debugFP, "feedMoves\n");
8629     }
8630     }
8631     for (i = backwardMostMove; i < upto; i++) {
8632         SendMoveToProgram(i, cps);
8633     }
8634 }
8635
8636
8637 void
8638 ResurrectChessProgram()
8639 {
8640      /* The chess program may have exited.
8641         If so, restart it and feed it all the moves made so far. */
8642
8643     if (appData.noChessProgram || first.pr != NoProc) return;
8644     
8645     StartChessProgram(&first);
8646     InitChessProgram(&first, FALSE);
8647     FeedMovesToProgram(&first, currentMove);
8648
8649     if (!first.sendTime) {
8650         /* can't tell gnuchess what its clock should read,
8651            so we bow to its notion. */
8652         ResetClocks();
8653         timeRemaining[0][currentMove] = whiteTimeRemaining;
8654         timeRemaining[1][currentMove] = blackTimeRemaining;
8655     }
8656
8657     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8658                 appData.icsEngineAnalyze) && first.analysisSupport) {
8659       SendToProgram("analyze\n", &first);
8660       first.analyzing = TRUE;
8661     }
8662 }
8663
8664 /*
8665  * Button procedures
8666  */
8667 void
8668 Reset(redraw, init)
8669      int redraw, init;
8670 {
8671     int i;
8672
8673     if (appData.debugMode) {
8674         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8675                 redraw, init, gameMode);
8676     }
8677     CleanupTail(); // [HGM] vari: delete any stored variations
8678     pausing = pauseExamInvalid = FALSE;
8679     startedFromSetupPosition = blackPlaysFirst = FALSE;
8680     firstMove = TRUE;
8681     whiteFlag = blackFlag = FALSE;
8682     userOfferedDraw = FALSE;
8683     hintRequested = bookRequested = FALSE;
8684     first.maybeThinking = FALSE;
8685     second.maybeThinking = FALSE;
8686     first.bookSuspend = FALSE; // [HGM] book
8687     second.bookSuspend = FALSE;
8688     thinkOutput[0] = NULLCHAR;
8689     lastHint[0] = NULLCHAR;
8690     ClearGameInfo(&gameInfo);
8691     gameInfo.variant = StringToVariant(appData.variant);
8692     ics_user_moved = ics_clock_paused = FALSE;
8693     ics_getting_history = H_FALSE;
8694     ics_gamenum = -1;
8695     white_holding[0] = black_holding[0] = NULLCHAR;
8696     ClearProgramStats();
8697     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8698     
8699     ResetFrontEnd();
8700     ClearHighlights();
8701     flipView = appData.flipView;
8702     ClearPremoveHighlights();
8703     gotPremove = FALSE;
8704     alarmSounded = FALSE;
8705
8706     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8707     if(appData.serverMovesName != NULL) {
8708         /* [HGM] prepare to make moves file for broadcasting */
8709         clock_t t = clock();
8710         if(serverMoves != NULL) fclose(serverMoves);
8711         serverMoves = fopen(appData.serverMovesName, "r");
8712         if(serverMoves != NULL) {
8713             fclose(serverMoves);
8714             /* delay 15 sec before overwriting, so all clients can see end */
8715             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8716         }
8717         serverMoves = fopen(appData.serverMovesName, "w");
8718     }
8719
8720     ExitAnalyzeMode();
8721     gameMode = BeginningOfGame;
8722     ModeHighlight();
8723     if(appData.icsActive) gameInfo.variant = VariantNormal;
8724     currentMove = forwardMostMove = backwardMostMove = 0;
8725     InitPosition(redraw);
8726     for (i = 0; i < MAX_MOVES; i++) {
8727         if (commentList[i] != NULL) {
8728             free(commentList[i]);
8729             commentList[i] = NULL;
8730         }
8731     }
8732     ResetClocks();
8733     timeRemaining[0][0] = whiteTimeRemaining;
8734     timeRemaining[1][0] = blackTimeRemaining;
8735     if (first.pr == NULL) {
8736         StartChessProgram(&first);
8737     }
8738     if (init) {
8739             InitChessProgram(&first, startedFromSetupPosition);
8740     }
8741     DisplayTitle("");
8742     DisplayMessage("", "");
8743     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8744     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8745 }
8746
8747 void
8748 AutoPlayGameLoop()
8749 {
8750     for (;;) {
8751         if (!AutoPlayOneMove())
8752           return;
8753         if (matchMode || appData.timeDelay == 0)
8754           continue;
8755         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8756           return;
8757         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8758         break;
8759     }
8760 }
8761
8762
8763 int
8764 AutoPlayOneMove()
8765 {
8766     int fromX, fromY, toX, toY;
8767
8768     if (appData.debugMode) {
8769       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8770     }
8771
8772     if (gameMode != PlayFromGameFile)
8773       return FALSE;
8774
8775     if (currentMove >= forwardMostMove) {
8776       gameMode = EditGame;
8777       ModeHighlight();
8778
8779       /* [AS] Clear current move marker at the end of a game */
8780       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8781
8782       return FALSE;
8783     }
8784     
8785     toX = moveList[currentMove][2] - AAA;
8786     toY = moveList[currentMove][3] - ONE;
8787
8788     if (moveList[currentMove][1] == '@') {
8789         if (appData.highlightLastMove) {
8790             SetHighlights(-1, -1, toX, toY);
8791         }
8792     } else {
8793         fromX = moveList[currentMove][0] - AAA;
8794         fromY = moveList[currentMove][1] - ONE;
8795
8796         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8797
8798         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8799
8800         if (appData.highlightLastMove) {
8801             SetHighlights(fromX, fromY, toX, toY);
8802         }
8803     }
8804     DisplayMove(currentMove);
8805     SendMoveToProgram(currentMove++, &first);
8806     DisplayBothClocks();
8807     DrawPosition(FALSE, boards[currentMove]);
8808     // [HGM] PV info: always display, routine tests if empty
8809     DisplayComment(currentMove - 1, commentList[currentMove]);
8810     return TRUE;
8811 }
8812
8813
8814 int
8815 LoadGameOneMove(readAhead)
8816      ChessMove readAhead;
8817 {
8818     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8819     char promoChar = NULLCHAR;
8820     ChessMove moveType;
8821     char move[MSG_SIZ];
8822     char *p, *q;
8823     
8824     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8825         gameMode != AnalyzeMode && gameMode != Training) {
8826         gameFileFP = NULL;
8827         return FALSE;
8828     }
8829     
8830     yyboardindex = forwardMostMove;
8831     if (readAhead != (ChessMove)0) {
8832       moveType = readAhead;
8833     } else {
8834       if (gameFileFP == NULL)
8835           return FALSE;
8836       moveType = (ChessMove) yylex();
8837     }
8838     
8839     done = FALSE;
8840     switch (moveType) {
8841       case Comment:
8842         if (appData.debugMode) 
8843           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8844         p = yy_text;
8845
8846         /* append the comment but don't display it */
8847         AppendComment(currentMove, p, FALSE);
8848         return TRUE;
8849
8850       case WhiteCapturesEnPassant:
8851       case BlackCapturesEnPassant:
8852       case WhitePromotionChancellor:
8853       case BlackPromotionChancellor:
8854       case WhitePromotionArchbishop:
8855       case BlackPromotionArchbishop:
8856       case WhitePromotionCentaur:
8857       case BlackPromotionCentaur:
8858       case WhitePromotionQueen:
8859       case BlackPromotionQueen:
8860       case WhitePromotionRook:
8861       case BlackPromotionRook:
8862       case WhitePromotionBishop:
8863       case BlackPromotionBishop:
8864       case WhitePromotionKnight:
8865       case BlackPromotionKnight:
8866       case WhitePromotionKing:
8867       case BlackPromotionKing:
8868       case NormalMove:
8869       case WhiteKingSideCastle:
8870       case WhiteQueenSideCastle:
8871       case BlackKingSideCastle:
8872       case BlackQueenSideCastle:
8873       case WhiteKingSideCastleWild:
8874       case WhiteQueenSideCastleWild:
8875       case BlackKingSideCastleWild:
8876       case BlackQueenSideCastleWild:
8877       /* PUSH Fabien */
8878       case WhiteHSideCastleFR:
8879       case WhiteASideCastleFR:
8880       case BlackHSideCastleFR:
8881       case BlackASideCastleFR:
8882       /* POP Fabien */
8883         if (appData.debugMode)
8884           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8885         fromX = currentMoveString[0] - AAA;
8886         fromY = currentMoveString[1] - ONE;
8887         toX = currentMoveString[2] - AAA;
8888         toY = currentMoveString[3] - ONE;
8889         promoChar = currentMoveString[4];
8890         break;
8891
8892       case WhiteDrop:
8893       case BlackDrop:
8894         if (appData.debugMode)
8895           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8896         fromX = moveType == WhiteDrop ?
8897           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8898         (int) CharToPiece(ToLower(currentMoveString[0]));
8899         fromY = DROP_RANK;
8900         toX = currentMoveString[2] - AAA;
8901         toY = currentMoveString[3] - ONE;
8902         break;
8903
8904       case WhiteWins:
8905       case BlackWins:
8906       case GameIsDrawn:
8907       case GameUnfinished:
8908         if (appData.debugMode)
8909           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8910         p = strchr(yy_text, '{');
8911         if (p == NULL) p = strchr(yy_text, '(');
8912         if (p == NULL) {
8913             p = yy_text;
8914             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8915         } else {
8916             q = strchr(p, *p == '{' ? '}' : ')');
8917             if (q != NULL) *q = NULLCHAR;
8918             p++;
8919         }
8920         GameEnds(moveType, p, GE_FILE);
8921         done = TRUE;
8922         if (cmailMsgLoaded) {
8923             ClearHighlights();
8924             flipView = WhiteOnMove(currentMove);
8925             if (moveType == GameUnfinished) flipView = !flipView;
8926             if (appData.debugMode)
8927               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8928         }
8929         break;
8930
8931       case (ChessMove) 0:       /* end of file */
8932         if (appData.debugMode)
8933           fprintf(debugFP, "Parser hit end of file\n");
8934         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8935           case MT_NONE:
8936           case MT_CHECK:
8937             break;
8938           case MT_CHECKMATE:
8939           case MT_STAINMATE:
8940             if (WhiteOnMove(currentMove)) {
8941                 GameEnds(BlackWins, "Black mates", GE_FILE);
8942             } else {
8943                 GameEnds(WhiteWins, "White mates", GE_FILE);
8944             }
8945             break;
8946           case MT_STALEMATE:
8947             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8948             break;
8949         }
8950         done = TRUE;
8951         break;
8952
8953       case MoveNumberOne:
8954         if (lastLoadGameStart == GNUChessGame) {
8955             /* GNUChessGames have numbers, but they aren't move numbers */
8956             if (appData.debugMode)
8957               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8958                       yy_text, (int) moveType);
8959             return LoadGameOneMove((ChessMove)0); /* tail recursion */
8960         }
8961         /* else fall thru */
8962
8963       case XBoardGame:
8964       case GNUChessGame:
8965       case PGNTag:
8966         /* Reached start of next game in file */
8967         if (appData.debugMode)
8968           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8969         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8970           case MT_NONE:
8971           case MT_CHECK:
8972             break;
8973           case MT_CHECKMATE:
8974           case MT_STAINMATE:
8975             if (WhiteOnMove(currentMove)) {
8976                 GameEnds(BlackWins, "Black mates", GE_FILE);
8977             } else {
8978                 GameEnds(WhiteWins, "White mates", GE_FILE);
8979             }
8980             break;
8981           case MT_STALEMATE:
8982             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8983             break;
8984         }
8985         done = TRUE;
8986         break;
8987
8988       case PositionDiagram:     /* should not happen; ignore */
8989       case ElapsedTime:         /* ignore */
8990       case NAG:                 /* ignore */
8991         if (appData.debugMode)
8992           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8993                   yy_text, (int) moveType);
8994         return LoadGameOneMove((ChessMove)0); /* tail recursion */
8995
8996       case IllegalMove:
8997         if (appData.testLegality) {
8998             if (appData.debugMode)
8999               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9000             sprintf(move, _("Illegal move: %d.%s%s"),
9001                     (forwardMostMove / 2) + 1,
9002                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9003             DisplayError(move, 0);
9004             done = TRUE;
9005         } else {
9006             if (appData.debugMode)
9007               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9008                       yy_text, currentMoveString);
9009             fromX = currentMoveString[0] - AAA;
9010             fromY = currentMoveString[1] - ONE;
9011             toX = currentMoveString[2] - AAA;
9012             toY = currentMoveString[3] - ONE;
9013             promoChar = currentMoveString[4];
9014         }
9015         break;
9016
9017       case AmbiguousMove:
9018         if (appData.debugMode)
9019           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9020         sprintf(move, _("Ambiguous move: %d.%s%s"),
9021                 (forwardMostMove / 2) + 1,
9022                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9023         DisplayError(move, 0);
9024         done = TRUE;
9025         break;
9026
9027       default:
9028       case ImpossibleMove:
9029         if (appData.debugMode)
9030           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9031         sprintf(move, _("Illegal move: %d.%s%s"),
9032                 (forwardMostMove / 2) + 1,
9033                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9034         DisplayError(move, 0);
9035         done = TRUE;
9036         break;
9037     }
9038
9039     if (done) {
9040         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9041             DrawPosition(FALSE, boards[currentMove]);
9042             DisplayBothClocks();
9043             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9044               DisplayComment(currentMove - 1, commentList[currentMove]);
9045         }
9046         (void) StopLoadGameTimer();
9047         gameFileFP = NULL;
9048         cmailOldMove = forwardMostMove;
9049         return FALSE;
9050     } else {
9051         /* currentMoveString is set as a side-effect of yylex */
9052         strcat(currentMoveString, "\n");
9053         strcpy(moveList[forwardMostMove], currentMoveString);
9054         
9055         thinkOutput[0] = NULLCHAR;
9056         MakeMove(fromX, fromY, toX, toY, promoChar);
9057         currentMove = forwardMostMove;
9058         return TRUE;
9059     }
9060 }
9061
9062 /* Load the nth game from the given file */
9063 int
9064 LoadGameFromFile(filename, n, title, useList)
9065      char *filename;
9066      int n;
9067      char *title;
9068      /*Boolean*/ int useList;
9069 {
9070     FILE *f;
9071     char buf[MSG_SIZ];
9072
9073     if (strcmp(filename, "-") == 0) {
9074         f = stdin;
9075         title = "stdin";
9076     } else {
9077         f = fopen(filename, "rb");
9078         if (f == NULL) {
9079           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9080             DisplayError(buf, errno);
9081             return FALSE;
9082         }
9083     }
9084     if (fseek(f, 0, 0) == -1) {
9085         /* f is not seekable; probably a pipe */
9086         useList = FALSE;
9087     }
9088     if (useList && n == 0) {
9089         int error = GameListBuild(f);
9090         if (error) {
9091             DisplayError(_("Cannot build game list"), error);
9092         } else if (!ListEmpty(&gameList) &&
9093                    ((ListGame *) gameList.tailPred)->number > 1) {
9094             GameListPopUp(f, title);
9095             return TRUE;
9096         }
9097         GameListDestroy();
9098         n = 1;
9099     }
9100     if (n == 0) n = 1;
9101     return LoadGame(f, n, title, FALSE);
9102 }
9103
9104
9105 void
9106 MakeRegisteredMove()
9107 {
9108     int fromX, fromY, toX, toY;
9109     char promoChar;
9110     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9111         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9112           case CMAIL_MOVE:
9113           case CMAIL_DRAW:
9114             if (appData.debugMode)
9115               fprintf(debugFP, "Restoring %s for game %d\n",
9116                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9117     
9118             thinkOutput[0] = NULLCHAR;
9119             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9120             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9121             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9122             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9123             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9124             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9125             MakeMove(fromX, fromY, toX, toY, promoChar);
9126             ShowMove(fromX, fromY, toX, toY);
9127               
9128             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9129               case MT_NONE:
9130               case MT_CHECK:
9131                 break;
9132                 
9133               case MT_CHECKMATE:
9134               case MT_STAINMATE:
9135                 if (WhiteOnMove(currentMove)) {
9136                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9137                 } else {
9138                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9139                 }
9140                 break;
9141                 
9142               case MT_STALEMATE:
9143                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9144                 break;
9145             }
9146
9147             break;
9148             
9149           case CMAIL_RESIGN:
9150             if (WhiteOnMove(currentMove)) {
9151                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9152             } else {
9153                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9154             }
9155             break;
9156             
9157           case CMAIL_ACCEPT:
9158             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9159             break;
9160               
9161           default:
9162             break;
9163         }
9164     }
9165
9166     return;
9167 }
9168
9169 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9170 int
9171 CmailLoadGame(f, gameNumber, title, useList)
9172      FILE *f;
9173      int gameNumber;
9174      char *title;
9175      int useList;
9176 {
9177     int retVal;
9178
9179     if (gameNumber > nCmailGames) {
9180         DisplayError(_("No more games in this message"), 0);
9181         return FALSE;
9182     }
9183     if (f == lastLoadGameFP) {
9184         int offset = gameNumber - lastLoadGameNumber;
9185         if (offset == 0) {
9186             cmailMsg[0] = NULLCHAR;
9187             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9188                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9189                 nCmailMovesRegistered--;
9190             }
9191             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9192             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9193                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9194             }
9195         } else {
9196             if (! RegisterMove()) return FALSE;
9197         }
9198     }
9199
9200     retVal = LoadGame(f, gameNumber, title, useList);
9201
9202     /* Make move registered during previous look at this game, if any */
9203     MakeRegisteredMove();
9204
9205     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9206         commentList[currentMove]
9207           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9208         DisplayComment(currentMove - 1, commentList[currentMove]);
9209     }
9210
9211     return retVal;
9212 }
9213
9214 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9215 int
9216 ReloadGame(offset)
9217      int offset;
9218 {
9219     int gameNumber = lastLoadGameNumber + offset;
9220     if (lastLoadGameFP == NULL) {
9221         DisplayError(_("No game has been loaded yet"), 0);
9222         return FALSE;
9223     }
9224     if (gameNumber <= 0) {
9225         DisplayError(_("Can't back up any further"), 0);
9226         return FALSE;
9227     }
9228     if (cmailMsgLoaded) {
9229         return CmailLoadGame(lastLoadGameFP, gameNumber,
9230                              lastLoadGameTitle, lastLoadGameUseList);
9231     } else {
9232         return LoadGame(lastLoadGameFP, gameNumber,
9233                         lastLoadGameTitle, lastLoadGameUseList);
9234     }
9235 }
9236
9237
9238
9239 /* Load the nth game from open file f */
9240 int
9241 LoadGame(f, gameNumber, title, useList)
9242      FILE *f;
9243      int gameNumber;
9244      char *title;
9245      int useList;
9246 {
9247     ChessMove cm;
9248     char buf[MSG_SIZ];
9249     int gn = gameNumber;
9250     ListGame *lg = NULL;
9251     int numPGNTags = 0;
9252     int err;
9253     GameMode oldGameMode;
9254     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9255
9256     if (appData.debugMode) 
9257         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9258
9259     if (gameMode == Training )
9260         SetTrainingModeOff();
9261
9262     oldGameMode = gameMode;
9263     if (gameMode != BeginningOfGame) {
9264       Reset(FALSE, TRUE);
9265     }
9266
9267     gameFileFP = f;
9268     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9269         fclose(lastLoadGameFP);
9270     }
9271
9272     if (useList) {
9273         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9274         
9275         if (lg) {
9276             fseek(f, lg->offset, 0);
9277             GameListHighlight(gameNumber);
9278             gn = 1;
9279         }
9280         else {
9281             DisplayError(_("Game number out of range"), 0);
9282             return FALSE;
9283         }
9284     } else {
9285         GameListDestroy();
9286         if (fseek(f, 0, 0) == -1) {
9287             if (f == lastLoadGameFP ?
9288                 gameNumber == lastLoadGameNumber + 1 :
9289                 gameNumber == 1) {
9290                 gn = 1;
9291             } else {
9292                 DisplayError(_("Can't seek on game file"), 0);
9293                 return FALSE;
9294             }
9295         }
9296     }
9297     lastLoadGameFP = f;
9298     lastLoadGameNumber = gameNumber;
9299     strcpy(lastLoadGameTitle, title);
9300     lastLoadGameUseList = useList;
9301
9302     yynewfile(f);
9303
9304     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9305       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9306                 lg->gameInfo.black);
9307             DisplayTitle(buf);
9308     } else if (*title != NULLCHAR) {
9309         if (gameNumber > 1) {
9310             sprintf(buf, "%s %d", title, gameNumber);
9311             DisplayTitle(buf);
9312         } else {
9313             DisplayTitle(title);
9314         }
9315     }
9316
9317     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9318         gameMode = PlayFromGameFile;
9319         ModeHighlight();
9320     }
9321
9322     currentMove = forwardMostMove = backwardMostMove = 0;
9323     CopyBoard(boards[0], initialPosition);
9324     StopClocks();
9325
9326     /*
9327      * Skip the first gn-1 games in the file.
9328      * Also skip over anything that precedes an identifiable 
9329      * start of game marker, to avoid being confused by 
9330      * garbage at the start of the file.  Currently 
9331      * recognized start of game markers are the move number "1",
9332      * the pattern "gnuchess .* game", the pattern
9333      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9334      * A game that starts with one of the latter two patterns
9335      * will also have a move number 1, possibly
9336      * following a position diagram.
9337      * 5-4-02: Let's try being more lenient and allowing a game to
9338      * start with an unnumbered move.  Does that break anything?
9339      */
9340     cm = lastLoadGameStart = (ChessMove) 0;
9341     while (gn > 0) {
9342         yyboardindex = forwardMostMove;
9343         cm = (ChessMove) yylex();
9344         switch (cm) {
9345           case (ChessMove) 0:
9346             if (cmailMsgLoaded) {
9347                 nCmailGames = CMAIL_MAX_GAMES - gn;
9348             } else {
9349                 Reset(TRUE, TRUE);
9350                 DisplayError(_("Game not found in file"), 0);
9351             }
9352             return FALSE;
9353
9354           case GNUChessGame:
9355           case XBoardGame:
9356             gn--;
9357             lastLoadGameStart = cm;
9358             break;
9359             
9360           case MoveNumberOne:
9361             switch (lastLoadGameStart) {
9362               case GNUChessGame:
9363               case XBoardGame:
9364               case PGNTag:
9365                 break;
9366               case MoveNumberOne:
9367               case (ChessMove) 0:
9368                 gn--;           /* count this game */
9369                 lastLoadGameStart = cm;
9370                 break;
9371               default:
9372                 /* impossible */
9373                 break;
9374             }
9375             break;
9376
9377           case PGNTag:
9378             switch (lastLoadGameStart) {
9379               case GNUChessGame:
9380               case PGNTag:
9381               case MoveNumberOne:
9382               case (ChessMove) 0:
9383                 gn--;           /* count this game */
9384                 lastLoadGameStart = cm;
9385                 break;
9386               case XBoardGame:
9387                 lastLoadGameStart = cm; /* game counted already */
9388                 break;
9389               default:
9390                 /* impossible */
9391                 break;
9392             }
9393             if (gn > 0) {
9394                 do {
9395                     yyboardindex = forwardMostMove;
9396                     cm = (ChessMove) yylex();
9397                 } while (cm == PGNTag || cm == Comment);
9398             }
9399             break;
9400
9401           case WhiteWins:
9402           case BlackWins:
9403           case GameIsDrawn:
9404             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9405                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9406                     != CMAIL_OLD_RESULT) {
9407                     nCmailResults ++ ;
9408                     cmailResult[  CMAIL_MAX_GAMES
9409                                 - gn - 1] = CMAIL_OLD_RESULT;
9410                 }
9411             }
9412             break;
9413
9414           case NormalMove:
9415             /* Only a NormalMove can be at the start of a game
9416              * without a position diagram. */
9417             if (lastLoadGameStart == (ChessMove) 0) {
9418               gn--;
9419               lastLoadGameStart = MoveNumberOne;
9420             }
9421             break;
9422
9423           default:
9424             break;
9425         }
9426     }
9427     
9428     if (appData.debugMode)
9429       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9430
9431     if (cm == XBoardGame) {
9432         /* Skip any header junk before position diagram and/or move 1 */
9433         for (;;) {
9434             yyboardindex = forwardMostMove;
9435             cm = (ChessMove) yylex();
9436
9437             if (cm == (ChessMove) 0 ||
9438                 cm == GNUChessGame || cm == XBoardGame) {
9439                 /* Empty game; pretend end-of-file and handle later */
9440                 cm = (ChessMove) 0;
9441                 break;
9442             }
9443
9444             if (cm == MoveNumberOne || cm == PositionDiagram ||
9445                 cm == PGNTag || cm == Comment)
9446               break;
9447         }
9448     } else if (cm == GNUChessGame) {
9449         if (gameInfo.event != NULL) {
9450             free(gameInfo.event);
9451         }
9452         gameInfo.event = StrSave(yy_text);
9453     }   
9454
9455     startedFromSetupPosition = FALSE;
9456     while (cm == PGNTag) {
9457         if (appData.debugMode) 
9458           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9459         err = ParsePGNTag(yy_text, &gameInfo);
9460         if (!err) numPGNTags++;
9461
9462         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9463         if(gameInfo.variant != oldVariant) {
9464             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9465             InitPosition(TRUE);
9466             oldVariant = gameInfo.variant;
9467             if (appData.debugMode) 
9468               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9469         }
9470
9471
9472         if (gameInfo.fen != NULL) {
9473           Board initial_position;
9474           startedFromSetupPosition = TRUE;
9475           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9476             Reset(TRUE, TRUE);
9477             DisplayError(_("Bad FEN position in file"), 0);
9478             return FALSE;
9479           }
9480           CopyBoard(boards[0], initial_position);
9481           if (blackPlaysFirst) {
9482             currentMove = forwardMostMove = backwardMostMove = 1;
9483             CopyBoard(boards[1], initial_position);
9484             strcpy(moveList[0], "");
9485             strcpy(parseList[0], "");
9486             timeRemaining[0][1] = whiteTimeRemaining;
9487             timeRemaining[1][1] = blackTimeRemaining;
9488             if (commentList[0] != NULL) {
9489               commentList[1] = commentList[0];
9490               commentList[0] = NULL;
9491             }
9492           } else {
9493             currentMove = forwardMostMove = backwardMostMove = 0;
9494           }
9495           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9496           {   int i;
9497               initialRulePlies = FENrulePlies;
9498               for( i=0; i< nrCastlingRights; i++ )
9499                   initialRights[i] = initial_position[CASTLING][i];
9500           }
9501           yyboardindex = forwardMostMove;
9502           free(gameInfo.fen);
9503           gameInfo.fen = NULL;
9504         }
9505
9506         yyboardindex = forwardMostMove;
9507         cm = (ChessMove) yylex();
9508
9509         /* Handle comments interspersed among the tags */
9510         while (cm == Comment) {
9511             char *p;
9512             if (appData.debugMode) 
9513               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9514             p = yy_text;
9515             AppendComment(currentMove, p, FALSE);
9516             yyboardindex = forwardMostMove;
9517             cm = (ChessMove) yylex();
9518         }
9519     }
9520
9521     /* don't rely on existence of Event tag since if game was
9522      * pasted from clipboard the Event tag may not exist
9523      */
9524     if (numPGNTags > 0){
9525         char *tags;
9526         if (gameInfo.variant == VariantNormal) {
9527           gameInfo.variant = StringToVariant(gameInfo.event);
9528         }
9529         if (!matchMode) {
9530           if( appData.autoDisplayTags ) {
9531             tags = PGNTags(&gameInfo);
9532             TagsPopUp(tags, CmailMsg());
9533             free(tags);
9534           }
9535         }
9536     } else {
9537         /* Make something up, but don't display it now */
9538         SetGameInfo();
9539         TagsPopDown();
9540     }
9541
9542     if (cm == PositionDiagram) {
9543         int i, j;
9544         char *p;
9545         Board initial_position;
9546
9547         if (appData.debugMode)
9548           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9549
9550         if (!startedFromSetupPosition) {
9551             p = yy_text;
9552             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9553               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9554                 switch (*p) {
9555                   case '[':
9556                   case '-':
9557                   case ' ':
9558                   case '\t':
9559                   case '\n':
9560                   case '\r':
9561                     break;
9562                   default:
9563                     initial_position[i][j++] = CharToPiece(*p);
9564                     break;
9565                 }
9566             while (*p == ' ' || *p == '\t' ||
9567                    *p == '\n' || *p == '\r') p++;
9568         
9569             if (strncmp(p, "black", strlen("black"))==0)
9570               blackPlaysFirst = TRUE;
9571             else
9572               blackPlaysFirst = FALSE;
9573             startedFromSetupPosition = TRUE;
9574         
9575             CopyBoard(boards[0], initial_position);
9576             if (blackPlaysFirst) {
9577                 currentMove = forwardMostMove = backwardMostMove = 1;
9578                 CopyBoard(boards[1], initial_position);
9579                 strcpy(moveList[0], "");
9580                 strcpy(parseList[0], "");
9581                 timeRemaining[0][1] = whiteTimeRemaining;
9582                 timeRemaining[1][1] = blackTimeRemaining;
9583                 if (commentList[0] != NULL) {
9584                     commentList[1] = commentList[0];
9585                     commentList[0] = NULL;
9586                 }
9587             } else {
9588                 currentMove = forwardMostMove = backwardMostMove = 0;
9589             }
9590         }
9591         yyboardindex = forwardMostMove;
9592         cm = (ChessMove) yylex();
9593     }
9594
9595     if (first.pr == NoProc) {
9596         StartChessProgram(&first);
9597     }
9598     InitChessProgram(&first, FALSE);
9599     SendToProgram("force\n", &first);
9600     if (startedFromSetupPosition) {
9601         SendBoard(&first, forwardMostMove);
9602     if (appData.debugMode) {
9603         fprintf(debugFP, "Load Game\n");
9604     }
9605         DisplayBothClocks();
9606     }      
9607
9608     /* [HGM] server: flag to write setup moves in broadcast file as one */
9609     loadFlag = appData.suppressLoadMoves;
9610
9611     while (cm == Comment) {
9612         char *p;
9613         if (appData.debugMode) 
9614           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9615         p = yy_text;
9616         AppendComment(currentMove, p, FALSE);
9617         yyboardindex = forwardMostMove;
9618         cm = (ChessMove) yylex();
9619     }
9620
9621     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9622         cm == WhiteWins || cm == BlackWins ||
9623         cm == GameIsDrawn || cm == GameUnfinished) {
9624         DisplayMessage("", _("No moves in game"));
9625         if (cmailMsgLoaded) {
9626             if (appData.debugMode)
9627               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9628             ClearHighlights();
9629             flipView = FALSE;
9630         }
9631         DrawPosition(FALSE, boards[currentMove]);
9632         DisplayBothClocks();
9633         gameMode = EditGame;
9634         ModeHighlight();
9635         gameFileFP = NULL;
9636         cmailOldMove = 0;
9637         return TRUE;
9638     }
9639
9640     // [HGM] PV info: routine tests if comment empty
9641     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9642         DisplayComment(currentMove - 1, commentList[currentMove]);
9643     }
9644     if (!matchMode && appData.timeDelay != 0) 
9645       DrawPosition(FALSE, boards[currentMove]);
9646
9647     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9648       programStats.ok_to_send = 1;
9649     }
9650
9651     /* if the first token after the PGN tags is a move
9652      * and not move number 1, retrieve it from the parser 
9653      */
9654     if (cm != MoveNumberOne)
9655         LoadGameOneMove(cm);
9656
9657     /* load the remaining moves from the file */
9658     while (LoadGameOneMove((ChessMove)0)) {
9659       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9660       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9661     }
9662
9663     /* rewind to the start of the game */
9664     currentMove = backwardMostMove;
9665
9666     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9667
9668     if (oldGameMode == AnalyzeFile ||
9669         oldGameMode == AnalyzeMode) {
9670       AnalyzeFileEvent();
9671     }
9672
9673     if (matchMode || appData.timeDelay == 0) {
9674       ToEndEvent();
9675       gameMode = EditGame;
9676       ModeHighlight();
9677     } else if (appData.timeDelay > 0) {
9678       AutoPlayGameLoop();
9679     }
9680
9681     if (appData.debugMode) 
9682         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9683
9684     loadFlag = 0; /* [HGM] true game starts */
9685     return TRUE;
9686 }
9687
9688 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9689 int
9690 ReloadPosition(offset)
9691      int offset;
9692 {
9693     int positionNumber = lastLoadPositionNumber + offset;
9694     if (lastLoadPositionFP == NULL) {
9695         DisplayError(_("No position has been loaded yet"), 0);
9696         return FALSE;
9697     }
9698     if (positionNumber <= 0) {
9699         DisplayError(_("Can't back up any further"), 0);
9700         return FALSE;
9701     }
9702     return LoadPosition(lastLoadPositionFP, positionNumber,
9703                         lastLoadPositionTitle);
9704 }
9705
9706 /* Load the nth position from the given file */
9707 int
9708 LoadPositionFromFile(filename, n, title)
9709      char *filename;
9710      int n;
9711      char *title;
9712 {
9713     FILE *f;
9714     char buf[MSG_SIZ];
9715
9716     if (strcmp(filename, "-") == 0) {
9717         return LoadPosition(stdin, n, "stdin");
9718     } else {
9719         f = fopen(filename, "rb");
9720         if (f == NULL) {
9721             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9722             DisplayError(buf, errno);
9723             return FALSE;
9724         } else {
9725             return LoadPosition(f, n, title);
9726         }
9727     }
9728 }
9729
9730 /* Load the nth position from the given open file, and close it */
9731 int
9732 LoadPosition(f, positionNumber, title)
9733      FILE *f;
9734      int positionNumber;
9735      char *title;
9736 {
9737     char *p, line[MSG_SIZ];
9738     Board initial_position;
9739     int i, j, fenMode, pn;
9740     
9741     if (gameMode == Training )
9742         SetTrainingModeOff();
9743
9744     if (gameMode != BeginningOfGame) {
9745         Reset(FALSE, TRUE);
9746     }
9747     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9748         fclose(lastLoadPositionFP);
9749     }
9750     if (positionNumber == 0) positionNumber = 1;
9751     lastLoadPositionFP = f;
9752     lastLoadPositionNumber = positionNumber;
9753     strcpy(lastLoadPositionTitle, title);
9754     if (first.pr == NoProc) {
9755       StartChessProgram(&first);
9756       InitChessProgram(&first, FALSE);
9757     }    
9758     pn = positionNumber;
9759     if (positionNumber < 0) {
9760         /* Negative position number means to seek to that byte offset */
9761         if (fseek(f, -positionNumber, 0) == -1) {
9762             DisplayError(_("Can't seek on position file"), 0);
9763             return FALSE;
9764         };
9765         pn = 1;
9766     } else {
9767         if (fseek(f, 0, 0) == -1) {
9768             if (f == lastLoadPositionFP ?
9769                 positionNumber == lastLoadPositionNumber + 1 :
9770                 positionNumber == 1) {
9771                 pn = 1;
9772             } else {
9773                 DisplayError(_("Can't seek on position file"), 0);
9774                 return FALSE;
9775             }
9776         }
9777     }
9778     /* See if this file is FEN or old-style xboard */
9779     if (fgets(line, MSG_SIZ, f) == NULL) {
9780         DisplayError(_("Position not found in file"), 0);
9781         return FALSE;
9782     }
9783     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9784     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9785
9786     if (pn >= 2) {
9787         if (fenMode || line[0] == '#') pn--;
9788         while (pn > 0) {
9789             /* skip positions before number pn */
9790             if (fgets(line, MSG_SIZ, f) == NULL) {
9791                 Reset(TRUE, TRUE);
9792                 DisplayError(_("Position not found in file"), 0);
9793                 return FALSE;
9794             }
9795             if (fenMode || line[0] == '#') pn--;
9796         }
9797     }
9798
9799     if (fenMode) {
9800         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9801             DisplayError(_("Bad FEN position in file"), 0);
9802             return FALSE;
9803         }
9804     } else {
9805         (void) fgets(line, MSG_SIZ, f);
9806         (void) fgets(line, MSG_SIZ, f);
9807     
9808         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9809             (void) fgets(line, MSG_SIZ, f);
9810             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9811                 if (*p == ' ')
9812                   continue;
9813                 initial_position[i][j++] = CharToPiece(*p);
9814             }
9815         }
9816     
9817         blackPlaysFirst = FALSE;
9818         if (!feof(f)) {
9819             (void) fgets(line, MSG_SIZ, f);
9820             if (strncmp(line, "black", strlen("black"))==0)
9821               blackPlaysFirst = TRUE;
9822         }
9823     }
9824     startedFromSetupPosition = TRUE;
9825     
9826     SendToProgram("force\n", &first);
9827     CopyBoard(boards[0], initial_position);
9828     if (blackPlaysFirst) {
9829         currentMove = forwardMostMove = backwardMostMove = 1;
9830         strcpy(moveList[0], "");
9831         strcpy(parseList[0], "");
9832         CopyBoard(boards[1], initial_position);
9833         DisplayMessage("", _("Black to play"));
9834     } else {
9835         currentMove = forwardMostMove = backwardMostMove = 0;
9836         DisplayMessage("", _("White to play"));
9837     }
9838     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9839     SendBoard(&first, forwardMostMove);
9840     if (appData.debugMode) {
9841 int i, j;
9842   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9843   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9844         fprintf(debugFP, "Load Position\n");
9845     }
9846
9847     if (positionNumber > 1) {
9848         sprintf(line, "%s %d", title, positionNumber);
9849         DisplayTitle(line);
9850     } else {
9851         DisplayTitle(title);
9852     }
9853     gameMode = EditGame;
9854     ModeHighlight();
9855     ResetClocks();
9856     timeRemaining[0][1] = whiteTimeRemaining;
9857     timeRemaining[1][1] = blackTimeRemaining;
9858     DrawPosition(FALSE, boards[currentMove]);
9859    
9860     return TRUE;
9861 }
9862
9863
9864 void
9865 CopyPlayerNameIntoFileName(dest, src)
9866      char **dest, *src;
9867 {
9868     while (*src != NULLCHAR && *src != ',') {
9869         if (*src == ' ') {
9870             *(*dest)++ = '_';
9871             src++;
9872         } else {
9873             *(*dest)++ = *src++;
9874         }
9875     }
9876 }
9877
9878 char *DefaultFileName(ext)
9879      char *ext;
9880 {
9881     static char def[MSG_SIZ];
9882     char *p;
9883
9884     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9885         p = def;
9886         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9887         *p++ = '-';
9888         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9889         *p++ = '.';
9890         strcpy(p, ext);
9891     } else {
9892         def[0] = NULLCHAR;
9893     }
9894     return def;
9895 }
9896
9897 /* Save the current game to the given file */
9898 int
9899 SaveGameToFile(filename, append)
9900      char *filename;
9901      int append;
9902 {
9903     FILE *f;
9904     char buf[MSG_SIZ];
9905
9906     if (strcmp(filename, "-") == 0) {
9907         return SaveGame(stdout, 0, NULL);
9908     } else {
9909         f = fopen(filename, append ? "a" : "w");
9910         if (f == NULL) {
9911             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9912             DisplayError(buf, errno);
9913             return FALSE;
9914         } else {
9915             return SaveGame(f, 0, NULL);
9916         }
9917     }
9918 }
9919
9920 char *
9921 SavePart(str)
9922      char *str;
9923 {
9924     static char buf[MSG_SIZ];
9925     char *p;
9926     
9927     p = strchr(str, ' ');
9928     if (p == NULL) return str;
9929     strncpy(buf, str, p - str);
9930     buf[p - str] = NULLCHAR;
9931     return buf;
9932 }
9933
9934 #define PGN_MAX_LINE 75
9935
9936 #define PGN_SIDE_WHITE  0
9937 #define PGN_SIDE_BLACK  1
9938
9939 /* [AS] */
9940 static int FindFirstMoveOutOfBook( int side )
9941 {
9942     int result = -1;
9943
9944     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9945         int index = backwardMostMove;
9946         int has_book_hit = 0;
9947
9948         if( (index % 2) != side ) {
9949             index++;
9950         }
9951
9952         while( index < forwardMostMove ) {
9953             /* Check to see if engine is in book */
9954             int depth = pvInfoList[index].depth;
9955             int score = pvInfoList[index].score;
9956             int in_book = 0;
9957
9958             if( depth <= 2 ) {
9959                 in_book = 1;
9960             }
9961             else if( score == 0 && depth == 63 ) {
9962                 in_book = 1; /* Zappa */
9963             }
9964             else if( score == 2 && depth == 99 ) {
9965                 in_book = 1; /* Abrok */
9966             }
9967
9968             has_book_hit += in_book;
9969
9970             if( ! in_book ) {
9971                 result = index;
9972
9973                 break;
9974             }
9975
9976             index += 2;
9977         }
9978     }
9979
9980     return result;
9981 }
9982
9983 /* [AS] */
9984 void GetOutOfBookInfo( char * buf )
9985 {
9986     int oob[2];
9987     int i;
9988     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9989
9990     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9991     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9992
9993     *buf = '\0';
9994
9995     if( oob[0] >= 0 || oob[1] >= 0 ) {
9996         for( i=0; i<2; i++ ) {
9997             int idx = oob[i];
9998
9999             if( idx >= 0 ) {
10000                 if( i > 0 && oob[0] >= 0 ) {
10001                     strcat( buf, "   " );
10002                 }
10003
10004                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10005                 sprintf( buf+strlen(buf), "%s%.2f", 
10006                     pvInfoList[idx].score >= 0 ? "+" : "",
10007                     pvInfoList[idx].score / 100.0 );
10008             }
10009         }
10010     }
10011 }
10012
10013 /* Save game in PGN style and close the file */
10014 int
10015 SaveGamePGN(f)
10016      FILE *f;
10017 {
10018     int i, offset, linelen, newblock;
10019     time_t tm;
10020 //    char *movetext;
10021     char numtext[32];
10022     int movelen, numlen, blank;
10023     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10024
10025     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10026     
10027     tm = time((time_t *) NULL);
10028     
10029     PrintPGNTags(f, &gameInfo);
10030     
10031     if (backwardMostMove > 0 || startedFromSetupPosition) {
10032         char *fen = PositionToFEN(backwardMostMove, NULL);
10033         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10034         fprintf(f, "\n{--------------\n");
10035         PrintPosition(f, backwardMostMove);
10036         fprintf(f, "--------------}\n");
10037         free(fen);
10038     }
10039     else {
10040         /* [AS] Out of book annotation */
10041         if( appData.saveOutOfBookInfo ) {
10042             char buf[64];
10043
10044             GetOutOfBookInfo( buf );
10045
10046             if( buf[0] != '\0' ) {
10047                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10048             }
10049         }
10050
10051         fprintf(f, "\n");
10052     }
10053
10054     i = backwardMostMove;
10055     linelen = 0;
10056     newblock = TRUE;
10057
10058     while (i < forwardMostMove) {
10059         /* Print comments preceding this move */
10060         if (commentList[i] != NULL) {
10061             if (linelen > 0) fprintf(f, "\n");
10062             fprintf(f, "%s", commentList[i]);
10063             linelen = 0;
10064             newblock = TRUE;
10065         }
10066
10067         /* Format move number */
10068         if ((i % 2) == 0) {
10069             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10070         } else {
10071             if (newblock) {
10072                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10073             } else {
10074                 numtext[0] = NULLCHAR;
10075             }
10076         }
10077         numlen = strlen(numtext);
10078         newblock = FALSE;
10079
10080         /* Print move number */
10081         blank = linelen > 0 && numlen > 0;
10082         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10083             fprintf(f, "\n");
10084             linelen = 0;
10085             blank = 0;
10086         }
10087         if (blank) {
10088             fprintf(f, " ");
10089             linelen++;
10090         }
10091         fprintf(f, "%s", numtext);
10092         linelen += numlen;
10093
10094         /* Get move */
10095         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10096         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10097
10098         /* Print move */
10099         blank = linelen > 0 && movelen > 0;
10100         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10101             fprintf(f, "\n");
10102             linelen = 0;
10103             blank = 0;
10104         }
10105         if (blank) {
10106             fprintf(f, " ");
10107             linelen++;
10108         }
10109         fprintf(f, "%s", move_buffer);
10110         linelen += movelen;
10111
10112         /* [AS] Add PV info if present */
10113         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10114             /* [HGM] add time */
10115             char buf[MSG_SIZ]; int seconds;
10116
10117             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10118
10119             if( seconds <= 0) buf[0] = 0; else
10120             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10121                 seconds = (seconds + 4)/10; // round to full seconds
10122                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10123                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10124             }
10125
10126             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10127                 pvInfoList[i].score >= 0 ? "+" : "",
10128                 pvInfoList[i].score / 100.0,
10129                 pvInfoList[i].depth,
10130                 buf );
10131
10132             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10133
10134             /* Print score/depth */
10135             blank = linelen > 0 && movelen > 0;
10136             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10137                 fprintf(f, "\n");
10138                 linelen = 0;
10139                 blank = 0;
10140             }
10141             if (blank) {
10142                 fprintf(f, " ");
10143                 linelen++;
10144             }
10145             fprintf(f, "%s", move_buffer);
10146             linelen += movelen;
10147         }
10148
10149         i++;
10150     }
10151     
10152     /* Start a new line */
10153     if (linelen > 0) fprintf(f, "\n");
10154
10155     /* Print comments after last move */
10156     if (commentList[i] != NULL) {
10157         fprintf(f, "%s\n", commentList[i]);
10158     }
10159
10160     /* Print result */
10161     if (gameInfo.resultDetails != NULL &&
10162         gameInfo.resultDetails[0] != NULLCHAR) {
10163         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10164                 PGNResult(gameInfo.result));
10165     } else {
10166         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10167     }
10168
10169     fclose(f);
10170     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10171     return TRUE;
10172 }
10173
10174 /* Save game in old style and close the file */
10175 int
10176 SaveGameOldStyle(f)
10177      FILE *f;
10178 {
10179     int i, offset;
10180     time_t tm;
10181     
10182     tm = time((time_t *) NULL);
10183     
10184     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10185     PrintOpponents(f);
10186     
10187     if (backwardMostMove > 0 || startedFromSetupPosition) {
10188         fprintf(f, "\n[--------------\n");
10189         PrintPosition(f, backwardMostMove);
10190         fprintf(f, "--------------]\n");
10191     } else {
10192         fprintf(f, "\n");
10193     }
10194
10195     i = backwardMostMove;
10196     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10197
10198     while (i < forwardMostMove) {
10199         if (commentList[i] != NULL) {
10200             fprintf(f, "[%s]\n", commentList[i]);
10201         }
10202
10203         if ((i % 2) == 1) {
10204             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10205             i++;
10206         } else {
10207             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10208             i++;
10209             if (commentList[i] != NULL) {
10210                 fprintf(f, "\n");
10211                 continue;
10212             }
10213             if (i >= forwardMostMove) {
10214                 fprintf(f, "\n");
10215                 break;
10216             }
10217             fprintf(f, "%s\n", parseList[i]);
10218             i++;
10219         }
10220     }
10221     
10222     if (commentList[i] != NULL) {
10223         fprintf(f, "[%s]\n", commentList[i]);
10224     }
10225
10226     /* This isn't really the old style, but it's close enough */
10227     if (gameInfo.resultDetails != NULL &&
10228         gameInfo.resultDetails[0] != NULLCHAR) {
10229         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10230                 gameInfo.resultDetails);
10231     } else {
10232         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10233     }
10234
10235     fclose(f);
10236     return TRUE;
10237 }
10238
10239 /* Save the current game to open file f and close the file */
10240 int
10241 SaveGame(f, dummy, dummy2)
10242      FILE *f;
10243      int dummy;
10244      char *dummy2;
10245 {
10246     if (gameMode == EditPosition) EditPositionDone(TRUE);
10247     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10248     if (appData.oldSaveStyle)
10249       return SaveGameOldStyle(f);
10250     else
10251       return SaveGamePGN(f);
10252 }
10253
10254 /* Save the current position to the given file */
10255 int
10256 SavePositionToFile(filename)
10257      char *filename;
10258 {
10259     FILE *f;
10260     char buf[MSG_SIZ];
10261
10262     if (strcmp(filename, "-") == 0) {
10263         return SavePosition(stdout, 0, NULL);
10264     } else {
10265         f = fopen(filename, "a");
10266         if (f == NULL) {
10267             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10268             DisplayError(buf, errno);
10269             return FALSE;
10270         } else {
10271             SavePosition(f, 0, NULL);
10272             return TRUE;
10273         }
10274     }
10275 }
10276
10277 /* Save the current position to the given open file and close the file */
10278 int
10279 SavePosition(f, dummy, dummy2)
10280      FILE *f;
10281      int dummy;
10282      char *dummy2;
10283 {
10284     time_t tm;
10285     char *fen;
10286     
10287     if (gameMode == EditPosition) EditPositionDone(TRUE);
10288     if (appData.oldSaveStyle) {
10289         tm = time((time_t *) NULL);
10290     
10291         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10292         PrintOpponents(f);
10293         fprintf(f, "[--------------\n");
10294         PrintPosition(f, currentMove);
10295         fprintf(f, "--------------]\n");
10296     } else {
10297         fen = PositionToFEN(currentMove, NULL);
10298         fprintf(f, "%s\n", fen);
10299         free(fen);
10300     }
10301     fclose(f);
10302     return TRUE;
10303 }
10304
10305 void
10306 ReloadCmailMsgEvent(unregister)
10307      int unregister;
10308 {
10309 #if !WIN32
10310     static char *inFilename = NULL;
10311     static char *outFilename;
10312     int i;
10313     struct stat inbuf, outbuf;
10314     int status;
10315     
10316     /* Any registered moves are unregistered if unregister is set, */
10317     /* i.e. invoked by the signal handler */
10318     if (unregister) {
10319         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10320             cmailMoveRegistered[i] = FALSE;
10321             if (cmailCommentList[i] != NULL) {
10322                 free(cmailCommentList[i]);
10323                 cmailCommentList[i] = NULL;
10324             }
10325         }
10326         nCmailMovesRegistered = 0;
10327     }
10328
10329     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10330         cmailResult[i] = CMAIL_NOT_RESULT;
10331     }
10332     nCmailResults = 0;
10333
10334     if (inFilename == NULL) {
10335         /* Because the filenames are static they only get malloced once  */
10336         /* and they never get freed                                      */
10337         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10338         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10339
10340         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10341         sprintf(outFilename, "%s.out", appData.cmailGameName);
10342     }
10343     
10344     status = stat(outFilename, &outbuf);
10345     if (status < 0) {
10346         cmailMailedMove = FALSE;
10347     } else {
10348         status = stat(inFilename, &inbuf);
10349         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10350     }
10351     
10352     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10353        counts the games, notes how each one terminated, etc.
10354        
10355        It would be nice to remove this kludge and instead gather all
10356        the information while building the game list.  (And to keep it
10357        in the game list nodes instead of having a bunch of fixed-size
10358        parallel arrays.)  Note this will require getting each game's
10359        termination from the PGN tags, as the game list builder does
10360        not process the game moves.  --mann
10361        */
10362     cmailMsgLoaded = TRUE;
10363     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10364     
10365     /* Load first game in the file or popup game menu */
10366     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10367
10368 #endif /* !WIN32 */
10369     return;
10370 }
10371
10372 int
10373 RegisterMove()
10374 {
10375     FILE *f;
10376     char string[MSG_SIZ];
10377
10378     if (   cmailMailedMove
10379         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10380         return TRUE;            /* Allow free viewing  */
10381     }
10382
10383     /* Unregister move to ensure that we don't leave RegisterMove        */
10384     /* with the move registered when the conditions for registering no   */
10385     /* longer hold                                                       */
10386     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10387         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10388         nCmailMovesRegistered --;
10389
10390         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10391           {
10392               free(cmailCommentList[lastLoadGameNumber - 1]);
10393               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10394           }
10395     }
10396
10397     if (cmailOldMove == -1) {
10398         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10399         return FALSE;
10400     }
10401
10402     if (currentMove > cmailOldMove + 1) {
10403         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10404         return FALSE;
10405     }
10406
10407     if (currentMove < cmailOldMove) {
10408         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10409         return FALSE;
10410     }
10411
10412     if (forwardMostMove > currentMove) {
10413         /* Silently truncate extra moves */
10414         TruncateGame();
10415     }
10416
10417     if (   (currentMove == cmailOldMove + 1)
10418         || (   (currentMove == cmailOldMove)
10419             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10420                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10421         if (gameInfo.result != GameUnfinished) {
10422             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10423         }
10424
10425         if (commentList[currentMove] != NULL) {
10426             cmailCommentList[lastLoadGameNumber - 1]
10427               = StrSave(commentList[currentMove]);
10428         }
10429         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10430
10431         if (appData.debugMode)
10432           fprintf(debugFP, "Saving %s for game %d\n",
10433                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10434
10435         sprintf(string,
10436                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10437         
10438         f = fopen(string, "w");
10439         if (appData.oldSaveStyle) {
10440             SaveGameOldStyle(f); /* also closes the file */
10441             
10442             sprintf(string, "%s.pos.out", appData.cmailGameName);
10443             f = fopen(string, "w");
10444             SavePosition(f, 0, NULL); /* also closes the file */
10445         } else {
10446             fprintf(f, "{--------------\n");
10447             PrintPosition(f, currentMove);
10448             fprintf(f, "--------------}\n\n");
10449             
10450             SaveGame(f, 0, NULL); /* also closes the file*/
10451         }
10452         
10453         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10454         nCmailMovesRegistered ++;
10455     } else if (nCmailGames == 1) {
10456         DisplayError(_("You have not made a move yet"), 0);
10457         return FALSE;
10458     }
10459
10460     return TRUE;
10461 }
10462
10463 void
10464 MailMoveEvent()
10465 {
10466 #if !WIN32
10467     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10468     FILE *commandOutput;
10469     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10470     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10471     int nBuffers;
10472     int i;
10473     int archived;
10474     char *arcDir;
10475
10476     if (! cmailMsgLoaded) {
10477         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10478         return;
10479     }
10480
10481     if (nCmailGames == nCmailResults) {
10482         DisplayError(_("No unfinished games"), 0);
10483         return;
10484     }
10485
10486 #if CMAIL_PROHIBIT_REMAIL
10487     if (cmailMailedMove) {
10488         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);
10489         DisplayError(msg, 0);
10490         return;
10491     }
10492 #endif
10493
10494     if (! (cmailMailedMove || RegisterMove())) return;
10495     
10496     if (   cmailMailedMove
10497         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10498         sprintf(string, partCommandString,
10499                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10500         commandOutput = popen(string, "r");
10501
10502         if (commandOutput == NULL) {
10503             DisplayError(_("Failed to invoke cmail"), 0);
10504         } else {
10505             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10506                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10507             }
10508             if (nBuffers > 1) {
10509                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10510                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10511                 nBytes = MSG_SIZ - 1;
10512             } else {
10513                 (void) memcpy(msg, buffer, nBytes);
10514             }
10515             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10516
10517             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10518                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10519
10520                 archived = TRUE;
10521                 for (i = 0; i < nCmailGames; i ++) {
10522                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10523                         archived = FALSE;
10524                     }
10525                 }
10526                 if (   archived
10527                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10528                         != NULL)) {
10529                     sprintf(buffer, "%s/%s.%s.archive",
10530                             arcDir,
10531                             appData.cmailGameName,
10532                             gameInfo.date);
10533                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10534                     cmailMsgLoaded = FALSE;
10535                 }
10536             }
10537
10538             DisplayInformation(msg);
10539             pclose(commandOutput);
10540         }
10541     } else {
10542         if ((*cmailMsg) != '\0') {
10543             DisplayInformation(cmailMsg);
10544         }
10545     }
10546
10547     return;
10548 #endif /* !WIN32 */
10549 }
10550
10551 char *
10552 CmailMsg()
10553 {
10554 #if WIN32
10555     return NULL;
10556 #else
10557     int  prependComma = 0;
10558     char number[5];
10559     char string[MSG_SIZ];       /* Space for game-list */
10560     int  i;
10561     
10562     if (!cmailMsgLoaded) return "";
10563
10564     if (cmailMailedMove) {
10565         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10566     } else {
10567         /* Create a list of games left */
10568         sprintf(string, "[");
10569         for (i = 0; i < nCmailGames; i ++) {
10570             if (! (   cmailMoveRegistered[i]
10571                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10572                 if (prependComma) {
10573                     sprintf(number, ",%d", i + 1);
10574                 } else {
10575                     sprintf(number, "%d", i + 1);
10576                     prependComma = 1;
10577                 }
10578                 
10579                 strcat(string, number);
10580             }
10581         }
10582         strcat(string, "]");
10583
10584         if (nCmailMovesRegistered + nCmailResults == 0) {
10585             switch (nCmailGames) {
10586               case 1:
10587                 sprintf(cmailMsg,
10588                         _("Still need to make move for game\n"));
10589                 break;
10590                 
10591               case 2:
10592                 sprintf(cmailMsg,
10593                         _("Still need to make moves for both games\n"));
10594                 break;
10595                 
10596               default:
10597                 sprintf(cmailMsg,
10598                         _("Still need to make moves for all %d games\n"),
10599                         nCmailGames);
10600                 break;
10601             }
10602         } else {
10603             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10604               case 1:
10605                 sprintf(cmailMsg,
10606                         _("Still need to make a move for game %s\n"),
10607                         string);
10608                 break;
10609                 
10610               case 0:
10611                 if (nCmailResults == nCmailGames) {
10612                     sprintf(cmailMsg, _("No unfinished games\n"));
10613                 } else {
10614                     sprintf(cmailMsg, _("Ready to send mail\n"));
10615                 }
10616                 break;
10617                 
10618               default:
10619                 sprintf(cmailMsg,
10620                         _("Still need to make moves for games %s\n"),
10621                         string);
10622             }
10623         }
10624     }
10625     return cmailMsg;
10626 #endif /* WIN32 */
10627 }
10628
10629 void
10630 ResetGameEvent()
10631 {
10632     if (gameMode == Training)
10633       SetTrainingModeOff();
10634
10635     Reset(TRUE, TRUE);
10636     cmailMsgLoaded = FALSE;
10637     if (appData.icsActive) {
10638       SendToICS(ics_prefix);
10639       SendToICS("refresh\n");
10640     }
10641 }
10642
10643 void
10644 ExitEvent(status)
10645      int status;
10646 {
10647     exiting++;
10648     if (exiting > 2) {
10649       /* Give up on clean exit */
10650       exit(status);
10651     }
10652     if (exiting > 1) {
10653       /* Keep trying for clean exit */
10654       return;
10655     }
10656
10657     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10658
10659     if (telnetISR != NULL) {
10660       RemoveInputSource(telnetISR);
10661     }
10662     if (icsPR != NoProc) {
10663       DestroyChildProcess(icsPR, TRUE);
10664     }
10665
10666     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10667     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10668
10669     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10670     /* make sure this other one finishes before killing it!                  */
10671     if(endingGame) { int count = 0;
10672         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10673         while(endingGame && count++ < 10) DoSleep(1);
10674         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10675     }
10676
10677     /* Kill off chess programs */
10678     if (first.pr != NoProc) {
10679         ExitAnalyzeMode();
10680         
10681         DoSleep( appData.delayBeforeQuit );
10682         SendToProgram("quit\n", &first);
10683         DoSleep( appData.delayAfterQuit );
10684         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10685     }
10686     if (second.pr != NoProc) {
10687         DoSleep( appData.delayBeforeQuit );
10688         SendToProgram("quit\n", &second);
10689         DoSleep( appData.delayAfterQuit );
10690         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10691     }
10692     if (first.isr != NULL) {
10693         RemoveInputSource(first.isr);
10694     }
10695     if (second.isr != NULL) {
10696         RemoveInputSource(second.isr);
10697     }
10698
10699     ShutDownFrontEnd();
10700     exit(status);
10701 }
10702
10703 void
10704 PauseEvent()
10705 {
10706     if (appData.debugMode)
10707         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10708     if (pausing) {
10709         pausing = FALSE;
10710         ModeHighlight();
10711         if (gameMode == MachinePlaysWhite ||
10712             gameMode == MachinePlaysBlack) {
10713             StartClocks();
10714         } else {
10715             DisplayBothClocks();
10716         }
10717         if (gameMode == PlayFromGameFile) {
10718             if (appData.timeDelay >= 0) 
10719                 AutoPlayGameLoop();
10720         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10721             Reset(FALSE, TRUE);
10722             SendToICS(ics_prefix);
10723             SendToICS("refresh\n");
10724         } else if (currentMove < forwardMostMove) {
10725             ForwardInner(forwardMostMove);
10726         }
10727         pauseExamInvalid = FALSE;
10728     } else {
10729         switch (gameMode) {
10730           default:
10731             return;
10732           case IcsExamining:
10733             pauseExamForwardMostMove = forwardMostMove;
10734             pauseExamInvalid = FALSE;
10735             /* fall through */
10736           case IcsObserving:
10737           case IcsPlayingWhite:
10738           case IcsPlayingBlack:
10739             pausing = TRUE;
10740             ModeHighlight();
10741             return;
10742           case PlayFromGameFile:
10743             (void) StopLoadGameTimer();
10744             pausing = TRUE;
10745             ModeHighlight();
10746             break;
10747           case BeginningOfGame:
10748             if (appData.icsActive) return;
10749             /* else fall through */
10750           case MachinePlaysWhite:
10751           case MachinePlaysBlack:
10752           case TwoMachinesPlay:
10753             if (forwardMostMove == 0)
10754               return;           /* don't pause if no one has moved */
10755             if ((gameMode == MachinePlaysWhite &&
10756                  !WhiteOnMove(forwardMostMove)) ||
10757                 (gameMode == MachinePlaysBlack &&
10758                  WhiteOnMove(forwardMostMove))) {
10759                 StopClocks();
10760             }
10761             pausing = TRUE;
10762             ModeHighlight();
10763             break;
10764         }
10765     }
10766 }
10767
10768 void
10769 EditCommentEvent()
10770 {
10771     char title[MSG_SIZ];
10772
10773     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10774         strcpy(title, _("Edit comment"));
10775     } else {
10776         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10777                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10778                 parseList[currentMove - 1]);
10779     }
10780
10781     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10782 }
10783
10784
10785 void
10786 EditTagsEvent()
10787 {
10788     char *tags = PGNTags(&gameInfo);
10789     EditTagsPopUp(tags);
10790     free(tags);
10791 }
10792
10793 void
10794 AnalyzeModeEvent()
10795 {
10796     if (appData.noChessProgram || gameMode == AnalyzeMode)
10797       return;
10798
10799     if (gameMode != AnalyzeFile) {
10800         if (!appData.icsEngineAnalyze) {
10801                EditGameEvent();
10802                if (gameMode != EditGame) return;
10803         }
10804         ResurrectChessProgram();
10805         SendToProgram("analyze\n", &first);
10806         first.analyzing = TRUE;
10807         /*first.maybeThinking = TRUE;*/
10808         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10809         EngineOutputPopUp();
10810     }
10811     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10812     pausing = FALSE;
10813     ModeHighlight();
10814     SetGameInfo();
10815
10816     StartAnalysisClock();
10817     GetTimeMark(&lastNodeCountTime);
10818     lastNodeCount = 0;
10819 }
10820
10821 void
10822 AnalyzeFileEvent()
10823 {
10824     if (appData.noChessProgram || gameMode == AnalyzeFile)
10825       return;
10826
10827     if (gameMode != AnalyzeMode) {
10828         EditGameEvent();
10829         if (gameMode != EditGame) return;
10830         ResurrectChessProgram();
10831         SendToProgram("analyze\n", &first);
10832         first.analyzing = TRUE;
10833         /*first.maybeThinking = TRUE;*/
10834         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10835         EngineOutputPopUp();
10836     }
10837     gameMode = AnalyzeFile;
10838     pausing = FALSE;
10839     ModeHighlight();
10840     SetGameInfo();
10841
10842     StartAnalysisClock();
10843     GetTimeMark(&lastNodeCountTime);
10844     lastNodeCount = 0;
10845 }
10846
10847 void
10848 MachineWhiteEvent()
10849 {
10850     char buf[MSG_SIZ];
10851     char *bookHit = NULL;
10852
10853     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10854       return;
10855
10856
10857     if (gameMode == PlayFromGameFile || 
10858         gameMode == TwoMachinesPlay  || 
10859         gameMode == Training         || 
10860         gameMode == AnalyzeMode      || 
10861         gameMode == EndOfGame)
10862         EditGameEvent();
10863
10864     if (gameMode == EditPosition) 
10865         EditPositionDone(TRUE);
10866
10867     if (!WhiteOnMove(currentMove)) {
10868         DisplayError(_("It is not White's turn"), 0);
10869         return;
10870     }
10871   
10872     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10873       ExitAnalyzeMode();
10874
10875     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10876         gameMode == AnalyzeFile)
10877         TruncateGame();
10878
10879     ResurrectChessProgram();    /* in case it isn't running */
10880     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10881         gameMode = MachinePlaysWhite;
10882         ResetClocks();
10883     } else
10884     gameMode = MachinePlaysWhite;
10885     pausing = FALSE;
10886     ModeHighlight();
10887     SetGameInfo();
10888     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10889     DisplayTitle(buf);
10890     if (first.sendName) {
10891       sprintf(buf, "name %s\n", gameInfo.black);
10892       SendToProgram(buf, &first);
10893     }
10894     if (first.sendTime) {
10895       if (first.useColors) {
10896         SendToProgram("black\n", &first); /*gnu kludge*/
10897       }
10898       SendTimeRemaining(&first, TRUE);
10899     }
10900     if (first.useColors) {
10901       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10902     }
10903     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10904     SetMachineThinkingEnables();
10905     first.maybeThinking = TRUE;
10906     StartClocks();
10907     firstMove = FALSE;
10908
10909     if (appData.autoFlipView && !flipView) {
10910       flipView = !flipView;
10911       DrawPosition(FALSE, NULL);
10912       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10913     }
10914
10915     if(bookHit) { // [HGM] book: simulate book reply
10916         static char bookMove[MSG_SIZ]; // a bit generous?
10917
10918         programStats.nodes = programStats.depth = programStats.time = 
10919         programStats.score = programStats.got_only_move = 0;
10920         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10921
10922         strcpy(bookMove, "move ");
10923         strcat(bookMove, bookHit);
10924         HandleMachineMove(bookMove, &first);
10925     }
10926 }
10927
10928 void
10929 MachineBlackEvent()
10930 {
10931     char buf[MSG_SIZ];
10932    char *bookHit = NULL;
10933
10934     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10935         return;
10936
10937
10938     if (gameMode == PlayFromGameFile || 
10939         gameMode == TwoMachinesPlay  || 
10940         gameMode == Training         || 
10941         gameMode == AnalyzeMode      || 
10942         gameMode == EndOfGame)
10943         EditGameEvent();
10944
10945     if (gameMode == EditPosition) 
10946         EditPositionDone(TRUE);
10947
10948     if (WhiteOnMove(currentMove)) {
10949         DisplayError(_("It is not Black's turn"), 0);
10950         return;
10951     }
10952     
10953     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10954       ExitAnalyzeMode();
10955
10956     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10957         gameMode == AnalyzeFile)
10958         TruncateGame();
10959
10960     ResurrectChessProgram();    /* in case it isn't running */
10961     gameMode = MachinePlaysBlack;
10962     pausing = FALSE;
10963     ModeHighlight();
10964     SetGameInfo();
10965     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10966     DisplayTitle(buf);
10967     if (first.sendName) {
10968       sprintf(buf, "name %s\n", gameInfo.white);
10969       SendToProgram(buf, &first);
10970     }
10971     if (first.sendTime) {
10972       if (first.useColors) {
10973         SendToProgram("white\n", &first); /*gnu kludge*/
10974       }
10975       SendTimeRemaining(&first, FALSE);
10976     }
10977     if (first.useColors) {
10978       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10979     }
10980     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10981     SetMachineThinkingEnables();
10982     first.maybeThinking = TRUE;
10983     StartClocks();
10984
10985     if (appData.autoFlipView && flipView) {
10986       flipView = !flipView;
10987       DrawPosition(FALSE, NULL);
10988       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10989     }
10990     if(bookHit) { // [HGM] book: simulate book reply
10991         static char bookMove[MSG_SIZ]; // a bit generous?
10992
10993         programStats.nodes = programStats.depth = programStats.time = 
10994         programStats.score = programStats.got_only_move = 0;
10995         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10996
10997         strcpy(bookMove, "move ");
10998         strcat(bookMove, bookHit);
10999         HandleMachineMove(bookMove, &first);
11000     }
11001 }
11002
11003
11004 void
11005 DisplayTwoMachinesTitle()
11006 {
11007     char buf[MSG_SIZ];
11008     if (appData.matchGames > 0) {
11009         if (first.twoMachinesColor[0] == 'w') {
11010             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11011                     gameInfo.white, gameInfo.black,
11012                     first.matchWins, second.matchWins,
11013                     matchGame - 1 - (first.matchWins + second.matchWins));
11014         } else {
11015             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11016                     gameInfo.white, gameInfo.black,
11017                     second.matchWins, first.matchWins,
11018                     matchGame - 1 - (first.matchWins + second.matchWins));
11019         }
11020     } else {
11021         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11022     }
11023     DisplayTitle(buf);
11024 }
11025
11026 void
11027 TwoMachinesEvent P((void))
11028 {
11029     int i;
11030     char buf[MSG_SIZ];
11031     ChessProgramState *onmove;
11032     char *bookHit = NULL;
11033     
11034     if (appData.noChessProgram) return;
11035
11036     switch (gameMode) {
11037       case TwoMachinesPlay:
11038         return;
11039       case MachinePlaysWhite:
11040       case MachinePlaysBlack:
11041         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11042             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11043             return;
11044         }
11045         /* fall through */
11046       case BeginningOfGame:
11047       case PlayFromGameFile:
11048       case EndOfGame:
11049         EditGameEvent();
11050         if (gameMode != EditGame) return;
11051         break;
11052       case EditPosition:
11053         EditPositionDone(TRUE);
11054         break;
11055       case AnalyzeMode:
11056       case AnalyzeFile:
11057         ExitAnalyzeMode();
11058         break;
11059       case EditGame:
11060       default:
11061         break;
11062     }
11063
11064 //    forwardMostMove = currentMove;
11065     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11066     ResurrectChessProgram();    /* in case first program isn't running */
11067
11068     if (second.pr == NULL) {
11069         StartChessProgram(&second);
11070         if (second.protocolVersion == 1) {
11071           TwoMachinesEventIfReady();
11072         } else {
11073           /* kludge: allow timeout for initial "feature" command */
11074           FreezeUI();
11075           DisplayMessage("", _("Starting second chess program"));
11076           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11077         }
11078         return;
11079     }
11080     DisplayMessage("", "");
11081     InitChessProgram(&second, FALSE);
11082     SendToProgram("force\n", &second);
11083     if (startedFromSetupPosition) {
11084         SendBoard(&second, backwardMostMove);
11085     if (appData.debugMode) {
11086         fprintf(debugFP, "Two Machines\n");
11087     }
11088     }
11089     for (i = backwardMostMove; i < forwardMostMove; i++) {
11090         SendMoveToProgram(i, &second);
11091     }
11092
11093     gameMode = TwoMachinesPlay;
11094     pausing = FALSE;
11095     ModeHighlight();
11096     SetGameInfo();
11097     DisplayTwoMachinesTitle();
11098     firstMove = TRUE;
11099     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11100         onmove = &first;
11101     } else {
11102         onmove = &second;
11103     }
11104
11105     SendToProgram(first.computerString, &first);
11106     if (first.sendName) {
11107       sprintf(buf, "name %s\n", second.tidy);
11108       SendToProgram(buf, &first);
11109     }
11110     SendToProgram(second.computerString, &second);
11111     if (second.sendName) {
11112       sprintf(buf, "name %s\n", first.tidy);
11113       SendToProgram(buf, &second);
11114     }
11115
11116     ResetClocks();
11117     if (!first.sendTime || !second.sendTime) {
11118         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11119         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11120     }
11121     if (onmove->sendTime) {
11122       if (onmove->useColors) {
11123         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11124       }
11125       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11126     }
11127     if (onmove->useColors) {
11128       SendToProgram(onmove->twoMachinesColor, onmove);
11129     }
11130     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11131 //    SendToProgram("go\n", onmove);
11132     onmove->maybeThinking = TRUE;
11133     SetMachineThinkingEnables();
11134
11135     StartClocks();
11136
11137     if(bookHit) { // [HGM] book: simulate book reply
11138         static char bookMove[MSG_SIZ]; // a bit generous?
11139
11140         programStats.nodes = programStats.depth = programStats.time = 
11141         programStats.score = programStats.got_only_move = 0;
11142         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11143
11144         strcpy(bookMove, "move ");
11145         strcat(bookMove, bookHit);
11146         savedMessage = bookMove; // args for deferred call
11147         savedState = onmove;
11148         ScheduleDelayedEvent(DeferredBookMove, 1);
11149     }
11150 }
11151
11152 void
11153 TrainingEvent()
11154 {
11155     if (gameMode == Training) {
11156       SetTrainingModeOff();
11157       gameMode = PlayFromGameFile;
11158       DisplayMessage("", _("Training mode off"));
11159     } else {
11160       gameMode = Training;
11161       animateTraining = appData.animate;
11162
11163       /* make sure we are not already at the end of the game */
11164       if (currentMove < forwardMostMove) {
11165         SetTrainingModeOn();
11166         DisplayMessage("", _("Training mode on"));
11167       } else {
11168         gameMode = PlayFromGameFile;
11169         DisplayError(_("Already at end of game"), 0);
11170       }
11171     }
11172     ModeHighlight();
11173 }
11174
11175 void
11176 IcsClientEvent()
11177 {
11178     if (!appData.icsActive) return;
11179     switch (gameMode) {
11180       case IcsPlayingWhite:
11181       case IcsPlayingBlack:
11182       case IcsObserving:
11183       case IcsIdle:
11184       case BeginningOfGame:
11185       case IcsExamining:
11186         return;
11187
11188       case EditGame:
11189         break;
11190
11191       case EditPosition:
11192         EditPositionDone(TRUE);
11193         break;
11194
11195       case AnalyzeMode:
11196       case AnalyzeFile:
11197         ExitAnalyzeMode();
11198         break;
11199         
11200       default:
11201         EditGameEvent();
11202         break;
11203     }
11204
11205     gameMode = IcsIdle;
11206     ModeHighlight();
11207     return;
11208 }
11209
11210
11211 void
11212 EditGameEvent()
11213 {
11214     int i;
11215
11216     switch (gameMode) {
11217       case Training:
11218         SetTrainingModeOff();
11219         break;
11220       case MachinePlaysWhite:
11221       case MachinePlaysBlack:
11222       case BeginningOfGame:
11223         SendToProgram("force\n", &first);
11224         SetUserThinkingEnables();
11225         break;
11226       case PlayFromGameFile:
11227         (void) StopLoadGameTimer();
11228         if (gameFileFP != NULL) {
11229             gameFileFP = NULL;
11230         }
11231         break;
11232       case EditPosition:
11233         EditPositionDone(TRUE);
11234         break;
11235       case AnalyzeMode:
11236       case AnalyzeFile:
11237         ExitAnalyzeMode();
11238         SendToProgram("force\n", &first);
11239         break;
11240       case TwoMachinesPlay:
11241         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11242         ResurrectChessProgram();
11243         SetUserThinkingEnables();
11244         break;
11245       case EndOfGame:
11246         ResurrectChessProgram();
11247         break;
11248       case IcsPlayingBlack:
11249       case IcsPlayingWhite:
11250         DisplayError(_("Warning: You are still playing a game"), 0);
11251         break;
11252       case IcsObserving:
11253         DisplayError(_("Warning: You are still observing a game"), 0);
11254         break;
11255       case IcsExamining:
11256         DisplayError(_("Warning: You are still examining a game"), 0);
11257         break;
11258       case IcsIdle:
11259         break;
11260       case EditGame:
11261       default:
11262         return;
11263     }
11264     
11265     pausing = FALSE;
11266     StopClocks();
11267     first.offeredDraw = second.offeredDraw = 0;
11268
11269     if (gameMode == PlayFromGameFile) {
11270         whiteTimeRemaining = timeRemaining[0][currentMove];
11271         blackTimeRemaining = timeRemaining[1][currentMove];
11272         DisplayTitle("");
11273     }
11274
11275     if (gameMode == MachinePlaysWhite ||
11276         gameMode == MachinePlaysBlack ||
11277         gameMode == TwoMachinesPlay ||
11278         gameMode == EndOfGame) {
11279         i = forwardMostMove;
11280         while (i > currentMove) {
11281             SendToProgram("undo\n", &first);
11282             i--;
11283         }
11284         whiteTimeRemaining = timeRemaining[0][currentMove];
11285         blackTimeRemaining = timeRemaining[1][currentMove];
11286         DisplayBothClocks();
11287         if (whiteFlag || blackFlag) {
11288             whiteFlag = blackFlag = 0;
11289         }
11290         DisplayTitle("");
11291     }           
11292     
11293     gameMode = EditGame;
11294     ModeHighlight();
11295     SetGameInfo();
11296 }
11297
11298
11299 void
11300 EditPositionEvent()
11301 {
11302     if (gameMode == EditPosition) {
11303         EditGameEvent();
11304         return;
11305     }
11306     
11307     EditGameEvent();
11308     if (gameMode != EditGame) return;
11309     
11310     gameMode = EditPosition;
11311     ModeHighlight();
11312     SetGameInfo();
11313     if (currentMove > 0)
11314       CopyBoard(boards[0], boards[currentMove]);
11315     
11316     blackPlaysFirst = !WhiteOnMove(currentMove);
11317     ResetClocks();
11318     currentMove = forwardMostMove = backwardMostMove = 0;
11319     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11320     DisplayMove(-1);
11321 }
11322
11323 void
11324 ExitAnalyzeMode()
11325 {
11326     /* [DM] icsEngineAnalyze - possible call from other functions */
11327     if (appData.icsEngineAnalyze) {
11328         appData.icsEngineAnalyze = FALSE;
11329
11330         DisplayMessage("",_("Close ICS engine analyze..."));
11331     }
11332     if (first.analysisSupport && first.analyzing) {
11333       SendToProgram("exit\n", &first);
11334       first.analyzing = FALSE;
11335     }
11336     thinkOutput[0] = NULLCHAR;
11337 }
11338
11339 void
11340 EditPositionDone(Boolean fakeRights)
11341 {
11342     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11343
11344     startedFromSetupPosition = TRUE;
11345     InitChessProgram(&first, FALSE);
11346     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11347       boards[0][EP_STATUS] = EP_NONE;
11348       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11349     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11350         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11351         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11352       } else boards[0][CASTLING][2] = NoRights;
11353     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11354         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11355         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11356       } else boards[0][CASTLING][5] = NoRights;
11357     }
11358     SendToProgram("force\n", &first);
11359     if (blackPlaysFirst) {
11360         strcpy(moveList[0], "");
11361         strcpy(parseList[0], "");
11362         currentMove = forwardMostMove = backwardMostMove = 1;
11363         CopyBoard(boards[1], boards[0]);
11364     } else {
11365         currentMove = forwardMostMove = backwardMostMove = 0;
11366     }
11367     SendBoard(&first, forwardMostMove);
11368     if (appData.debugMode) {
11369         fprintf(debugFP, "EditPosDone\n");
11370     }
11371     DisplayTitle("");
11372     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11373     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11374     gameMode = EditGame;
11375     ModeHighlight();
11376     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11377     ClearHighlights(); /* [AS] */
11378 }
11379
11380 /* Pause for `ms' milliseconds */
11381 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11382 void
11383 TimeDelay(ms)
11384      long ms;
11385 {
11386     TimeMark m1, m2;
11387
11388     GetTimeMark(&m1);
11389     do {
11390         GetTimeMark(&m2);
11391     } while (SubtractTimeMarks(&m2, &m1) < ms);
11392 }
11393
11394 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11395 void
11396 SendMultiLineToICS(buf)
11397      char *buf;
11398 {
11399     char temp[MSG_SIZ+1], *p;
11400     int len;
11401
11402     len = strlen(buf);
11403     if (len > MSG_SIZ)
11404       len = MSG_SIZ;
11405   
11406     strncpy(temp, buf, len);
11407     temp[len] = 0;
11408
11409     p = temp;
11410     while (*p) {
11411         if (*p == '\n' || *p == '\r')
11412           *p = ' ';
11413         ++p;
11414     }
11415
11416     strcat(temp, "\n");
11417     SendToICS(temp);
11418     SendToPlayer(temp, strlen(temp));
11419 }
11420
11421 void
11422 SetWhiteToPlayEvent()
11423 {
11424     if (gameMode == EditPosition) {
11425         blackPlaysFirst = FALSE;
11426         DisplayBothClocks();    /* works because currentMove is 0 */
11427     } else if (gameMode == IcsExamining) {
11428         SendToICS(ics_prefix);
11429         SendToICS("tomove white\n");
11430     }
11431 }
11432
11433 void
11434 SetBlackToPlayEvent()
11435 {
11436     if (gameMode == EditPosition) {
11437         blackPlaysFirst = TRUE;
11438         currentMove = 1;        /* kludge */
11439         DisplayBothClocks();
11440         currentMove = 0;
11441     } else if (gameMode == IcsExamining) {
11442         SendToICS(ics_prefix);
11443         SendToICS("tomove black\n");
11444     }
11445 }
11446
11447 void
11448 EditPositionMenuEvent(selection, x, y)
11449      ChessSquare selection;
11450      int x, y;
11451 {
11452     char buf[MSG_SIZ];
11453     ChessSquare piece = boards[0][y][x];
11454
11455     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11456
11457     switch (selection) {
11458       case ClearBoard:
11459         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11460             SendToICS(ics_prefix);
11461             SendToICS("bsetup clear\n");
11462         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11463             SendToICS(ics_prefix);
11464             SendToICS("clearboard\n");
11465         } else {
11466             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11467                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11468                 for (y = 0; y < BOARD_HEIGHT; y++) {
11469                     if (gameMode == IcsExamining) {
11470                         if (boards[currentMove][y][x] != EmptySquare) {
11471                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11472                                     AAA + x, ONE + y);
11473                             SendToICS(buf);
11474                         }
11475                     } else {
11476                         boards[0][y][x] = p;
11477                     }
11478                 }
11479             }
11480         }
11481         if (gameMode == EditPosition) {
11482             DrawPosition(FALSE, boards[0]);
11483         }
11484         break;
11485
11486       case WhitePlay:
11487         SetWhiteToPlayEvent();
11488         break;
11489
11490       case BlackPlay:
11491         SetBlackToPlayEvent();
11492         break;
11493
11494       case EmptySquare:
11495         if (gameMode == IcsExamining) {
11496             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11497             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11498             SendToICS(buf);
11499         } else {
11500             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11501                 if(x == BOARD_LEFT-2) {
11502                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11503                     boards[0][y][1] = 0;
11504                 } else
11505                 if(x == BOARD_RGHT+1) {
11506                     if(y >= gameInfo.holdingsSize) break;
11507                     boards[0][y][BOARD_WIDTH-2] = 0;
11508                 } else break;
11509             }
11510             boards[0][y][x] = EmptySquare;
11511             DrawPosition(FALSE, boards[0]);
11512         }
11513         break;
11514
11515       case PromotePiece:
11516         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11517            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11518             selection = (ChessSquare) (PROMOTED piece);
11519         } else if(piece == EmptySquare) selection = WhiteSilver;
11520         else selection = (ChessSquare)((int)piece - 1);
11521         goto defaultlabel;
11522
11523       case DemotePiece:
11524         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11525            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11526             selection = (ChessSquare) (DEMOTED piece);
11527         } else if(piece == EmptySquare) selection = BlackSilver;
11528         else selection = (ChessSquare)((int)piece + 1);       
11529         goto defaultlabel;
11530
11531       case WhiteQueen:
11532       case BlackQueen:
11533         if(gameInfo.variant == VariantShatranj ||
11534            gameInfo.variant == VariantXiangqi  ||
11535            gameInfo.variant == VariantCourier    )
11536             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11537         goto defaultlabel;
11538
11539       case WhiteKing:
11540       case BlackKing:
11541         if(gameInfo.variant == VariantXiangqi)
11542             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11543         if(gameInfo.variant == VariantKnightmate)
11544             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11545       default:
11546         defaultlabel:
11547         if (gameMode == IcsExamining) {
11548             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11549             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11550                     PieceToChar(selection), AAA + x, ONE + y);
11551             SendToICS(buf);
11552         } else {
11553             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11554                 int n;
11555                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11556                     n = PieceToNumber(selection - BlackPawn);
11557                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11558                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11559                     boards[0][BOARD_HEIGHT-1-n][1]++;
11560                 } else
11561                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11562                     n = PieceToNumber(selection);
11563                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11564                     boards[0][n][BOARD_WIDTH-1] = selection;
11565                     boards[0][n][BOARD_WIDTH-2]++;
11566                 }
11567             } else
11568             boards[0][y][x] = selection;
11569             DrawPosition(TRUE, boards[0]);
11570         }
11571         break;
11572     }
11573 }
11574
11575
11576 void
11577 DropMenuEvent(selection, x, y)
11578      ChessSquare selection;
11579      int x, y;
11580 {
11581     ChessMove moveType;
11582
11583     switch (gameMode) {
11584       case IcsPlayingWhite:
11585       case MachinePlaysBlack:
11586         if (!WhiteOnMove(currentMove)) {
11587             DisplayMoveError(_("It is Black's turn"));
11588             return;
11589         }
11590         moveType = WhiteDrop;
11591         break;
11592       case IcsPlayingBlack:
11593       case MachinePlaysWhite:
11594         if (WhiteOnMove(currentMove)) {
11595             DisplayMoveError(_("It is White's turn"));
11596             return;
11597         }
11598         moveType = BlackDrop;
11599         break;
11600       case EditGame:
11601         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11602         break;
11603       default:
11604         return;
11605     }
11606
11607     if (moveType == BlackDrop && selection < BlackPawn) {
11608       selection = (ChessSquare) ((int) selection
11609                                  + (int) BlackPawn - (int) WhitePawn);
11610     }
11611     if (boards[currentMove][y][x] != EmptySquare) {
11612         DisplayMoveError(_("That square is occupied"));
11613         return;
11614     }
11615
11616     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11617 }
11618
11619 void
11620 AcceptEvent()
11621 {
11622     /* Accept a pending offer of any kind from opponent */
11623     
11624     if (appData.icsActive) {
11625         SendToICS(ics_prefix);
11626         SendToICS("accept\n");
11627     } else if (cmailMsgLoaded) {
11628         if (currentMove == cmailOldMove &&
11629             commentList[cmailOldMove] != NULL &&
11630             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11631                    "Black offers a draw" : "White offers a draw")) {
11632             TruncateGame();
11633             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11635         } else {
11636             DisplayError(_("There is no pending offer on this move"), 0);
11637             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11638         }
11639     } else {
11640         /* Not used for offers from chess program */
11641     }
11642 }
11643
11644 void
11645 DeclineEvent()
11646 {
11647     /* Decline a pending offer of any kind from opponent */
11648     
11649     if (appData.icsActive) {
11650         SendToICS(ics_prefix);
11651         SendToICS("decline\n");
11652     } else if (cmailMsgLoaded) {
11653         if (currentMove == cmailOldMove &&
11654             commentList[cmailOldMove] != NULL &&
11655             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11656                    "Black offers a draw" : "White offers a draw")) {
11657 #ifdef NOTDEF
11658             AppendComment(cmailOldMove, "Draw declined", TRUE);
11659             DisplayComment(cmailOldMove - 1, "Draw declined");
11660 #endif /*NOTDEF*/
11661         } else {
11662             DisplayError(_("There is no pending offer on this move"), 0);
11663         }
11664     } else {
11665         /* Not used for offers from chess program */
11666     }
11667 }
11668
11669 void
11670 RematchEvent()
11671 {
11672     /* Issue ICS rematch command */
11673     if (appData.icsActive) {
11674         SendToICS(ics_prefix);
11675         SendToICS("rematch\n");
11676     }
11677 }
11678
11679 void
11680 CallFlagEvent()
11681 {
11682     /* Call your opponent's flag (claim a win on time) */
11683     if (appData.icsActive) {
11684         SendToICS(ics_prefix);
11685         SendToICS("flag\n");
11686     } else {
11687         switch (gameMode) {
11688           default:
11689             return;
11690           case MachinePlaysWhite:
11691             if (whiteFlag) {
11692                 if (blackFlag)
11693                   GameEnds(GameIsDrawn, "Both players ran out of time",
11694                            GE_PLAYER);
11695                 else
11696                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11697             } else {
11698                 DisplayError(_("Your opponent is not out of time"), 0);
11699             }
11700             break;
11701           case MachinePlaysBlack:
11702             if (blackFlag) {
11703                 if (whiteFlag)
11704                   GameEnds(GameIsDrawn, "Both players ran out of time",
11705                            GE_PLAYER);
11706                 else
11707                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11708             } else {
11709                 DisplayError(_("Your opponent is not out of time"), 0);
11710             }
11711             break;
11712         }
11713     }
11714 }
11715
11716 void
11717 DrawEvent()
11718 {
11719     /* Offer draw or accept pending draw offer from opponent */
11720     
11721     if (appData.icsActive) {
11722         /* Note: tournament rules require draw offers to be
11723            made after you make your move but before you punch
11724            your clock.  Currently ICS doesn't let you do that;
11725            instead, you immediately punch your clock after making
11726            a move, but you can offer a draw at any time. */
11727         
11728         SendToICS(ics_prefix);
11729         SendToICS("draw\n");
11730     } else if (cmailMsgLoaded) {
11731         if (currentMove == cmailOldMove &&
11732             commentList[cmailOldMove] != NULL &&
11733             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11734                    "Black offers a draw" : "White offers a draw")) {
11735             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11736             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11737         } else if (currentMove == cmailOldMove + 1) {
11738             char *offer = WhiteOnMove(cmailOldMove) ?
11739               "White offers a draw" : "Black offers a draw";
11740             AppendComment(currentMove, offer, TRUE);
11741             DisplayComment(currentMove - 1, offer);
11742             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11743         } else {
11744             DisplayError(_("You must make your move before offering a draw"), 0);
11745             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11746         }
11747     } else if (first.offeredDraw) {
11748         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11749     } else {
11750         if (first.sendDrawOffers) {
11751             SendToProgram("draw\n", &first);
11752             userOfferedDraw = TRUE;
11753         }
11754     }
11755 }
11756
11757 void
11758 AdjournEvent()
11759 {
11760     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11761     
11762     if (appData.icsActive) {
11763         SendToICS(ics_prefix);
11764         SendToICS("adjourn\n");
11765     } else {
11766         /* Currently GNU Chess doesn't offer or accept Adjourns */
11767     }
11768 }
11769
11770
11771 void
11772 AbortEvent()
11773 {
11774     /* Offer Abort or accept pending Abort offer from opponent */
11775     
11776     if (appData.icsActive) {
11777         SendToICS(ics_prefix);
11778         SendToICS("abort\n");
11779     } else {
11780         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11781     }
11782 }
11783
11784 void
11785 ResignEvent()
11786 {
11787     /* Resign.  You can do this even if it's not your turn. */
11788     
11789     if (appData.icsActive) {
11790         SendToICS(ics_prefix);
11791         SendToICS("resign\n");
11792     } else {
11793         switch (gameMode) {
11794           case MachinePlaysWhite:
11795             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11796             break;
11797           case MachinePlaysBlack:
11798             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11799             break;
11800           case EditGame:
11801             if (cmailMsgLoaded) {
11802                 TruncateGame();
11803                 if (WhiteOnMove(cmailOldMove)) {
11804                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11805                 } else {
11806                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11807                 }
11808                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11809             }
11810             break;
11811           default:
11812             break;
11813         }
11814     }
11815 }
11816
11817
11818 void
11819 StopObservingEvent()
11820 {
11821     /* Stop observing current games */
11822     SendToICS(ics_prefix);
11823     SendToICS("unobserve\n");
11824 }
11825
11826 void
11827 StopExaminingEvent()
11828 {
11829     /* Stop observing current game */
11830     SendToICS(ics_prefix);
11831     SendToICS("unexamine\n");
11832 }
11833
11834 void
11835 ForwardInner(target)
11836      int target;
11837 {
11838     int limit;
11839
11840     if (appData.debugMode)
11841         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11842                 target, currentMove, forwardMostMove);
11843
11844     if (gameMode == EditPosition)
11845       return;
11846
11847     if (gameMode == PlayFromGameFile && !pausing)
11848       PauseEvent();
11849     
11850     if (gameMode == IcsExamining && pausing)
11851       limit = pauseExamForwardMostMove;
11852     else
11853       limit = forwardMostMove;
11854     
11855     if (target > limit) target = limit;
11856
11857     if (target > 0 && moveList[target - 1][0]) {
11858         int fromX, fromY, toX, toY;
11859         toX = moveList[target - 1][2] - AAA;
11860         toY = moveList[target - 1][3] - ONE;
11861         if (moveList[target - 1][1] == '@') {
11862             if (appData.highlightLastMove) {
11863                 SetHighlights(-1, -1, toX, toY);
11864             }
11865         } else {
11866             fromX = moveList[target - 1][0] - AAA;
11867             fromY = moveList[target - 1][1] - ONE;
11868             if (target == currentMove + 1) {
11869                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11870             }
11871             if (appData.highlightLastMove) {
11872                 SetHighlights(fromX, fromY, toX, toY);
11873             }
11874         }
11875     }
11876     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11877         gameMode == Training || gameMode == PlayFromGameFile || 
11878         gameMode == AnalyzeFile) {
11879         while (currentMove < target) {
11880             SendMoveToProgram(currentMove++, &first);
11881         }
11882     } else {
11883         currentMove = target;
11884     }
11885     
11886     if (gameMode == EditGame || gameMode == EndOfGame) {
11887         whiteTimeRemaining = timeRemaining[0][currentMove];
11888         blackTimeRemaining = timeRemaining[1][currentMove];
11889     }
11890     DisplayBothClocks();
11891     DisplayMove(currentMove - 1);
11892     DrawPosition(FALSE, boards[currentMove]);
11893     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11894     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11895         DisplayComment(currentMove - 1, commentList[currentMove]);
11896     }
11897 }
11898
11899
11900 void
11901 ForwardEvent()
11902 {
11903     if (gameMode == IcsExamining && !pausing) {
11904         SendToICS(ics_prefix);
11905         SendToICS("forward\n");
11906     } else {
11907         ForwardInner(currentMove + 1);
11908     }
11909 }
11910
11911 void
11912 ToEndEvent()
11913 {
11914     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11915         /* to optimze, we temporarily turn off analysis mode while we feed
11916          * the remaining moves to the engine. Otherwise we get analysis output
11917          * after each move.
11918          */ 
11919         if (first.analysisSupport) {
11920           SendToProgram("exit\nforce\n", &first);
11921           first.analyzing = FALSE;
11922         }
11923     }
11924         
11925     if (gameMode == IcsExamining && !pausing) {
11926         SendToICS(ics_prefix);
11927         SendToICS("forward 999999\n");
11928     } else {
11929         ForwardInner(forwardMostMove);
11930     }
11931
11932     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11933         /* we have fed all the moves, so reactivate analysis mode */
11934         SendToProgram("analyze\n", &first);
11935         first.analyzing = TRUE;
11936         /*first.maybeThinking = TRUE;*/
11937         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11938     }
11939 }
11940
11941 void
11942 BackwardInner(target)
11943      int target;
11944 {
11945     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11946
11947     if (appData.debugMode)
11948         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11949                 target, currentMove, forwardMostMove);
11950
11951     if (gameMode == EditPosition) return;
11952     if (currentMove <= backwardMostMove) {
11953         ClearHighlights();
11954         DrawPosition(full_redraw, boards[currentMove]);
11955         return;
11956     }
11957     if (gameMode == PlayFromGameFile && !pausing)
11958       PauseEvent();
11959     
11960     if (moveList[target][0]) {
11961         int fromX, fromY, toX, toY;
11962         toX = moveList[target][2] - AAA;
11963         toY = moveList[target][3] - ONE;
11964         if (moveList[target][1] == '@') {
11965             if (appData.highlightLastMove) {
11966                 SetHighlights(-1, -1, toX, toY);
11967             }
11968         } else {
11969             fromX = moveList[target][0] - AAA;
11970             fromY = moveList[target][1] - ONE;
11971             if (target == currentMove - 1) {
11972                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11973             }
11974             if (appData.highlightLastMove) {
11975                 SetHighlights(fromX, fromY, toX, toY);
11976             }
11977         }
11978     }
11979     if (gameMode == EditGame || gameMode==AnalyzeMode ||
11980         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11981         while (currentMove > target) {
11982             SendToProgram("undo\n", &first);
11983             currentMove--;
11984         }
11985     } else {
11986         currentMove = target;
11987     }
11988     
11989     if (gameMode == EditGame || gameMode == EndOfGame) {
11990         whiteTimeRemaining = timeRemaining[0][currentMove];
11991         blackTimeRemaining = timeRemaining[1][currentMove];
11992     }
11993     DisplayBothClocks();
11994     DisplayMove(currentMove - 1);
11995     DrawPosition(full_redraw, boards[currentMove]);
11996     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11997     // [HGM] PV info: routine tests if comment empty
11998     DisplayComment(currentMove - 1, commentList[currentMove]);
11999 }
12000
12001 void
12002 BackwardEvent()
12003 {
12004     if (gameMode == IcsExamining && !pausing) {
12005         SendToICS(ics_prefix);
12006         SendToICS("backward\n");
12007     } else {
12008         BackwardInner(currentMove - 1);
12009     }
12010 }
12011
12012 void
12013 ToStartEvent()
12014 {
12015     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12016         /* to optimize, we temporarily turn off analysis mode while we undo
12017          * all the moves. Otherwise we get analysis output after each undo.
12018          */ 
12019         if (first.analysisSupport) {
12020           SendToProgram("exit\nforce\n", &first);
12021           first.analyzing = FALSE;
12022         }
12023     }
12024
12025     if (gameMode == IcsExamining && !pausing) {
12026         SendToICS(ics_prefix);
12027         SendToICS("backward 999999\n");
12028     } else {
12029         BackwardInner(backwardMostMove);
12030     }
12031
12032     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12033         /* we have fed all the moves, so reactivate analysis mode */
12034         SendToProgram("analyze\n", &first);
12035         first.analyzing = TRUE;
12036         /*first.maybeThinking = TRUE;*/
12037         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12038     }
12039 }
12040
12041 void
12042 ToNrEvent(int to)
12043 {
12044   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12045   if (to >= forwardMostMove) to = forwardMostMove;
12046   if (to <= backwardMostMove) to = backwardMostMove;
12047   if (to < currentMove) {
12048     BackwardInner(to);
12049   } else {
12050     ForwardInner(to);
12051   }
12052 }
12053
12054 void
12055 RevertEvent()
12056 {
12057     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12058         return;
12059     }
12060     if (gameMode != IcsExamining) {
12061         DisplayError(_("You are not examining a game"), 0);
12062         return;
12063     }
12064     if (pausing) {
12065         DisplayError(_("You can't revert while pausing"), 0);
12066         return;
12067     }
12068     SendToICS(ics_prefix);
12069     SendToICS("revert\n");
12070 }
12071
12072 void
12073 RetractMoveEvent()
12074 {
12075     switch (gameMode) {
12076       case MachinePlaysWhite:
12077       case MachinePlaysBlack:
12078         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12079             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12080             return;
12081         }
12082         if (forwardMostMove < 2) return;
12083         currentMove = forwardMostMove = forwardMostMove - 2;
12084         whiteTimeRemaining = timeRemaining[0][currentMove];
12085         blackTimeRemaining = timeRemaining[1][currentMove];
12086         DisplayBothClocks();
12087         DisplayMove(currentMove - 1);
12088         ClearHighlights();/*!! could figure this out*/
12089         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12090         SendToProgram("remove\n", &first);
12091         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12092         break;
12093
12094       case BeginningOfGame:
12095       default:
12096         break;
12097
12098       case IcsPlayingWhite:
12099       case IcsPlayingBlack:
12100         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12101             SendToICS(ics_prefix);
12102             SendToICS("takeback 2\n");
12103         } else {
12104             SendToICS(ics_prefix);
12105             SendToICS("takeback 1\n");
12106         }
12107         break;
12108     }
12109 }
12110
12111 void
12112 MoveNowEvent()
12113 {
12114     ChessProgramState *cps;
12115
12116     switch (gameMode) {
12117       case MachinePlaysWhite:
12118         if (!WhiteOnMove(forwardMostMove)) {
12119             DisplayError(_("It is your turn"), 0);
12120             return;
12121         }
12122         cps = &first;
12123         break;
12124       case MachinePlaysBlack:
12125         if (WhiteOnMove(forwardMostMove)) {
12126             DisplayError(_("It is your turn"), 0);
12127             return;
12128         }
12129         cps = &first;
12130         break;
12131       case TwoMachinesPlay:
12132         if (WhiteOnMove(forwardMostMove) ==
12133             (first.twoMachinesColor[0] == 'w')) {
12134             cps = &first;
12135         } else {
12136             cps = &second;
12137         }
12138         break;
12139       case BeginningOfGame:
12140       default:
12141         return;
12142     }
12143     SendToProgram("?\n", cps);
12144 }
12145
12146 void
12147 TruncateGameEvent()
12148 {
12149     EditGameEvent();
12150     if (gameMode != EditGame) return;
12151     TruncateGame();
12152 }
12153
12154 void
12155 TruncateGame()
12156 {
12157     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12158     if (forwardMostMove > currentMove) {
12159         if (gameInfo.resultDetails != NULL) {
12160             free(gameInfo.resultDetails);
12161             gameInfo.resultDetails = NULL;
12162             gameInfo.result = GameUnfinished;
12163         }
12164         forwardMostMove = currentMove;
12165         HistorySet(parseList, backwardMostMove, forwardMostMove,
12166                    currentMove-1);
12167     }
12168 }
12169
12170 void
12171 HintEvent()
12172 {
12173     if (appData.noChessProgram) return;
12174     switch (gameMode) {
12175       case MachinePlaysWhite:
12176         if (WhiteOnMove(forwardMostMove)) {
12177             DisplayError(_("Wait until your turn"), 0);
12178             return;
12179         }
12180         break;
12181       case BeginningOfGame:
12182       case MachinePlaysBlack:
12183         if (!WhiteOnMove(forwardMostMove)) {
12184             DisplayError(_("Wait until your turn"), 0);
12185             return;
12186         }
12187         break;
12188       default:
12189         DisplayError(_("No hint available"), 0);
12190         return;
12191     }
12192     SendToProgram("hint\n", &first);
12193     hintRequested = TRUE;
12194 }
12195
12196 void
12197 BookEvent()
12198 {
12199     if (appData.noChessProgram) return;
12200     switch (gameMode) {
12201       case MachinePlaysWhite:
12202         if (WhiteOnMove(forwardMostMove)) {
12203             DisplayError(_("Wait until your turn"), 0);
12204             return;
12205         }
12206         break;
12207       case BeginningOfGame:
12208       case MachinePlaysBlack:
12209         if (!WhiteOnMove(forwardMostMove)) {
12210             DisplayError(_("Wait until your turn"), 0);
12211             return;
12212         }
12213         break;
12214       case EditPosition:
12215         EditPositionDone(TRUE);
12216         break;
12217       case TwoMachinesPlay:
12218         return;
12219       default:
12220         break;
12221     }
12222     SendToProgram("bk\n", &first);
12223     bookOutput[0] = NULLCHAR;
12224     bookRequested = TRUE;
12225 }
12226
12227 void
12228 AboutGameEvent()
12229 {
12230     char *tags = PGNTags(&gameInfo);
12231     TagsPopUp(tags, CmailMsg());
12232     free(tags);
12233 }
12234
12235 /* end button procedures */
12236
12237 void
12238 PrintPosition(fp, move)
12239      FILE *fp;
12240      int move;
12241 {
12242     int i, j;
12243     
12244     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12245         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12246             char c = PieceToChar(boards[move][i][j]);
12247             fputc(c == 'x' ? '.' : c, fp);
12248             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12249         }
12250     }
12251     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12252       fprintf(fp, "white to play\n");
12253     else
12254       fprintf(fp, "black to play\n");
12255 }
12256
12257 void
12258 PrintOpponents(fp)
12259      FILE *fp;
12260 {
12261     if (gameInfo.white != NULL) {
12262         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12263     } else {
12264         fprintf(fp, "\n");
12265     }
12266 }
12267
12268 /* Find last component of program's own name, using some heuristics */
12269 void
12270 TidyProgramName(prog, host, buf)
12271      char *prog, *host, buf[MSG_SIZ];
12272 {
12273     char *p, *q;
12274     int local = (strcmp(host, "localhost") == 0);
12275     while (!local && (p = strchr(prog, ';')) != NULL) {
12276         p++;
12277         while (*p == ' ') p++;
12278         prog = p;
12279     }
12280     if (*prog == '"' || *prog == '\'') {
12281         q = strchr(prog + 1, *prog);
12282     } else {
12283         q = strchr(prog, ' ');
12284     }
12285     if (q == NULL) q = prog + strlen(prog);
12286     p = q;
12287     while (p >= prog && *p != '/' && *p != '\\') p--;
12288     p++;
12289     if(p == prog && *p == '"') p++;
12290     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12291     memcpy(buf, p, q - p);
12292     buf[q - p] = NULLCHAR;
12293     if (!local) {
12294         strcat(buf, "@");
12295         strcat(buf, host);
12296     }
12297 }
12298
12299 char *
12300 TimeControlTagValue()
12301 {
12302     char buf[MSG_SIZ];
12303     if (!appData.clockMode) {
12304         strcpy(buf, "-");
12305     } else if (movesPerSession > 0) {
12306         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12307     } else if (timeIncrement == 0) {
12308         sprintf(buf, "%ld", timeControl/1000);
12309     } else {
12310         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12311     }
12312     return StrSave(buf);
12313 }
12314
12315 void
12316 SetGameInfo()
12317 {
12318     /* This routine is used only for certain modes */
12319     VariantClass v = gameInfo.variant;
12320     ChessMove r = GameUnfinished;
12321     char *p = NULL;
12322
12323     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12324         r = gameInfo.result; 
12325         p = gameInfo.resultDetails; 
12326         gameInfo.resultDetails = NULL;
12327     }
12328     ClearGameInfo(&gameInfo);
12329     gameInfo.variant = v;
12330
12331     switch (gameMode) {
12332       case MachinePlaysWhite:
12333         gameInfo.event = StrSave( appData.pgnEventHeader );
12334         gameInfo.site = StrSave(HostName());
12335         gameInfo.date = PGNDate();
12336         gameInfo.round = StrSave("-");
12337         gameInfo.white = StrSave(first.tidy);
12338         gameInfo.black = StrSave(UserName());
12339         gameInfo.timeControl = TimeControlTagValue();
12340         break;
12341
12342       case MachinePlaysBlack:
12343         gameInfo.event = StrSave( appData.pgnEventHeader );
12344         gameInfo.site = StrSave(HostName());
12345         gameInfo.date = PGNDate();
12346         gameInfo.round = StrSave("-");
12347         gameInfo.white = StrSave(UserName());
12348         gameInfo.black = StrSave(first.tidy);
12349         gameInfo.timeControl = TimeControlTagValue();
12350         break;
12351
12352       case TwoMachinesPlay:
12353         gameInfo.event = StrSave( appData.pgnEventHeader );
12354         gameInfo.site = StrSave(HostName());
12355         gameInfo.date = PGNDate();
12356         if (matchGame > 0) {
12357             char buf[MSG_SIZ];
12358             sprintf(buf, "%d", matchGame);
12359             gameInfo.round = StrSave(buf);
12360         } else {
12361             gameInfo.round = StrSave("-");
12362         }
12363         if (first.twoMachinesColor[0] == 'w') {
12364             gameInfo.white = StrSave(first.tidy);
12365             gameInfo.black = StrSave(second.tidy);
12366         } else {
12367             gameInfo.white = StrSave(second.tidy);
12368             gameInfo.black = StrSave(first.tidy);
12369         }
12370         gameInfo.timeControl = TimeControlTagValue();
12371         break;
12372
12373       case EditGame:
12374         gameInfo.event = StrSave("Edited game");
12375         gameInfo.site = StrSave(HostName());
12376         gameInfo.date = PGNDate();
12377         gameInfo.round = StrSave("-");
12378         gameInfo.white = StrSave("-");
12379         gameInfo.black = StrSave("-");
12380         gameInfo.result = r;
12381         gameInfo.resultDetails = p;
12382         break;
12383
12384       case EditPosition:
12385         gameInfo.event = StrSave("Edited position");
12386         gameInfo.site = StrSave(HostName());
12387         gameInfo.date = PGNDate();
12388         gameInfo.round = StrSave("-");
12389         gameInfo.white = StrSave("-");
12390         gameInfo.black = StrSave("-");
12391         break;
12392
12393       case IcsPlayingWhite:
12394       case IcsPlayingBlack:
12395       case IcsObserving:
12396       case IcsExamining:
12397         break;
12398
12399       case PlayFromGameFile:
12400         gameInfo.event = StrSave("Game from non-PGN file");
12401         gameInfo.site = StrSave(HostName());
12402         gameInfo.date = PGNDate();
12403         gameInfo.round = StrSave("-");
12404         gameInfo.white = StrSave("?");
12405         gameInfo.black = StrSave("?");
12406         break;
12407
12408       default:
12409         break;
12410     }
12411 }
12412
12413 void
12414 ReplaceComment(index, text)
12415      int index;
12416      char *text;
12417 {
12418     int len;
12419
12420     while (*text == '\n') text++;
12421     len = strlen(text);
12422     while (len > 0 && text[len - 1] == '\n') len--;
12423
12424     if (commentList[index] != NULL)
12425       free(commentList[index]);
12426
12427     if (len == 0) {
12428         commentList[index] = NULL;
12429         return;
12430     }
12431   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12432       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12433       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12434     commentList[index] = (char *) malloc(len + 2);
12435     strncpy(commentList[index], text, len);
12436     commentList[index][len] = '\n';
12437     commentList[index][len + 1] = NULLCHAR;
12438   } else { 
12439     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12440     char *p;
12441     commentList[index] = (char *) malloc(len + 6);
12442     strcpy(commentList[index], "{\n");
12443     strncpy(commentList[index]+2, text, len);
12444     commentList[index][len+2] = NULLCHAR;
12445     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12446     strcat(commentList[index], "\n}\n");
12447   }
12448 }
12449
12450 void
12451 CrushCRs(text)
12452      char *text;
12453 {
12454   char *p = text;
12455   char *q = text;
12456   char ch;
12457
12458   do {
12459     ch = *p++;
12460     if (ch == '\r') continue;
12461     *q++ = ch;
12462   } while (ch != '\0');
12463 }
12464
12465 void
12466 AppendComment(index, text, addBraces)
12467      int index;
12468      char *text;
12469      Boolean addBraces; // [HGM] braces: tells if we should add {}
12470 {
12471     int oldlen, len;
12472     char *old;
12473
12474 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12475     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12476
12477     CrushCRs(text);
12478     while (*text == '\n') text++;
12479     len = strlen(text);
12480     while (len > 0 && text[len - 1] == '\n') len--;
12481
12482     if (len == 0) return;
12483
12484     if (commentList[index] != NULL) {
12485         old = commentList[index];
12486         oldlen = strlen(old);
12487         while(commentList[index][oldlen-1] ==  '\n')
12488           commentList[index][--oldlen] = NULLCHAR;
12489         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12490         strcpy(commentList[index], old);
12491         free(old);
12492         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12493         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12494           if(addBraces) addBraces = FALSE; else { text++; len--; }
12495           while (*text == '\n') { text++; len--; }
12496           commentList[index][--oldlen] = NULLCHAR;
12497       }
12498         if(addBraces) strcat(commentList[index], "\n{\n");
12499         else          strcat(commentList[index], "\n");
12500         strcat(commentList[index], text);
12501         if(addBraces) strcat(commentList[index], "\n}\n");
12502         else          strcat(commentList[index], "\n");
12503     } else {
12504         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12505         if(addBraces)
12506              strcpy(commentList[index], "{\n");
12507         else commentList[index][0] = NULLCHAR;
12508         strcat(commentList[index], text);
12509         strcat(commentList[index], "\n");
12510         if(addBraces) strcat(commentList[index], "}\n");
12511     }
12512 }
12513
12514 static char * FindStr( char * text, char * sub_text )
12515 {
12516     char * result = strstr( text, sub_text );
12517
12518     if( result != NULL ) {
12519         result += strlen( sub_text );
12520     }
12521
12522     return result;
12523 }
12524
12525 /* [AS] Try to extract PV info from PGN comment */
12526 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12527 char *GetInfoFromComment( int index, char * text )
12528 {
12529     char * sep = text;
12530
12531     if( text != NULL && index > 0 ) {
12532         int score = 0;
12533         int depth = 0;
12534         int time = -1, sec = 0, deci;
12535         char * s_eval = FindStr( text, "[%eval " );
12536         char * s_emt = FindStr( text, "[%emt " );
12537
12538         if( s_eval != NULL || s_emt != NULL ) {
12539             /* New style */
12540             char delim;
12541
12542             if( s_eval != NULL ) {
12543                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12544                     return text;
12545                 }
12546
12547                 if( delim != ']' ) {
12548                     return text;
12549                 }
12550             }
12551
12552             if( s_emt != NULL ) {
12553             }
12554                 return text;
12555         }
12556         else {
12557             /* We expect something like: [+|-]nnn.nn/dd */
12558             int score_lo = 0;
12559
12560             if(*text != '{') return text; // [HGM] braces: must be normal comment
12561
12562             sep = strchr( text, '/' );
12563             if( sep == NULL || sep < (text+4) ) {
12564                 return text;
12565             }
12566
12567             time = -1; sec = -1; deci = -1;
12568             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12569                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12570                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12571                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12572                 return text;
12573             }
12574
12575             if( score_lo < 0 || score_lo >= 100 ) {
12576                 return text;
12577             }
12578
12579             if(sec >= 0) time = 600*time + 10*sec; else
12580             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12581
12582             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12583
12584             /* [HGM] PV time: now locate end of PV info */
12585             while( *++sep >= '0' && *sep <= '9'); // strip depth
12586             if(time >= 0)
12587             while( *++sep >= '0' && *sep <= '9'); // strip time
12588             if(sec >= 0)
12589             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12590             if(deci >= 0)
12591             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12592             while(*sep == ' ') sep++;
12593         }
12594
12595         if( depth <= 0 ) {
12596             return text;
12597         }
12598
12599         if( time < 0 ) {
12600             time = -1;
12601         }
12602
12603         pvInfoList[index-1].depth = depth;
12604         pvInfoList[index-1].score = score;
12605         pvInfoList[index-1].time  = 10*time; // centi-sec
12606         if(*sep == '}') *sep = 0; else *--sep = '{';
12607     }
12608     return sep;
12609 }
12610
12611 void
12612 SendToProgram(message, cps)
12613      char *message;
12614      ChessProgramState *cps;
12615 {
12616     int count, outCount, error;
12617     char buf[MSG_SIZ];
12618
12619     if (cps->pr == NULL) return;
12620     Attention(cps);
12621     
12622     if (appData.debugMode) {
12623         TimeMark now;
12624         GetTimeMark(&now);
12625         fprintf(debugFP, "%ld >%-6s: %s", 
12626                 SubtractTimeMarks(&now, &programStartTime),
12627                 cps->which, message);
12628     }
12629     
12630     count = strlen(message);
12631     outCount = OutputToProcess(cps->pr, message, count, &error);
12632     if (outCount < count && !exiting 
12633                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12634         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12635         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12636             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12637                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12638                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12639             } else {
12640                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12641             }
12642             gameInfo.resultDetails = StrSave(buf);
12643         }
12644         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12645     }
12646 }
12647
12648 void
12649 ReceiveFromProgram(isr, closure, message, count, error)
12650      InputSourceRef isr;
12651      VOIDSTAR closure;
12652      char *message;
12653      int count;
12654      int error;
12655 {
12656     char *end_str;
12657     char buf[MSG_SIZ];
12658     ChessProgramState *cps = (ChessProgramState *)closure;
12659
12660     if (isr != cps->isr) return; /* Killed intentionally */
12661     if (count <= 0) {
12662         if (count == 0) {
12663             sprintf(buf,
12664                     _("Error: %s chess program (%s) exited unexpectedly"),
12665                     cps->which, cps->program);
12666         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12667                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12668                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12669                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12670                 } else {
12671                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12672                 }
12673                 gameInfo.resultDetails = StrSave(buf);
12674             }
12675             RemoveInputSource(cps->isr);
12676             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12677         } else {
12678             sprintf(buf,
12679                     _("Error reading from %s chess program (%s)"),
12680                     cps->which, cps->program);
12681             RemoveInputSource(cps->isr);
12682
12683             /* [AS] Program is misbehaving badly... kill it */
12684             if( count == -2 ) {
12685                 DestroyChildProcess( cps->pr, 9 );
12686                 cps->pr = NoProc;
12687             }
12688
12689             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12690         }
12691         return;
12692     }
12693     
12694     if ((end_str = strchr(message, '\r')) != NULL)
12695       *end_str = NULLCHAR;
12696     if ((end_str = strchr(message, '\n')) != NULL)
12697       *end_str = NULLCHAR;
12698     
12699     if (appData.debugMode) {
12700         TimeMark now; int print = 1;
12701         char *quote = ""; char c; int i;
12702
12703         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12704                 char start = message[0];
12705                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12706                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12707                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12708                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12709                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12710                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12711                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12712                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12713                         { quote = "# "; print = (appData.engineComments == 2); }
12714                 message[0] = start; // restore original message
12715         }
12716         if(print) {
12717                 GetTimeMark(&now);
12718                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12719                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12720                         quote,
12721                         message);
12722         }
12723     }
12724
12725     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12726     if (appData.icsEngineAnalyze) {
12727         if (strstr(message, "whisper") != NULL ||
12728              strstr(message, "kibitz") != NULL || 
12729             strstr(message, "tellics") != NULL) return;
12730     }
12731
12732     HandleMachineMove(message, cps);
12733 }
12734
12735
12736 void
12737 SendTimeControl(cps, mps, tc, inc, sd, st)
12738      ChessProgramState *cps;
12739      int mps, inc, sd, st;
12740      long tc;
12741 {
12742     char buf[MSG_SIZ];
12743     int seconds;
12744
12745     if( timeControl_2 > 0 ) {
12746         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12747             tc = timeControl_2;
12748         }
12749     }
12750     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12751     inc /= cps->timeOdds;
12752     st  /= cps->timeOdds;
12753
12754     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12755
12756     if (st > 0) {
12757       /* Set exact time per move, normally using st command */
12758       if (cps->stKludge) {
12759         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12760         seconds = st % 60;
12761         if (seconds == 0) {
12762           sprintf(buf, "level 1 %d\n", st/60);
12763         } else {
12764           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12765         }
12766       } else {
12767         sprintf(buf, "st %d\n", st);
12768       }
12769     } else {
12770       /* Set conventional or incremental time control, using level command */
12771       if (seconds == 0) {
12772         /* Note old gnuchess bug -- minutes:seconds used to not work.
12773            Fixed in later versions, but still avoid :seconds
12774            when seconds is 0. */
12775         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12776       } else {
12777         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12778                 seconds, inc/1000);
12779       }
12780     }
12781     SendToProgram(buf, cps);
12782
12783     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12784     /* Orthogonally, limit search to given depth */
12785     if (sd > 0) {
12786       if (cps->sdKludge) {
12787         sprintf(buf, "depth\n%d\n", sd);
12788       } else {
12789         sprintf(buf, "sd %d\n", sd);
12790       }
12791       SendToProgram(buf, cps);
12792     }
12793
12794     if(cps->nps > 0) { /* [HGM] nps */
12795         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12796         else {
12797                 sprintf(buf, "nps %d\n", cps->nps);
12798               SendToProgram(buf, cps);
12799         }
12800     }
12801 }
12802
12803 ChessProgramState *WhitePlayer()
12804 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12805 {
12806     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12807        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12808         return &second;
12809     return &first;
12810 }
12811
12812 void
12813 SendTimeRemaining(cps, machineWhite)
12814      ChessProgramState *cps;
12815      int /*boolean*/ machineWhite;
12816 {
12817     char message[MSG_SIZ];
12818     long time, otime;
12819
12820     /* Note: this routine must be called when the clocks are stopped
12821        or when they have *just* been set or switched; otherwise
12822        it will be off by the time since the current tick started.
12823     */
12824     if (machineWhite) {
12825         time = whiteTimeRemaining / 10;
12826         otime = blackTimeRemaining / 10;
12827     } else {
12828         time = blackTimeRemaining / 10;
12829         otime = whiteTimeRemaining / 10;
12830     }
12831     /* [HGM] translate opponent's time by time-odds factor */
12832     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12833     if (appData.debugMode) {
12834         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12835     }
12836
12837     if (time <= 0) time = 1;
12838     if (otime <= 0) otime = 1;
12839     
12840     sprintf(message, "time %ld\n", time);
12841     SendToProgram(message, cps);
12842
12843     sprintf(message, "otim %ld\n", otime);
12844     SendToProgram(message, cps);
12845 }
12846
12847 int
12848 BoolFeature(p, name, loc, cps)
12849      char **p;
12850      char *name;
12851      int *loc;
12852      ChessProgramState *cps;
12853 {
12854   char buf[MSG_SIZ];
12855   int len = strlen(name);
12856   int val;
12857   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12858     (*p) += len + 1;
12859     sscanf(*p, "%d", &val);
12860     *loc = (val != 0);
12861     while (**p && **p != ' ') (*p)++;
12862     sprintf(buf, "accepted %s\n", name);
12863     SendToProgram(buf, cps);
12864     return TRUE;
12865   }
12866   return FALSE;
12867 }
12868
12869 int
12870 IntFeature(p, name, loc, cps)
12871      char **p;
12872      char *name;
12873      int *loc;
12874      ChessProgramState *cps;
12875 {
12876   char buf[MSG_SIZ];
12877   int len = strlen(name);
12878   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12879     (*p) += len + 1;
12880     sscanf(*p, "%d", loc);
12881     while (**p && **p != ' ') (*p)++;
12882     sprintf(buf, "accepted %s\n", name);
12883     SendToProgram(buf, cps);
12884     return TRUE;
12885   }
12886   return FALSE;
12887 }
12888
12889 int
12890 StringFeature(p, name, loc, cps)
12891      char **p;
12892      char *name;
12893      char loc[];
12894      ChessProgramState *cps;
12895 {
12896   char buf[MSG_SIZ];
12897   int len = strlen(name);
12898   if (strncmp((*p), name, len) == 0
12899       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12900     (*p) += len + 2;
12901     sscanf(*p, "%[^\"]", loc);
12902     while (**p && **p != '\"') (*p)++;
12903     if (**p == '\"') (*p)++;
12904     sprintf(buf, "accepted %s\n", name);
12905     SendToProgram(buf, cps);
12906     return TRUE;
12907   }
12908   return FALSE;
12909 }
12910
12911 int 
12912 ParseOption(Option *opt, ChessProgramState *cps)
12913 // [HGM] options: process the string that defines an engine option, and determine
12914 // name, type, default value, and allowed value range
12915 {
12916         char *p, *q, buf[MSG_SIZ];
12917         int n, min = (-1)<<31, max = 1<<31, def;
12918
12919         if(p = strstr(opt->name, " -spin ")) {
12920             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12921             if(max < min) max = min; // enforce consistency
12922             if(def < min) def = min;
12923             if(def > max) def = max;
12924             opt->value = def;
12925             opt->min = min;
12926             opt->max = max;
12927             opt->type = Spin;
12928         } else if((p = strstr(opt->name, " -slider "))) {
12929             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12930             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12931             if(max < min) max = min; // enforce consistency
12932             if(def < min) def = min;
12933             if(def > max) def = max;
12934             opt->value = def;
12935             opt->min = min;
12936             opt->max = max;
12937             opt->type = Spin; // Slider;
12938         } else if((p = strstr(opt->name, " -string "))) {
12939             opt->textValue = p+9;
12940             opt->type = TextBox;
12941         } else if((p = strstr(opt->name, " -file "))) {
12942             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12943             opt->textValue = p+7;
12944             opt->type = TextBox; // FileName;
12945         } else if((p = strstr(opt->name, " -path "))) {
12946             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12947             opt->textValue = p+7;
12948             opt->type = TextBox; // PathName;
12949         } else if(p = strstr(opt->name, " -check ")) {
12950             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12951             opt->value = (def != 0);
12952             opt->type = CheckBox;
12953         } else if(p = strstr(opt->name, " -combo ")) {
12954             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12955             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12956             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12957             opt->value = n = 0;
12958             while(q = StrStr(q, " /// ")) {
12959                 n++; *q = 0;    // count choices, and null-terminate each of them
12960                 q += 5;
12961                 if(*q == '*') { // remember default, which is marked with * prefix
12962                     q++;
12963                     opt->value = n;
12964                 }
12965                 cps->comboList[cps->comboCnt++] = q;
12966             }
12967             cps->comboList[cps->comboCnt++] = NULL;
12968             opt->max = n + 1;
12969             opt->type = ComboBox;
12970         } else if(p = strstr(opt->name, " -button")) {
12971             opt->type = Button;
12972         } else if(p = strstr(opt->name, " -save")) {
12973             opt->type = SaveButton;
12974         } else return FALSE;
12975         *p = 0; // terminate option name
12976         // now look if the command-line options define a setting for this engine option.
12977         if(cps->optionSettings && cps->optionSettings[0])
12978             p = strstr(cps->optionSettings, opt->name); else p = NULL;
12979         if(p && (p == cps->optionSettings || p[-1] == ',')) {
12980                 sprintf(buf, "option %s", p);
12981                 if(p = strstr(buf, ",")) *p = 0;
12982                 strcat(buf, "\n");
12983                 SendToProgram(buf, cps);
12984         }
12985         return TRUE;
12986 }
12987
12988 void
12989 FeatureDone(cps, val)
12990      ChessProgramState* cps;
12991      int val;
12992 {
12993   DelayedEventCallback cb = GetDelayedEvent();
12994   if ((cb == InitBackEnd3 && cps == &first) ||
12995       (cb == TwoMachinesEventIfReady && cps == &second)) {
12996     CancelDelayedEvent();
12997     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12998   }
12999   cps->initDone = val;
13000 }
13001
13002 /* Parse feature command from engine */
13003 void
13004 ParseFeatures(args, cps)
13005      char* args;
13006      ChessProgramState *cps;  
13007 {
13008   char *p = args;
13009   char *q;
13010   int val;
13011   char buf[MSG_SIZ];
13012
13013   for (;;) {
13014     while (*p == ' ') p++;
13015     if (*p == NULLCHAR) return;
13016
13017     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13018     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13019     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13020     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13021     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13022     if (BoolFeature(&p, "reuse", &val, cps)) {
13023       /* Engine can disable reuse, but can't enable it if user said no */
13024       if (!val) cps->reuse = FALSE;
13025       continue;
13026     }
13027     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13028     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13029       if (gameMode == TwoMachinesPlay) {
13030         DisplayTwoMachinesTitle();
13031       } else {
13032         DisplayTitle("");
13033       }
13034       continue;
13035     }
13036     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13037     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13038     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13039     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13040     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13041     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13042     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13043     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13044     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13045     if (IntFeature(&p, "done", &val, cps)) {
13046       FeatureDone(cps, val);
13047       continue;
13048     }
13049     /* Added by Tord: */
13050     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13051     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13052     /* End of additions by Tord */
13053
13054     /* [HGM] added features: */
13055     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13056     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13057     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13058     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13059     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13060     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13061     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13062         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13063             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13064             SendToProgram(buf, cps);
13065             continue;
13066         }
13067         if(cps->nrOptions >= MAX_OPTIONS) {
13068             cps->nrOptions--;
13069             sprintf(buf, "%s engine has too many options\n", cps->which);
13070             DisplayError(buf, 0);
13071         }
13072         continue;
13073     }
13074     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13075     /* End of additions by HGM */
13076
13077     /* unknown feature: complain and skip */
13078     q = p;
13079     while (*q && *q != '=') q++;
13080     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13081     SendToProgram(buf, cps);
13082     p = q;
13083     if (*p == '=') {
13084       p++;
13085       if (*p == '\"') {
13086         p++;
13087         while (*p && *p != '\"') p++;
13088         if (*p == '\"') p++;
13089       } else {
13090         while (*p && *p != ' ') p++;
13091       }
13092     }
13093   }
13094
13095 }
13096
13097 void
13098 PeriodicUpdatesEvent(newState)
13099      int newState;
13100 {
13101     if (newState == appData.periodicUpdates)
13102       return;
13103
13104     appData.periodicUpdates=newState;
13105
13106     /* Display type changes, so update it now */
13107 //    DisplayAnalysis();
13108
13109     /* Get the ball rolling again... */
13110     if (newState) {
13111         AnalysisPeriodicEvent(1);
13112         StartAnalysisClock();
13113     }
13114 }
13115
13116 void
13117 PonderNextMoveEvent(newState)
13118      int newState;
13119 {
13120     if (newState == appData.ponderNextMove) return;
13121     if (gameMode == EditPosition) EditPositionDone(TRUE);
13122     if (newState) {
13123         SendToProgram("hard\n", &first);
13124         if (gameMode == TwoMachinesPlay) {
13125             SendToProgram("hard\n", &second);
13126         }
13127     } else {
13128         SendToProgram("easy\n", &first);
13129         thinkOutput[0] = NULLCHAR;
13130         if (gameMode == TwoMachinesPlay) {
13131             SendToProgram("easy\n", &second);
13132         }
13133     }
13134     appData.ponderNextMove = newState;
13135 }
13136
13137 void
13138 NewSettingEvent(option, command, value)
13139      char *command;
13140      int option, value;
13141 {
13142     char buf[MSG_SIZ];
13143
13144     if (gameMode == EditPosition) EditPositionDone(TRUE);
13145     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13146     SendToProgram(buf, &first);
13147     if (gameMode == TwoMachinesPlay) {
13148         SendToProgram(buf, &second);
13149     }
13150 }
13151
13152 void
13153 ShowThinkingEvent()
13154 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13155 {
13156     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13157     int newState = appData.showThinking
13158         // [HGM] thinking: other features now need thinking output as well
13159         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13160     
13161     if (oldState == newState) return;
13162     oldState = newState;
13163     if (gameMode == EditPosition) EditPositionDone(TRUE);
13164     if (oldState) {
13165         SendToProgram("post\n", &first);
13166         if (gameMode == TwoMachinesPlay) {
13167             SendToProgram("post\n", &second);
13168         }
13169     } else {
13170         SendToProgram("nopost\n", &first);
13171         thinkOutput[0] = NULLCHAR;
13172         if (gameMode == TwoMachinesPlay) {
13173             SendToProgram("nopost\n", &second);
13174         }
13175     }
13176 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13177 }
13178
13179 void
13180 AskQuestionEvent(title, question, replyPrefix, which)
13181      char *title; char *question; char *replyPrefix; char *which;
13182 {
13183   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13184   if (pr == NoProc) return;
13185   AskQuestion(title, question, replyPrefix, pr);
13186 }
13187
13188 void
13189 DisplayMove(moveNumber)
13190      int moveNumber;
13191 {
13192     char message[MSG_SIZ];
13193     char res[MSG_SIZ];
13194     char cpThinkOutput[MSG_SIZ];
13195
13196     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13197     
13198     if (moveNumber == forwardMostMove - 1 || 
13199         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13200
13201         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13202
13203         if (strchr(cpThinkOutput, '\n')) {
13204             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13205         }
13206     } else {
13207         *cpThinkOutput = NULLCHAR;
13208     }
13209
13210     /* [AS] Hide thinking from human user */
13211     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13212         *cpThinkOutput = NULLCHAR;
13213         if( thinkOutput[0] != NULLCHAR ) {
13214             int i;
13215
13216             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13217                 cpThinkOutput[i] = '.';
13218             }
13219             cpThinkOutput[i] = NULLCHAR;
13220             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13221         }
13222     }
13223
13224     if (moveNumber == forwardMostMove - 1 &&
13225         gameInfo.resultDetails != NULL) {
13226         if (gameInfo.resultDetails[0] == NULLCHAR) {
13227             sprintf(res, " %s", PGNResult(gameInfo.result));
13228         } else {
13229             sprintf(res, " {%s} %s",
13230                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13231         }
13232     } else {
13233         res[0] = NULLCHAR;
13234     }
13235
13236     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13237         DisplayMessage(res, cpThinkOutput);
13238     } else {
13239         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13240                 WhiteOnMove(moveNumber) ? " " : ".. ",
13241                 parseList[moveNumber], res);
13242         DisplayMessage(message, cpThinkOutput);
13243     }
13244 }
13245
13246 void
13247 DisplayComment(moveNumber, text)
13248      int moveNumber;
13249      char *text;
13250 {
13251     char title[MSG_SIZ];
13252     char buf[8000]; // comment can be long!
13253     int score, depth;
13254     
13255     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13256       strcpy(title, "Comment");
13257     } else {
13258       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13259               WhiteOnMove(moveNumber) ? " " : ".. ",
13260               parseList[moveNumber]);
13261     }
13262     // [HGM] PV info: display PV info together with (or as) comment
13263     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13264       if(text == NULL) text = "";                                           
13265       score = pvInfoList[moveNumber].score;
13266       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13267               depth, (pvInfoList[moveNumber].time+50)/100, text);
13268       text = buf;
13269     }
13270     if (text != NULL && (appData.autoDisplayComment || commentUp))
13271         CommentPopUp(title, text);
13272 }
13273
13274 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13275  * might be busy thinking or pondering.  It can be omitted if your
13276  * gnuchess is configured to stop thinking immediately on any user
13277  * input.  However, that gnuchess feature depends on the FIONREAD
13278  * ioctl, which does not work properly on some flavors of Unix.
13279  */
13280 void
13281 Attention(cps)
13282      ChessProgramState *cps;
13283 {
13284 #if ATTENTION
13285     if (!cps->useSigint) return;
13286     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13287     switch (gameMode) {
13288       case MachinePlaysWhite:
13289       case MachinePlaysBlack:
13290       case TwoMachinesPlay:
13291       case IcsPlayingWhite:
13292       case IcsPlayingBlack:
13293       case AnalyzeMode:
13294       case AnalyzeFile:
13295         /* Skip if we know it isn't thinking */
13296         if (!cps->maybeThinking) return;
13297         if (appData.debugMode)
13298           fprintf(debugFP, "Interrupting %s\n", cps->which);
13299         InterruptChildProcess(cps->pr);
13300         cps->maybeThinking = FALSE;
13301         break;
13302       default:
13303         break;
13304     }
13305 #endif /*ATTENTION*/
13306 }
13307
13308 int
13309 CheckFlags()
13310 {
13311     if (whiteTimeRemaining <= 0) {
13312         if (!whiteFlag) {
13313             whiteFlag = TRUE;
13314             if (appData.icsActive) {
13315                 if (appData.autoCallFlag &&
13316                     gameMode == IcsPlayingBlack && !blackFlag) {
13317                   SendToICS(ics_prefix);
13318                   SendToICS("flag\n");
13319                 }
13320             } else {
13321                 if (blackFlag) {
13322                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13323                 } else {
13324                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13325                     if (appData.autoCallFlag) {
13326                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13327                         return TRUE;
13328                     }
13329                 }
13330             }
13331         }
13332     }
13333     if (blackTimeRemaining <= 0) {
13334         if (!blackFlag) {
13335             blackFlag = TRUE;
13336             if (appData.icsActive) {
13337                 if (appData.autoCallFlag &&
13338                     gameMode == IcsPlayingWhite && !whiteFlag) {
13339                   SendToICS(ics_prefix);
13340                   SendToICS("flag\n");
13341                 }
13342             } else {
13343                 if (whiteFlag) {
13344                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13345                 } else {
13346                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13347                     if (appData.autoCallFlag) {
13348                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13349                         return TRUE;
13350                     }
13351                 }
13352             }
13353         }
13354     }
13355     return FALSE;
13356 }
13357
13358 void
13359 CheckTimeControl()
13360 {
13361     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13362         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13363
13364     /*
13365      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13366      */
13367     if ( !WhiteOnMove(forwardMostMove) )
13368         /* White made time control */
13369         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13370         /* [HGM] time odds: correct new time quota for time odds! */
13371                                             / WhitePlayer()->timeOdds;
13372       else
13373         /* Black made time control */
13374         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13375                                             / WhitePlayer()->other->timeOdds;
13376 }
13377
13378 void
13379 DisplayBothClocks()
13380 {
13381     int wom = gameMode == EditPosition ?
13382       !blackPlaysFirst : WhiteOnMove(currentMove);
13383     DisplayWhiteClock(whiteTimeRemaining, wom);
13384     DisplayBlackClock(blackTimeRemaining, !wom);
13385 }
13386
13387
13388 /* Timekeeping seems to be a portability nightmare.  I think everyone
13389    has ftime(), but I'm really not sure, so I'm including some ifdefs
13390    to use other calls if you don't.  Clocks will be less accurate if
13391    you have neither ftime nor gettimeofday.
13392 */
13393
13394 /* VS 2008 requires the #include outside of the function */
13395 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13396 #include <sys/timeb.h>
13397 #endif
13398
13399 /* Get the current time as a TimeMark */
13400 void
13401 GetTimeMark(tm)
13402      TimeMark *tm;
13403 {
13404 #if HAVE_GETTIMEOFDAY
13405
13406     struct timeval timeVal;
13407     struct timezone timeZone;
13408
13409     gettimeofday(&timeVal, &timeZone);
13410     tm->sec = (long) timeVal.tv_sec; 
13411     tm->ms = (int) (timeVal.tv_usec / 1000L);
13412
13413 #else /*!HAVE_GETTIMEOFDAY*/
13414 #if HAVE_FTIME
13415
13416 // include <sys/timeb.h> / moved to just above start of function
13417     struct timeb timeB;
13418
13419     ftime(&timeB);
13420     tm->sec = (long) timeB.time;
13421     tm->ms = (int) timeB.millitm;
13422
13423 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13424     tm->sec = (long) time(NULL);
13425     tm->ms = 0;
13426 #endif
13427 #endif
13428 }
13429
13430 /* Return the difference in milliseconds between two
13431    time marks.  We assume the difference will fit in a long!
13432 */
13433 long
13434 SubtractTimeMarks(tm2, tm1)
13435      TimeMark *tm2, *tm1;
13436 {
13437     return 1000L*(tm2->sec - tm1->sec) +
13438            (long) (tm2->ms - tm1->ms);
13439 }
13440
13441
13442 /*
13443  * Code to manage the game clocks.
13444  *
13445  * In tournament play, black starts the clock and then white makes a move.
13446  * We give the human user a slight advantage if he is playing white---the
13447  * clocks don't run until he makes his first move, so it takes zero time.
13448  * Also, we don't account for network lag, so we could get out of sync
13449  * with GNU Chess's clock -- but then, referees are always right.  
13450  */
13451
13452 static TimeMark tickStartTM;
13453 static long intendedTickLength;
13454
13455 long
13456 NextTickLength(timeRemaining)
13457      long timeRemaining;
13458 {
13459     long nominalTickLength, nextTickLength;
13460
13461     if (timeRemaining > 0L && timeRemaining <= 10000L)
13462       nominalTickLength = 100L;
13463     else
13464       nominalTickLength = 1000L;
13465     nextTickLength = timeRemaining % nominalTickLength;
13466     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13467
13468     return nextTickLength;
13469 }
13470
13471 /* Adjust clock one minute up or down */
13472 void
13473 AdjustClock(Boolean which, int dir)
13474 {
13475     if(which) blackTimeRemaining += 60000*dir;
13476     else      whiteTimeRemaining += 60000*dir;
13477     DisplayBothClocks();
13478 }
13479
13480 /* Stop clocks and reset to a fresh time control */
13481 void
13482 ResetClocks() 
13483 {
13484     (void) StopClockTimer();
13485     if (appData.icsActive) {
13486         whiteTimeRemaining = blackTimeRemaining = 0;
13487     } else if (searchTime) {
13488         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13489         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13490     } else { /* [HGM] correct new time quote for time odds */
13491         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13492         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13493     }
13494     if (whiteFlag || blackFlag) {
13495         DisplayTitle("");
13496         whiteFlag = blackFlag = FALSE;
13497     }
13498     DisplayBothClocks();
13499 }
13500
13501 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13502
13503 /* Decrement running clock by amount of time that has passed */
13504 void
13505 DecrementClocks()
13506 {
13507     long timeRemaining;
13508     long lastTickLength, fudge;
13509     TimeMark now;
13510
13511     if (!appData.clockMode) return;
13512     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13513         
13514     GetTimeMark(&now);
13515
13516     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13517
13518     /* Fudge if we woke up a little too soon */
13519     fudge = intendedTickLength - lastTickLength;
13520     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13521
13522     if (WhiteOnMove(forwardMostMove)) {
13523         if(whiteNPS >= 0) lastTickLength = 0;
13524         timeRemaining = whiteTimeRemaining -= lastTickLength;
13525         DisplayWhiteClock(whiteTimeRemaining - fudge,
13526                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13527     } else {
13528         if(blackNPS >= 0) lastTickLength = 0;
13529         timeRemaining = blackTimeRemaining -= lastTickLength;
13530         DisplayBlackClock(blackTimeRemaining - fudge,
13531                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13532     }
13533
13534     if (CheckFlags()) return;
13535         
13536     tickStartTM = now;
13537     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13538     StartClockTimer(intendedTickLength);
13539
13540     /* if the time remaining has fallen below the alarm threshold, sound the
13541      * alarm. if the alarm has sounded and (due to a takeback or time control
13542      * with increment) the time remaining has increased to a level above the
13543      * threshold, reset the alarm so it can sound again. 
13544      */
13545     
13546     if (appData.icsActive && appData.icsAlarm) {
13547
13548         /* make sure we are dealing with the user's clock */
13549         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13550                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13551            )) return;
13552
13553         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13554             alarmSounded = FALSE;
13555         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13556             PlayAlarmSound();
13557             alarmSounded = TRUE;
13558         }
13559     }
13560 }
13561
13562
13563 /* A player has just moved, so stop the previously running
13564    clock and (if in clock mode) start the other one.
13565    We redisplay both clocks in case we're in ICS mode, because
13566    ICS gives us an update to both clocks after every move.
13567    Note that this routine is called *after* forwardMostMove
13568    is updated, so the last fractional tick must be subtracted
13569    from the color that is *not* on move now.
13570 */
13571 void
13572 SwitchClocks()
13573 {
13574     long lastTickLength;
13575     TimeMark now;
13576     int flagged = FALSE;
13577
13578     GetTimeMark(&now);
13579
13580     if (StopClockTimer() && appData.clockMode) {
13581         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13582         if (WhiteOnMove(forwardMostMove)) {
13583             if(blackNPS >= 0) lastTickLength = 0;
13584             blackTimeRemaining -= lastTickLength;
13585            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13586 //         if(pvInfoList[forwardMostMove-1].time == -1)
13587                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13588                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13589         } else {
13590            if(whiteNPS >= 0) lastTickLength = 0;
13591            whiteTimeRemaining -= lastTickLength;
13592            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13593 //         if(pvInfoList[forwardMostMove-1].time == -1)
13594                  pvInfoList[forwardMostMove-1].time = 
13595                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13596         }
13597         flagged = CheckFlags();
13598     }
13599     CheckTimeControl();
13600
13601     if (flagged || !appData.clockMode) return;
13602
13603     switch (gameMode) {
13604       case MachinePlaysBlack:
13605       case MachinePlaysWhite:
13606       case BeginningOfGame:
13607         if (pausing) return;
13608         break;
13609
13610       case EditGame:
13611       case PlayFromGameFile:
13612       case IcsExamining:
13613         return;
13614
13615       default:
13616         break;
13617     }
13618
13619     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13620         if(WhiteOnMove(forwardMostMove))
13621              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13622         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13623     }
13624
13625     tickStartTM = now;
13626     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13627       whiteTimeRemaining : blackTimeRemaining);
13628     StartClockTimer(intendedTickLength);
13629 }
13630         
13631
13632 /* Stop both clocks */
13633 void
13634 StopClocks()
13635 {       
13636     long lastTickLength;
13637     TimeMark now;
13638
13639     if (!StopClockTimer()) return;
13640     if (!appData.clockMode) return;
13641
13642     GetTimeMark(&now);
13643
13644     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13645     if (WhiteOnMove(forwardMostMove)) {
13646         if(whiteNPS >= 0) lastTickLength = 0;
13647         whiteTimeRemaining -= lastTickLength;
13648         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13649     } else {
13650         if(blackNPS >= 0) lastTickLength = 0;
13651         blackTimeRemaining -= lastTickLength;
13652         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13653     }
13654     CheckFlags();
13655 }
13656         
13657 /* Start clock of player on move.  Time may have been reset, so
13658    if clock is already running, stop and restart it. */
13659 void
13660 StartClocks()
13661 {
13662     (void) StopClockTimer(); /* in case it was running already */
13663     DisplayBothClocks();
13664     if (CheckFlags()) return;
13665
13666     if (!appData.clockMode) return;
13667     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13668
13669     GetTimeMark(&tickStartTM);
13670     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13671       whiteTimeRemaining : blackTimeRemaining);
13672
13673    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13674     whiteNPS = blackNPS = -1; 
13675     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13676        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13677         whiteNPS = first.nps;
13678     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13679        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13680         blackNPS = first.nps;
13681     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13682         whiteNPS = second.nps;
13683     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13684         blackNPS = second.nps;
13685     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13686
13687     StartClockTimer(intendedTickLength);
13688 }
13689
13690 char *
13691 TimeString(ms)
13692      long ms;
13693 {
13694     long second, minute, hour, day;
13695     char *sign = "";
13696     static char buf[32];
13697     
13698     if (ms > 0 && ms <= 9900) {
13699       /* convert milliseconds to tenths, rounding up */
13700       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13701
13702       sprintf(buf, " %03.1f ", tenths/10.0);
13703       return buf;
13704     }
13705
13706     /* convert milliseconds to seconds, rounding up */
13707     /* use floating point to avoid strangeness of integer division
13708        with negative dividends on many machines */
13709     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13710
13711     if (second < 0) {
13712         sign = "-";
13713         second = -second;
13714     }
13715     
13716     day = second / (60 * 60 * 24);
13717     second = second % (60 * 60 * 24);
13718     hour = second / (60 * 60);
13719     second = second % (60 * 60);
13720     minute = second / 60;
13721     second = second % 60;
13722     
13723     if (day > 0)
13724       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13725               sign, day, hour, minute, second);
13726     else if (hour > 0)
13727       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13728     else
13729       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13730     
13731     return buf;
13732 }
13733
13734
13735 /*
13736  * This is necessary because some C libraries aren't ANSI C compliant yet.
13737  */
13738 char *
13739 StrStr(string, match)
13740      char *string, *match;
13741 {
13742     int i, length;
13743     
13744     length = strlen(match);
13745     
13746     for (i = strlen(string) - length; i >= 0; i--, string++)
13747       if (!strncmp(match, string, length))
13748         return string;
13749     
13750     return NULL;
13751 }
13752
13753 char *
13754 StrCaseStr(string, match)
13755      char *string, *match;
13756 {
13757     int i, j, length;
13758     
13759     length = strlen(match);
13760     
13761     for (i = strlen(string) - length; i >= 0; i--, string++) {
13762         for (j = 0; j < length; j++) {
13763             if (ToLower(match[j]) != ToLower(string[j]))
13764               break;
13765         }
13766         if (j == length) return string;
13767     }
13768
13769     return NULL;
13770 }
13771
13772 #ifndef _amigados
13773 int
13774 StrCaseCmp(s1, s2)
13775      char *s1, *s2;
13776 {
13777     char c1, c2;
13778     
13779     for (;;) {
13780         c1 = ToLower(*s1++);
13781         c2 = ToLower(*s2++);
13782         if (c1 > c2) return 1;
13783         if (c1 < c2) return -1;
13784         if (c1 == NULLCHAR) return 0;
13785     }
13786 }
13787
13788
13789 int
13790 ToLower(c)
13791      int c;
13792 {
13793     return isupper(c) ? tolower(c) : c;
13794 }
13795
13796
13797 int
13798 ToUpper(c)
13799      int c;
13800 {
13801     return islower(c) ? toupper(c) : c;
13802 }
13803 #endif /* !_amigados    */
13804
13805 char *
13806 StrSave(s)
13807      char *s;
13808 {
13809     char *ret;
13810
13811     if ((ret = (char *) malloc(strlen(s) + 1))) {
13812         strcpy(ret, s);
13813     }
13814     return ret;
13815 }
13816
13817 char *
13818 StrSavePtr(s, savePtr)
13819      char *s, **savePtr;
13820 {
13821     if (*savePtr) {
13822         free(*savePtr);
13823     }
13824     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13825         strcpy(*savePtr, s);
13826     }
13827     return(*savePtr);
13828 }
13829
13830 char *
13831 PGNDate()
13832 {
13833     time_t clock;
13834     struct tm *tm;
13835     char buf[MSG_SIZ];
13836
13837     clock = time((time_t *)NULL);
13838     tm = localtime(&clock);
13839     sprintf(buf, "%04d.%02d.%02d",
13840             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13841     return StrSave(buf);
13842 }
13843
13844
13845 char *
13846 PositionToFEN(move, overrideCastling)
13847      int move;
13848      char *overrideCastling;
13849 {
13850     int i, j, fromX, fromY, toX, toY;
13851     int whiteToPlay;
13852     char buf[128];
13853     char *p, *q;
13854     int emptycount;
13855     ChessSquare piece;
13856
13857     whiteToPlay = (gameMode == EditPosition) ?
13858       !blackPlaysFirst : (move % 2 == 0);
13859     p = buf;
13860
13861     /* Piece placement data */
13862     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13863         emptycount = 0;
13864         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13865             if (boards[move][i][j] == EmptySquare) {
13866                 emptycount++;
13867             } else { ChessSquare piece = boards[move][i][j];
13868                 if (emptycount > 0) {
13869                     if(emptycount<10) /* [HGM] can be >= 10 */
13870                         *p++ = '0' + emptycount;
13871                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13872                     emptycount = 0;
13873                 }
13874                 if(PieceToChar(piece) == '+') {
13875                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13876                     *p++ = '+';
13877                     piece = (ChessSquare)(DEMOTED piece);
13878                 } 
13879                 *p++ = PieceToChar(piece);
13880                 if(p[-1] == '~') {
13881                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13882                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13883                     *p++ = '~';
13884                 }
13885             }
13886         }
13887         if (emptycount > 0) {
13888             if(emptycount<10) /* [HGM] can be >= 10 */
13889                 *p++ = '0' + emptycount;
13890             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13891             emptycount = 0;
13892         }
13893         *p++ = '/';
13894     }
13895     *(p - 1) = ' ';
13896
13897     /* [HGM] print Crazyhouse or Shogi holdings */
13898     if( gameInfo.holdingsWidth ) {
13899         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13900         q = p;
13901         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13902             piece = boards[move][i][BOARD_WIDTH-1];
13903             if( piece != EmptySquare )
13904               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13905                   *p++ = PieceToChar(piece);
13906         }
13907         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13908             piece = boards[move][BOARD_HEIGHT-i-1][0];
13909             if( piece != EmptySquare )
13910               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13911                   *p++ = PieceToChar(piece);
13912         }
13913
13914         if( q == p ) *p++ = '-';
13915         *p++ = ']';
13916         *p++ = ' ';
13917     }
13918
13919     /* Active color */
13920     *p++ = whiteToPlay ? 'w' : 'b';
13921     *p++ = ' ';
13922
13923   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13924     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13925   } else {
13926   if(nrCastlingRights) {
13927      q = p;
13928      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13929        /* [HGM] write directly from rights */
13930            if(boards[move][CASTLING][2] != NoRights &&
13931               boards[move][CASTLING][0] != NoRights   )
13932                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13933            if(boards[move][CASTLING][2] != NoRights &&
13934               boards[move][CASTLING][1] != NoRights   )
13935                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13936            if(boards[move][CASTLING][5] != NoRights &&
13937               boards[move][CASTLING][3] != NoRights   )
13938                 *p++ = boards[move][CASTLING][3] + AAA;
13939            if(boards[move][CASTLING][5] != NoRights &&
13940               boards[move][CASTLING][4] != NoRights   )
13941                 *p++ = boards[move][CASTLING][4] + AAA;
13942      } else {
13943
13944         /* [HGM] write true castling rights */
13945         if( nrCastlingRights == 6 ) {
13946             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13947                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
13948             if(boards[move][CASTLING][1] == BOARD_LEFT &&
13949                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
13950             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13951                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
13952             if(boards[move][CASTLING][4] == BOARD_LEFT &&
13953                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
13954         }
13955      }
13956      if (q == p) *p++ = '-'; /* No castling rights */
13957      *p++ = ' ';
13958   }
13959
13960   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
13961      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
13962     /* En passant target square */
13963     if (move > backwardMostMove) {
13964         fromX = moveList[move - 1][0] - AAA;
13965         fromY = moveList[move - 1][1] - ONE;
13966         toX = moveList[move - 1][2] - AAA;
13967         toY = moveList[move - 1][3] - ONE;
13968         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13969             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13970             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13971             fromX == toX) {
13972             /* 2-square pawn move just happened */
13973             *p++ = toX + AAA;
13974             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13975         } else {
13976             *p++ = '-';
13977         }
13978     } else if(move == backwardMostMove) {
13979         // [HGM] perhaps we should always do it like this, and forget the above?
13980         if((signed char)boards[move][EP_STATUS] >= 0) {
13981             *p++ = boards[move][EP_STATUS] + AAA;
13982             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13983         } else {
13984             *p++ = '-';
13985         }
13986     } else {
13987         *p++ = '-';
13988     }
13989     *p++ = ' ';
13990   }
13991   }
13992
13993     /* [HGM] find reversible plies */
13994     {   int i = 0, j=move;
13995
13996         if (appData.debugMode) { int k;
13997             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13998             for(k=backwardMostMove; k<=forwardMostMove; k++)
13999                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14000
14001         }
14002
14003         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14004         if( j == backwardMostMove ) i += initialRulePlies;
14005         sprintf(p, "%d ", i);
14006         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14007     }
14008     /* Fullmove number */
14009     sprintf(p, "%d", (move / 2) + 1);
14010     
14011     return StrSave(buf);
14012 }
14013
14014 Boolean
14015 ParseFEN(board, blackPlaysFirst, fen)
14016     Board board;
14017      int *blackPlaysFirst;
14018      char *fen;
14019 {
14020     int i, j;
14021     char *p;
14022     int emptycount;
14023     ChessSquare piece;
14024
14025     p = fen;
14026
14027     /* [HGM] by default clear Crazyhouse holdings, if present */
14028     if(gameInfo.holdingsWidth) {
14029        for(i=0; i<BOARD_HEIGHT; i++) {
14030            board[i][0]             = EmptySquare; /* black holdings */
14031            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14032            board[i][1]             = (ChessSquare) 0; /* black counts */
14033            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14034        }
14035     }
14036
14037     /* Piece placement data */
14038     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14039         j = 0;
14040         for (;;) {
14041             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14042                 if (*p == '/') p++;
14043                 emptycount = gameInfo.boardWidth - j;
14044                 while (emptycount--)
14045                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14046                 break;
14047 #if(BOARD_FILES >= 10)
14048             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14049                 p++; emptycount=10;
14050                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14051                 while (emptycount--)
14052                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14053 #endif
14054             } else if (isdigit(*p)) {
14055                 emptycount = *p++ - '0';
14056                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14057                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14058                 while (emptycount--)
14059                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14060             } else if (*p == '+' || isalpha(*p)) {
14061                 if (j >= gameInfo.boardWidth) return FALSE;
14062                 if(*p=='+') {
14063                     piece = CharToPiece(*++p);
14064                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14065                     piece = (ChessSquare) (PROMOTED piece ); p++;
14066                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14067                 } else piece = CharToPiece(*p++);
14068
14069                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14070                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14071                     piece = (ChessSquare) (PROMOTED piece);
14072                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14073                     p++;
14074                 }
14075                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14076             } else {
14077                 return FALSE;
14078             }
14079         }
14080     }
14081     while (*p == '/' || *p == ' ') p++;
14082
14083     /* [HGM] look for Crazyhouse holdings here */
14084     while(*p==' ') p++;
14085     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14086         if(*p == '[') p++;
14087         if(*p == '-' ) *p++; /* empty holdings */ else {
14088             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14089             /* if we would allow FEN reading to set board size, we would   */
14090             /* have to add holdings and shift the board read so far here   */
14091             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14092                 *p++;
14093                 if((int) piece >= (int) BlackPawn ) {
14094                     i = (int)piece - (int)BlackPawn;
14095                     i = PieceToNumber((ChessSquare)i);
14096                     if( i >= gameInfo.holdingsSize ) return FALSE;
14097                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14098                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14099                 } else {
14100                     i = (int)piece - (int)WhitePawn;
14101                     i = PieceToNumber((ChessSquare)i);
14102                     if( i >= gameInfo.holdingsSize ) return FALSE;
14103                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14104                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14105                 }
14106             }
14107         }
14108         if(*p == ']') *p++;
14109     }
14110
14111     while(*p == ' ') p++;
14112
14113     /* Active color */
14114     switch (*p++) {
14115       case 'w':
14116         *blackPlaysFirst = FALSE;
14117         break;
14118       case 'b': 
14119         *blackPlaysFirst = TRUE;
14120         break;
14121       default:
14122         return FALSE;
14123     }
14124
14125     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14126     /* return the extra info in global variiables             */
14127
14128     /* set defaults in case FEN is incomplete */
14129     board[EP_STATUS] = EP_UNKNOWN;
14130     for(i=0; i<nrCastlingRights; i++ ) {
14131         board[CASTLING][i] =
14132             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14133     }   /* assume possible unless obviously impossible */
14134     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14135     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14136     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14137     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14138     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14139     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14140     FENrulePlies = 0;
14141
14142     while(*p==' ') p++;
14143     if(nrCastlingRights) {
14144       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14145           /* castling indicator present, so default becomes no castlings */
14146           for(i=0; i<nrCastlingRights; i++ ) {
14147                  board[CASTLING][i] = NoRights;
14148           }
14149       }
14150       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14151              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14152              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14153              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14154         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14155
14156         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14157             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14158             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14159         }
14160         switch(c) {
14161           case'K':
14162               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14163               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14164               board[CASTLING][2] = whiteKingFile;
14165               break;
14166           case'Q':
14167               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14168               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14169               board[CASTLING][2] = whiteKingFile;
14170               break;
14171           case'k':
14172               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14173               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14174               board[CASTLING][5] = blackKingFile;
14175               break;
14176           case'q':
14177               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14178               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14179               board[CASTLING][5] = blackKingFile;
14180           case '-':
14181               break;
14182           default: /* FRC castlings */
14183               if(c >= 'a') { /* black rights */
14184                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14185                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14186                   if(i == BOARD_RGHT) break;
14187                   board[CASTLING][5] = i;
14188                   c -= AAA;
14189                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14190                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14191                   if(c > i)
14192                       board[CASTLING][3] = c;
14193                   else
14194                       board[CASTLING][4] = c;
14195               } else { /* white rights */
14196                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14197                     if(board[0][i] == WhiteKing) break;
14198                   if(i == BOARD_RGHT) break;
14199                   board[CASTLING][2] = i;
14200                   c -= AAA - 'a' + 'A';
14201                   if(board[0][c] >= WhiteKing) break;
14202                   if(c > i)
14203                       board[CASTLING][0] = c;
14204                   else
14205                       board[CASTLING][1] = c;
14206               }
14207         }
14208       }
14209     if (appData.debugMode) {
14210         fprintf(debugFP, "FEN castling rights:");
14211         for(i=0; i<nrCastlingRights; i++)
14212         fprintf(debugFP, " %d", board[CASTLING][i]);
14213         fprintf(debugFP, "\n");
14214     }
14215
14216       while(*p==' ') p++;
14217     }
14218
14219     /* read e.p. field in games that know e.p. capture */
14220     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14221        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { 
14222       if(*p=='-') {
14223         p++; board[EP_STATUS] = EP_NONE;
14224       } else {
14225          char c = *p++ - AAA;
14226
14227          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14228          if(*p >= '0' && *p <='9') *p++;
14229          board[EP_STATUS] = c;
14230       }
14231     }
14232
14233
14234     if(sscanf(p, "%d", &i) == 1) {
14235         FENrulePlies = i; /* 50-move ply counter */
14236         /* (The move number is still ignored)    */
14237     }
14238
14239     return TRUE;
14240 }
14241       
14242 void
14243 EditPositionPasteFEN(char *fen)
14244 {
14245   if (fen != NULL) {
14246     Board initial_position;
14247
14248     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14249       DisplayError(_("Bad FEN position in clipboard"), 0);
14250       return ;
14251     } else {
14252       int savedBlackPlaysFirst = blackPlaysFirst;
14253       EditPositionEvent();
14254       blackPlaysFirst = savedBlackPlaysFirst;
14255       CopyBoard(boards[0], initial_position);
14256       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14257       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14258       DisplayBothClocks();
14259       DrawPosition(FALSE, boards[currentMove]);
14260     }
14261   }
14262 }
14263
14264 static char cseq[12] = "\\   ";
14265
14266 Boolean set_cont_sequence(char *new_seq)
14267 {
14268     int len;
14269     Boolean ret;
14270
14271     // handle bad attempts to set the sequence
14272         if (!new_seq)
14273                 return 0; // acceptable error - no debug
14274
14275     len = strlen(new_seq);
14276     ret = (len > 0) && (len < sizeof(cseq));
14277     if (ret)
14278         strcpy(cseq, new_seq);
14279     else if (appData.debugMode)
14280         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14281     return ret;
14282 }
14283
14284 /*
14285     reformat a source message so words don't cross the width boundary.  internal
14286     newlines are not removed.  returns the wrapped size (no null character unless
14287     included in source message).  If dest is NULL, only calculate the size required
14288     for the dest buffer.  lp argument indicats line position upon entry, and it's
14289     passed back upon exit.
14290 */
14291 int wrap(char *dest, char *src, int count, int width, int *lp)
14292 {
14293     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14294
14295     cseq_len = strlen(cseq);
14296     old_line = line = *lp;
14297     ansi = len = clen = 0;
14298
14299     for (i=0; i < count; i++)
14300     {
14301         if (src[i] == '\033')
14302             ansi = 1;
14303
14304         // if we hit the width, back up
14305         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14306         {
14307             // store i & len in case the word is too long
14308             old_i = i, old_len = len;
14309
14310             // find the end of the last word
14311             while (i && src[i] != ' ' && src[i] != '\n')
14312             {
14313                 i--;
14314                 len--;
14315             }
14316
14317             // word too long?  restore i & len before splitting it
14318             if ((old_i-i+clen) >= width)
14319             {
14320                 i = old_i;
14321                 len = old_len;
14322             }
14323
14324             // extra space?
14325             if (i && src[i-1] == ' ')
14326                 len--;
14327
14328             if (src[i] != ' ' && src[i] != '\n')
14329             {
14330                 i--;
14331                 if (len)
14332                     len--;
14333             }
14334
14335             // now append the newline and continuation sequence
14336             if (dest)
14337                 dest[len] = '\n';
14338             len++;
14339             if (dest)
14340                 strncpy(dest+len, cseq, cseq_len);
14341             len += cseq_len;
14342             line = cseq_len;
14343             clen = cseq_len;
14344             continue;
14345         }
14346
14347         if (dest)
14348             dest[len] = src[i];
14349         len++;
14350         if (!ansi)
14351             line++;
14352         if (src[i] == '\n')
14353             line = 0;
14354         if (src[i] == 'm')
14355             ansi = 0;
14356     }
14357     if (dest && appData.debugMode)
14358     {
14359         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14360             count, width, line, len, *lp);
14361         show_bytes(debugFP, src, count);
14362         fprintf(debugFP, "\ndest: ");
14363         show_bytes(debugFP, dest, len);
14364         fprintf(debugFP, "\n");
14365     }
14366     *lp = dest ? line : old_line;
14367
14368     return len;
14369 }
14370
14371 // [HGM] vari: routines for shelving variations
14372
14373 void 
14374 PushTail(int firstMove, int lastMove)
14375 {
14376         int i, j, nrMoves = lastMove - firstMove;
14377
14378         if(appData.icsActive) { // only in local mode
14379                 forwardMostMove = currentMove; // mimic old ICS behavior
14380                 return;
14381         }
14382         if(storedGames >= MAX_VARIATIONS-1) return;
14383
14384         // push current tail of game on stack
14385         savedResult[storedGames] = gameInfo.result;
14386         savedDetails[storedGames] = gameInfo.resultDetails;
14387         gameInfo.resultDetails = NULL;
14388         savedFirst[storedGames] = firstMove;
14389         savedLast [storedGames] = lastMove;
14390         savedFramePtr[storedGames] = framePtr;
14391         framePtr -= nrMoves; // reserve space for the boards
14392         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14393             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14394             for(j=0; j<MOVE_LEN; j++)
14395                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14396             for(j=0; j<2*MOVE_LEN; j++)
14397                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14398             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14399             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14400             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14401             pvInfoList[firstMove+i-1].depth = 0;
14402             commentList[framePtr+i] = commentList[firstMove+i];
14403             commentList[firstMove+i] = NULL;
14404         }
14405
14406         storedGames++;
14407         forwardMostMove = currentMove; // truncte game so we can start variation
14408         if(storedGames == 1) GreyRevert(FALSE);
14409 }
14410
14411 Boolean
14412 PopTail(Boolean annotate)
14413 {
14414         int i, j, nrMoves;
14415         char buf[8000], moveBuf[20];
14416
14417         if(appData.icsActive) return FALSE; // only in local mode
14418         if(!storedGames) return FALSE; // sanity
14419
14420         storedGames--;
14421         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14422         nrMoves = savedLast[storedGames] - currentMove;
14423         if(annotate) {
14424                 int cnt = 10;
14425                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14426                 else strcpy(buf, "(");
14427                 for(i=currentMove; i<forwardMostMove; i++) {
14428                         if(WhiteOnMove(i))
14429                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14430                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14431                         strcat(buf, moveBuf);
14432                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14433                 }
14434                 strcat(buf, ")");
14435         }
14436         for(i=1; i<nrMoves; i++) { // copy last variation back
14437             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14438             for(j=0; j<MOVE_LEN; j++)
14439                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14440             for(j=0; j<2*MOVE_LEN; j++)
14441                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14442             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14443             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14444             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14445             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14446             commentList[currentMove+i] = commentList[framePtr+i];
14447             commentList[framePtr+i] = NULL;
14448         }
14449         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14450         framePtr = savedFramePtr[storedGames];
14451         gameInfo.result = savedResult[storedGames];
14452         if(gameInfo.resultDetails != NULL) {
14453             free(gameInfo.resultDetails);
14454       }
14455         gameInfo.resultDetails = savedDetails[storedGames];
14456         forwardMostMove = currentMove + nrMoves;
14457         if(storedGames == 0) GreyRevert(TRUE);
14458         return TRUE;
14459 }
14460
14461 void 
14462 CleanupTail()
14463 {       // remove all shelved variations
14464         int i;
14465         for(i=0; i<storedGames; i++) {
14466             if(savedDetails[i])
14467                 free(savedDetails[i]);
14468             savedDetails[i] = NULL;
14469         }
14470         for(i=framePtr; i<MAX_MOVES; i++) {
14471                 if(commentList[i]) free(commentList[i]);
14472                 commentList[i] = NULL;
14473         }
14474         framePtr = MAX_MOVES-1;
14475         storedGames = 0;
14476 }