Merge branch 'master' into gtk
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 #else
135 # define _(s) (s)
136 # define N_(s) s
137 #endif
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
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    if (appData.debugMode) {
1976      fprintf(debugFP, "Switch board from %s to %s\n",
1977              VariantName(gameInfo.variant), VariantName(newVariant));
1978      setbuf(debugFP, NULL);
1979    }
1980    shuffleOpenings = 0;       /* [HGM] shuffle */
1981    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982    switch(newVariant) 
1983      {
1984      case VariantShogi:
1985        newWidth = 9;  newHeight = 9;
1986        gameInfo.holdingsSize = 7;
1987      case VariantBughouse:
1988      case VariantCrazyhouse:
1989        newHoldingsWidth = 2; break;
1990      case VariantGreat:
1991        newWidth = 10;
1992      case VariantSuper:
1993        newHoldingsWidth = 2;
1994        gameInfo.holdingsSize = 8;
1995        break;
1996      case VariantGothic:
1997      case VariantCapablanca:
1998      case VariantCapaRandom:
1999        newWidth = 10;
2000      default:
2001        newHoldingsWidth = gameInfo.holdingsSize = 0;
2002      };
2003    
2004    if(newWidth  != gameInfo.boardWidth  ||
2005       newHeight != gameInfo.boardHeight ||
2006       newHoldingsWidth != gameInfo.holdingsWidth ) {
2007      
2008      /* shift position to new playing area, if needed */
2009      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010        for(i=0; i<BOARD_HEIGHT; i++) 
2011          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013              board[i][j];
2014        for(i=0; i<newHeight; i++) {
2015          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017        }
2018      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019        for(i=0; i<BOARD_HEIGHT; i++)
2020          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2022              board[i][j];
2023      }
2024      gameInfo.boardWidth  = newWidth;
2025      gameInfo.boardHeight = newHeight;
2026      gameInfo.holdingsWidth = newHoldingsWidth;
2027      gameInfo.variant = newVariant;
2028      InitDrawingSizes(-2, 0);
2029    } else gameInfo.variant = newVariant;
2030    CopyBoard(oldBoard, board);   // remember correctly formatted board
2031      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2032    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2033 }
2034
2035 static int loggedOn = FALSE;
2036
2037 /*-- Game start info cache: --*/
2038 int gs_gamenum;
2039 char gs_kind[MSG_SIZ];
2040 static char player1Name[128] = "";
2041 static char player2Name[128] = "";
2042 static char cont_seq[] = "\n\\   ";
2043 static int player1Rating = -1;
2044 static int player2Rating = -1;
2045 /*----------------------------*/
2046
2047 ColorClass curColor = ColorNormal;
2048 int suppressKibitz = 0;
2049
2050 void
2051 read_from_ics(isr, closure, data, count, error)
2052      InputSourceRef isr;
2053      VOIDSTAR closure;
2054      char *data;
2055      int count;
2056      int error;
2057 {
2058 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2059 #define STARTED_NONE 0
2060 #define STARTED_MOVES 1
2061 #define STARTED_BOARD 2
2062 #define STARTED_OBSERVE 3
2063 #define STARTED_HOLDINGS 4
2064 #define STARTED_CHATTER 5
2065 #define STARTED_COMMENT 6
2066 #define STARTED_MOVES_NOHIDE 7
2067
2068     static int started = STARTED_NONE;
2069     static char parse[20000];
2070     static int parse_pos = 0;
2071     static char buf[BUF_SIZE + 1];
2072     static int firstTime = TRUE, intfSet = FALSE;
2073     static ColorClass prevColor = ColorNormal;
2074     static int savingComment = FALSE;
2075     static int cmatch = 0; // continuation sequence match
2076     char *bp;
2077     char str[500];
2078     int i, oldi;
2079     int buf_len;
2080     int next_out;
2081     int tkind;
2082     int backup;    /* [DM] For zippy color lines */
2083     char *p;
2084     char talker[MSG_SIZ]; // [HGM] chat
2085     int channel;
2086
2087     if (appData.debugMode) {
2088       if (!error) {
2089         fprintf(debugFP, "<ICS: ");
2090         show_bytes(debugFP, data, count);
2091         fprintf(debugFP, "\n");
2092       }
2093     }
2094
2095     if (appData.debugMode) { int f = forwardMostMove;
2096         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2098                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2099     }
2100     if (count > 0) {
2101         /* If last read ended with a partial line that we couldn't parse,
2102            prepend it to the new read and try again. */
2103         if (leftover_len > 0) {
2104             for (i=0; i<leftover_len; i++)
2105               buf[i] = buf[leftover_start + i];
2106         }
2107
2108     /* copy new characters into the buffer */
2109     bp = buf + leftover_len;
2110     buf_len=leftover_len;
2111     for (i=0; i<count; i++)
2112     {
2113         // ignore these
2114         if (data[i] == '\r')
2115             continue;
2116
2117         // join lines split by ICS?
2118         if (!appData.noJoin)
2119         {
2120             /*
2121                 Joining just consists of finding matches against the
2122                 continuation sequence, and discarding that sequence
2123                 if found instead of copying it.  So, until a match
2124                 fails, there's nothing to do since it might be the
2125                 complete sequence, and thus, something we don't want
2126                 copied.
2127             */
2128             if (data[i] == cont_seq[cmatch])
2129             {
2130                 cmatch++;
2131                 if (cmatch == strlen(cont_seq))
2132                 {
2133                     cmatch = 0; // complete match.  just reset the counter
2134
2135                     /*
2136                         it's possible for the ICS to not include the space
2137                         at the end of the last word, making our [correct]
2138                         join operation fuse two separate words.  the server
2139                         does this when the space occurs at the width setting.
2140                     */
2141                     if (!buf_len || buf[buf_len-1] != ' ')
2142                     {
2143                         *bp++ = ' ';
2144                         buf_len++;
2145                     }
2146                 }
2147                 continue;
2148             }
2149             else if (cmatch)
2150             {
2151                 /*
2152                     match failed, so we have to copy what matched before
2153                     falling through and copying this character.  In reality,
2154                     this will only ever be just the newline character, but
2155                     it doesn't hurt to be precise.
2156                 */
2157                 strncpy(bp, cont_seq, cmatch);
2158                 bp += cmatch;
2159                 buf_len += cmatch;
2160                 cmatch = 0;
2161             }
2162         }
2163
2164         // copy this char
2165         *bp++ = data[i];
2166         buf_len++;
2167     }
2168
2169         buf[buf_len] = NULLCHAR;
2170 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2171         next_out = 0;
2172         leftover_start = 0;
2173
2174         i = 0;
2175         while (i < buf_len) {
2176             /* Deal with part of the TELNET option negotiation
2177                protocol.  We refuse to do anything beyond the
2178                defaults, except that we allow the WILL ECHO option,
2179                which ICS uses to turn off password echoing when we are
2180                directly connected to it.  We reject this option
2181                if localLineEditing mode is on (always on in xboard)
2182                and we are talking to port 23, which might be a real
2183                telnet server that will try to keep WILL ECHO on permanently.
2184              */
2185             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2186                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2187                 unsigned char option;
2188                 oldi = i;
2189                 switch ((unsigned char) buf[++i]) {
2190                   case TN_WILL:
2191                     if (appData.debugMode)
2192                       fprintf(debugFP, "\n<WILL ");
2193                     switch (option = (unsigned char) buf[++i]) {
2194                       case TN_ECHO:
2195                         if (appData.debugMode)
2196                           fprintf(debugFP, "ECHO ");
2197                         /* Reply only if this is a change, according
2198                            to the protocol rules. */
2199                         if (remoteEchoOption) break;
2200                         if (appData.localLineEditing &&
2201                             atoi(appData.icsPort) == TN_PORT) {
2202                             TelnetRequest(TN_DONT, TN_ECHO);
2203                         } else {
2204                             EchoOff();
2205                             TelnetRequest(TN_DO, TN_ECHO);
2206                             remoteEchoOption = TRUE;
2207                         }
2208                         break;
2209                       default:
2210                         if (appData.debugMode)
2211                           fprintf(debugFP, "%d ", option);
2212                         /* Whatever this is, we don't want it. */
2213                         TelnetRequest(TN_DONT, option);
2214                         break;
2215                     }
2216                     break;
2217                   case TN_WONT:
2218                     if (appData.debugMode)
2219                       fprintf(debugFP, "\n<WONT ");
2220                     switch (option = (unsigned char) buf[++i]) {
2221                       case TN_ECHO:
2222                         if (appData.debugMode)
2223                           fprintf(debugFP, "ECHO ");
2224                         /* Reply only if this is a change, according
2225                            to the protocol rules. */
2226                         if (!remoteEchoOption) break;
2227                         EchoOn();
2228                         TelnetRequest(TN_DONT, TN_ECHO);
2229                         remoteEchoOption = FALSE;
2230                         break;
2231                       default:
2232                         if (appData.debugMode)
2233                           fprintf(debugFP, "%d ", (unsigned char) option);
2234                         /* Whatever this is, it must already be turned
2235                            off, because we never agree to turn on
2236                            anything non-default, so according to the
2237                            protocol rules, we don't reply. */
2238                         break;
2239                     }
2240                     break;
2241                   case TN_DO:
2242                     if (appData.debugMode)
2243                       fprintf(debugFP, "\n<DO ");
2244                     switch (option = (unsigned char) buf[++i]) {
2245                       default:
2246                         /* Whatever this is, we refuse to do it. */
2247                         if (appData.debugMode)
2248                           fprintf(debugFP, "%d ", option);
2249                         TelnetRequest(TN_WONT, option);
2250                         break;
2251                     }
2252                     break;
2253                   case TN_DONT:
2254                     if (appData.debugMode)
2255                       fprintf(debugFP, "\n<DONT ");
2256                     switch (option = (unsigned char) buf[++i]) {
2257                       default:
2258                         if (appData.debugMode)
2259                           fprintf(debugFP, "%d ", option);
2260                         /* Whatever this is, we are already not doing
2261                            it, because we never agree to do anything
2262                            non-default, so according to the protocol
2263                            rules, we don't reply. */
2264                         break;
2265                     }
2266                     break;
2267                   case TN_IAC:
2268                     if (appData.debugMode)
2269                       fprintf(debugFP, "\n<IAC ");
2270                     /* Doubled IAC; pass it through */
2271                     i--;
2272                     break;
2273                   default:
2274                     if (appData.debugMode)
2275                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2276                     /* Drop all other telnet commands on the floor */
2277                     break;
2278                 }
2279                 if (oldi > next_out)
2280                   SendToPlayer(&buf[next_out], oldi - next_out);
2281                 if (++i > next_out)
2282                   next_out = i;
2283                 continue;
2284             }
2285
2286             /* OK, this at least will *usually* work */
2287             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2288                 loggedOn = TRUE;
2289             }
2290
2291             if (loggedOn && !intfSet) {
2292                 if (ics_type == ICS_ICC) {
2293                   sprintf(str,
2294                           "/set-quietly interface %s\n/set-quietly style 12\n",
2295                           programVersion);
2296                 } else if (ics_type == ICS_CHESSNET) {
2297                   sprintf(str, "/style 12\n");
2298                 } else {
2299                   strcpy(str, "alias $ @\n$set interface ");
2300                   strcat(str, programVersion);
2301                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2302 #ifdef WIN32
2303                   strcat(str, "$iset nohighlight 1\n");
2304 #endif
2305                   strcat(str, "$iset lock 1\n$style 12\n");
2306                 }
2307                 SendToICS(str);
2308                 NotifyFrontendLogin();
2309                 intfSet = TRUE;
2310             }
2311
2312             if (started == STARTED_COMMENT) {
2313                 /* Accumulate characters in comment */
2314                 parse[parse_pos++] = buf[i];
2315                 if (buf[i] == '\n') {
2316                     parse[parse_pos] = NULLCHAR;
2317                     if(chattingPartner>=0) {
2318                         char mess[MSG_SIZ];
2319                         sprintf(mess, "%s%s", talker, parse);
2320                         OutputChatMessage(chattingPartner, mess);
2321                         chattingPartner = -1;
2322                     } else
2323                     if(!suppressKibitz) // [HGM] kibitz
2324                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2325                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2326                         int nrDigit = 0, nrAlph = 0, j;
2327                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2328                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2329                         parse[parse_pos] = NULLCHAR;
2330                         // try to be smart: if it does not look like search info, it should go to
2331                         // ICS interaction window after all, not to engine-output window.
2332                         for(j=0; j<parse_pos; j++) { // count letters and digits
2333                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2334                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2335                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2336                         }
2337                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2338                             int depth=0; float score;
2339                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2340                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2341                                 pvInfoList[forwardMostMove-1].depth = depth;
2342                                 pvInfoList[forwardMostMove-1].score = 100*score;
2343                             }
2344                             OutputKibitz(suppressKibitz, parse);
2345                             next_out = i+1; // [HGM] suppress printing in ICS window
2346                         } else {
2347                             char tmp[MSG_SIZ];
2348                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2349                             SendToPlayer(tmp, strlen(tmp));
2350                         }
2351                     }
2352                     started = STARTED_NONE;
2353                 } else {
2354                     /* Don't match patterns against characters in comment */
2355                     i++;
2356                     continue;
2357                 }
2358             }
2359             if (started == STARTED_CHATTER) {
2360                 if (buf[i] != '\n') {
2361                     /* Don't match patterns against characters in chatter */
2362                     i++;
2363                     continue;
2364                 }
2365                 started = STARTED_NONE;
2366             }
2367
2368             /* Kludge to deal with rcmd protocol */
2369             if (firstTime && looking_at(buf, &i, "\001*")) {
2370                 DisplayFatalError(&buf[1], 0, 1);
2371                 continue;
2372             } else {
2373                 firstTime = FALSE;
2374             }
2375
2376             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2377                 ics_type = ICS_ICC;
2378                 ics_prefix = "/";
2379                 if (appData.debugMode)
2380                   fprintf(debugFP, "ics_type %d\n", ics_type);
2381                 continue;
2382             }
2383             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2384                 ics_type = ICS_FICS;
2385                 ics_prefix = "$";
2386                 if (appData.debugMode)
2387                   fprintf(debugFP, "ics_type %d\n", ics_type);
2388                 continue;
2389             }
2390             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2391                 ics_type = ICS_CHESSNET;
2392                 ics_prefix = "/";
2393                 if (appData.debugMode)
2394                   fprintf(debugFP, "ics_type %d\n", ics_type);
2395                 continue;
2396             }
2397
2398             if (!loggedOn &&
2399                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2400                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2401                  looking_at(buf, &i, "will be \"*\""))) {
2402               strcpy(ics_handle, star_match[0]);
2403               continue;
2404             }
2405
2406             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2407               char buf[MSG_SIZ];
2408               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2409               DisplayIcsInteractionTitle(buf);
2410               have_set_title = TRUE;
2411             }
2412
2413             /* skip finger notes */
2414             if (started == STARTED_NONE &&
2415                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2416                  (buf[i] == '1' && buf[i+1] == '0')) &&
2417                 buf[i+2] == ':' && buf[i+3] == ' ') {
2418               started = STARTED_CHATTER;
2419               i += 3;
2420               continue;
2421             }
2422
2423             /* skip formula vars */
2424             if (started == STARTED_NONE &&
2425                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2426               started = STARTED_CHATTER;
2427               i += 3;
2428               continue;
2429             }
2430
2431             oldi = i;
2432             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2433             if (appData.autoKibitz && started == STARTED_NONE &&
2434                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2435                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2436                 if(looking_at(buf, &i, "* kibitzes: ") &&
2437                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2438                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2439                         suppressKibitz = TRUE;
2440                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2441                                 && (gameMode == IcsPlayingWhite)) ||
2442                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2443                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2444                             started = STARTED_CHATTER; // own kibitz we simply discard
2445                         else {
2446                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2447                             parse_pos = 0; parse[0] = NULLCHAR;
2448                             savingComment = TRUE;
2449                             suppressKibitz = gameMode != IcsObserving ? 2 :
2450                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2451                         }
2452                         continue;
2453                 } else
2454                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2455                     // suppress the acknowledgements of our own autoKibitz
2456                     SendToPlayer(star_match[0], strlen(star_match[0]));
2457                     looking_at(buf, &i, "*% "); // eat prompt
2458                     next_out = i;
2459                 }
2460             } // [HGM] kibitz: end of patch
2461
2462 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2463
2464             // [HGM] chat: intercept tells by users for which we have an open chat window
2465             channel = -1;
2466             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2467                                            looking_at(buf, &i, "* whispers:") ||
2468                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2469                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2470                 int p;
2471                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2472                 chattingPartner = -1;
2473
2474                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2475                 for(p=0; p<MAX_CHAT; p++) {
2476                     if(channel == atoi(chatPartner[p])) {
2477                     talker[0] = '['; strcat(talker, "]");
2478                     chattingPartner = p; break;
2479                     }
2480                 } else
2481                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2482                 for(p=0; p<MAX_CHAT; p++) {
2483                     if(!strcmp("WHISPER", chatPartner[p])) {
2484                         talker[0] = '['; strcat(talker, "]");
2485                         chattingPartner = p; break;
2486                     }
2487                 }
2488                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2489                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2490                     talker[0] = 0;
2491                     chattingPartner = p; break;
2492                 }
2493                 if(chattingPartner<0) i = oldi; else {
2494                     started = STARTED_COMMENT;
2495                     parse_pos = 0; parse[0] = NULLCHAR;
2496                     savingComment = TRUE;
2497                     suppressKibitz = TRUE;
2498                 }
2499             } // [HGM] chat: end of patch
2500
2501             if (appData.zippyTalk || appData.zippyPlay) {
2502                 /* [DM] Backup address for color zippy lines */
2503                 backup = i;
2504 #if ZIPPY
2505        #ifdef WIN32
2506                if (loggedOn == TRUE)
2507                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2508                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2509        #else
2510                 if (ZippyControl(buf, &i) ||
2511                     ZippyConverse(buf, &i) ||
2512                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2513                       loggedOn = TRUE;
2514                       if (!appData.colorize) continue;
2515                 }
2516        #endif
2517 #endif
2518             } // [DM] 'else { ' deleted
2519                 if (
2520                     /* Regular tells and says */
2521                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2522                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2523                     looking_at(buf, &i, "* says: ") ||
2524                     /* Don't color "message" or "messages" output */
2525                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2526                     looking_at(buf, &i, "*. * at *:*: ") ||
2527                     looking_at(buf, &i, "--* (*:*): ") ||
2528                     /* Message notifications (same color as tells) */
2529                     looking_at(buf, &i, "* has left a message ") ||
2530                     looking_at(buf, &i, "* just sent you a message:\n") ||
2531                     /* Whispers and kibitzes */
2532                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2533                     looking_at(buf, &i, "* kibitzes: ") ||
2534                     /* Channel tells */
2535                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2536
2537                   if (tkind == 1 && strchr(star_match[0], ':')) {
2538                       /* Avoid "tells you:" spoofs in channels */
2539                      tkind = 3;
2540                   }
2541                   if (star_match[0][0] == NULLCHAR ||
2542                       strchr(star_match[0], ' ') ||
2543                       (tkind == 3 && strchr(star_match[1], ' '))) {
2544                     /* Reject bogus matches */
2545                     i = oldi;
2546                   } else {
2547                     if (appData.colorize) {
2548                       if (oldi > next_out) {
2549                         SendToPlayer(&buf[next_out], oldi - next_out);
2550                         next_out = oldi;
2551                       }
2552                       switch (tkind) {
2553                       case 1:
2554                         Colorize(ColorTell, FALSE);
2555                         curColor = ColorTell;
2556                         break;
2557                       case 2:
2558                         Colorize(ColorKibitz, FALSE);
2559                         curColor = ColorKibitz;
2560                         break;
2561                       case 3:
2562                         p = strrchr(star_match[1], '(');
2563                         if (p == NULL) {
2564                           p = star_match[1];
2565                         } else {
2566                           p++;
2567                         }
2568                         if (atoi(p) == 1) {
2569                           Colorize(ColorChannel1, FALSE);
2570                           curColor = ColorChannel1;
2571                         } else {
2572                           Colorize(ColorChannel, FALSE);
2573                           curColor = ColorChannel;
2574                         }
2575                         break;
2576                       case 5:
2577                         curColor = ColorNormal;
2578                         break;
2579                       }
2580                     }
2581                     if (started == STARTED_NONE && appData.autoComment &&
2582                         (gameMode == IcsObserving ||
2583                          gameMode == IcsPlayingWhite ||
2584                          gameMode == IcsPlayingBlack)) {
2585                       parse_pos = i - oldi;
2586                       memcpy(parse, &buf[oldi], parse_pos);
2587                       parse[parse_pos] = NULLCHAR;
2588                       started = STARTED_COMMENT;
2589                       savingComment = TRUE;
2590                     } else {
2591                       started = STARTED_CHATTER;
2592                       savingComment = FALSE;
2593                     }
2594                     loggedOn = TRUE;
2595                     continue;
2596                   }
2597                 }
2598
2599                 if (looking_at(buf, &i, "* s-shouts: ") ||
2600                     looking_at(buf, &i, "* c-shouts: ")) {
2601                     if (appData.colorize) {
2602                         if (oldi > next_out) {
2603                             SendToPlayer(&buf[next_out], oldi - next_out);
2604                             next_out = oldi;
2605                         }
2606                         Colorize(ColorSShout, FALSE);
2607                         curColor = ColorSShout;
2608                     }
2609                     loggedOn = TRUE;
2610                     started = STARTED_CHATTER;
2611                     continue;
2612                 }
2613
2614                 if (looking_at(buf, &i, "--->")) {
2615                     loggedOn = TRUE;
2616                     continue;
2617                 }
2618
2619                 if (looking_at(buf, &i, "* shouts: ") ||
2620                     looking_at(buf, &i, "--> ")) {
2621                     if (appData.colorize) {
2622                         if (oldi > next_out) {
2623                             SendToPlayer(&buf[next_out], oldi - next_out);
2624                             next_out = oldi;
2625                         }
2626                         Colorize(ColorShout, FALSE);
2627                         curColor = ColorShout;
2628                     }
2629                     loggedOn = TRUE;
2630                     started = STARTED_CHATTER;
2631                     continue;
2632                 }
2633
2634                 if (looking_at( buf, &i, "Challenge:")) {
2635                     if (appData.colorize) {
2636                         if (oldi > next_out) {
2637                             SendToPlayer(&buf[next_out], oldi - next_out);
2638                             next_out = oldi;
2639                         }
2640                         Colorize(ColorChallenge, FALSE);
2641                         curColor = ColorChallenge;
2642                     }
2643                     loggedOn = TRUE;
2644                     continue;
2645                 }
2646
2647                 if (looking_at(buf, &i, "* offers you") ||
2648                     looking_at(buf, &i, "* offers to be") ||
2649                     looking_at(buf, &i, "* would like to") ||
2650                     looking_at(buf, &i, "* requests to") ||
2651                     looking_at(buf, &i, "Your opponent offers") ||
2652                     looking_at(buf, &i, "Your opponent requests")) {
2653
2654                     if (appData.colorize) {
2655                         if (oldi > next_out) {
2656                             SendToPlayer(&buf[next_out], oldi - next_out);
2657                             next_out = oldi;
2658                         }
2659                         Colorize(ColorRequest, FALSE);
2660                         curColor = ColorRequest;
2661                     }
2662                     continue;
2663                 }
2664
2665                 if (looking_at(buf, &i, "* (*) seeking")) {
2666                     if (appData.colorize) {
2667                         if (oldi > next_out) {
2668                             SendToPlayer(&buf[next_out], oldi - next_out);
2669                             next_out = oldi;
2670                         }
2671                         Colorize(ColorSeek, FALSE);
2672                         curColor = ColorSeek;
2673                     }
2674                     continue;
2675             }
2676
2677             if (looking_at(buf, &i, "\\   ")) {
2678                 if (prevColor != ColorNormal) {
2679                     if (oldi > next_out) {
2680                         SendToPlayer(&buf[next_out], oldi - next_out);
2681                         next_out = oldi;
2682                     }
2683                     Colorize(prevColor, TRUE);
2684                     curColor = prevColor;
2685                 }
2686                 if (savingComment) {
2687                     parse_pos = i - oldi;
2688                     memcpy(parse, &buf[oldi], parse_pos);
2689                     parse[parse_pos] = NULLCHAR;
2690                     started = STARTED_COMMENT;
2691                 } else {
2692                     started = STARTED_CHATTER;
2693                 }
2694                 continue;
2695             }
2696
2697             if (looking_at(buf, &i, "Black Strength :") ||
2698                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2699                 looking_at(buf, &i, "<10>") ||
2700                 looking_at(buf, &i, "#@#")) {
2701                 /* Wrong board style */
2702                 loggedOn = TRUE;
2703                 SendToICS(ics_prefix);
2704                 SendToICS("set style 12\n");
2705                 SendToICS(ics_prefix);
2706                 SendToICS("refresh\n");
2707                 continue;
2708             }
2709
2710             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2711                 ICSInitScript();
2712                 have_sent_ICS_logon = 1;
2713                 continue;
2714             }
2715
2716             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2717                 (looking_at(buf, &i, "\n<12> ") ||
2718                  looking_at(buf, &i, "<12> "))) {
2719                 loggedOn = TRUE;
2720                 if (oldi > next_out) {
2721                     SendToPlayer(&buf[next_out], oldi - next_out);
2722                 }
2723                 next_out = i;
2724                 started = STARTED_BOARD;
2725                 parse_pos = 0;
2726                 continue;
2727             }
2728
2729             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2730                 looking_at(buf, &i, "<b1> ")) {
2731                 if (oldi > next_out) {
2732                     SendToPlayer(&buf[next_out], oldi - next_out);
2733                 }
2734                 next_out = i;
2735                 started = STARTED_HOLDINGS;
2736                 parse_pos = 0;
2737                 continue;
2738             }
2739
2740             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2741                 loggedOn = TRUE;
2742                 /* Header for a move list -- first line */
2743
2744                 switch (ics_getting_history) {
2745                   case H_FALSE:
2746                     switch (gameMode) {
2747                       case IcsIdle:
2748                       case BeginningOfGame:
2749                         /* User typed "moves" or "oldmoves" while we
2750                            were idle.  Pretend we asked for these
2751                            moves and soak them up so user can step
2752                            through them and/or save them.
2753                            */
2754                         Reset(FALSE, TRUE);
2755                         gameMode = IcsObserving;
2756                         ModeHighlight();
2757                         ics_gamenum = -1;
2758                         ics_getting_history = H_GOT_UNREQ_HEADER;
2759                         break;
2760                       case EditGame: /*?*/
2761                       case EditPosition: /*?*/
2762                         /* Should above feature work in these modes too? */
2763                         /* For now it doesn't */
2764                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2765                         break;
2766                       default:
2767                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2768                         break;
2769                     }
2770                     break;
2771                   case H_REQUESTED:
2772                     /* Is this the right one? */
2773                     if (gameInfo.white && gameInfo.black &&
2774                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2775                         strcmp(gameInfo.black, star_match[2]) == 0) {
2776                         /* All is well */
2777                         ics_getting_history = H_GOT_REQ_HEADER;
2778                     }
2779                     break;
2780                   case H_GOT_REQ_HEADER:
2781                   case H_GOT_UNREQ_HEADER:
2782                   case H_GOT_UNWANTED_HEADER:
2783                   case H_GETTING_MOVES:
2784                     /* Should not happen */
2785                     DisplayError(_("Error gathering move list: two headers"), 0);
2786                     ics_getting_history = H_FALSE;
2787                     break;
2788                 }
2789
2790                 /* Save player ratings into gameInfo if needed */
2791                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2792                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2793                     (gameInfo.whiteRating == -1 ||
2794                      gameInfo.blackRating == -1)) {
2795
2796                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2797                     gameInfo.blackRating = string_to_rating(star_match[3]);
2798                     if (appData.debugMode)
2799                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2800                               gameInfo.whiteRating, gameInfo.blackRating);
2801                 }
2802                 continue;
2803             }
2804
2805             if (looking_at(buf, &i,
2806               "* * match, initial time: * minute*, increment: * second")) {
2807                 /* Header for a move list -- second line */
2808                 /* Initial board will follow if this is a wild game */
2809                 if (gameInfo.event != NULL) free(gameInfo.event);
2810                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2811                 gameInfo.event = StrSave(str);
2812                 /* [HGM] we switched variant. Translate boards if needed. */
2813                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2814                 continue;
2815             }
2816
2817             if (looking_at(buf, &i, "Move  ")) {
2818                 /* Beginning of a move list */
2819                 switch (ics_getting_history) {
2820                   case H_FALSE:
2821                     /* Normally should not happen */
2822                     /* Maybe user hit reset while we were parsing */
2823                     break;
2824                   case H_REQUESTED:
2825                     /* Happens if we are ignoring a move list that is not
2826                      * the one we just requested.  Common if the user
2827                      * tries to observe two games without turning off
2828                      * getMoveList */
2829                     break;
2830                   case H_GETTING_MOVES:
2831                     /* Should not happen */
2832                     DisplayError(_("Error gathering move list: nested"), 0);
2833                     ics_getting_history = H_FALSE;
2834                     break;
2835                   case H_GOT_REQ_HEADER:
2836                     ics_getting_history = H_GETTING_MOVES;
2837                     started = STARTED_MOVES;
2838                     parse_pos = 0;
2839                     if (oldi > next_out) {
2840                         SendToPlayer(&buf[next_out], oldi - next_out);
2841                     }
2842                     break;
2843                   case H_GOT_UNREQ_HEADER:
2844                     ics_getting_history = H_GETTING_MOVES;
2845                     started = STARTED_MOVES_NOHIDE;
2846                     parse_pos = 0;
2847                     break;
2848                   case H_GOT_UNWANTED_HEADER:
2849                     ics_getting_history = H_FALSE;
2850                     break;
2851                 }
2852                 continue;
2853             }
2854
2855             if (looking_at(buf, &i, "% ") ||
2856                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2857                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2858                 if(suppressKibitz) next_out = i;
2859                 savingComment = FALSE;
2860                 suppressKibitz = 0;
2861                 switch (started) {
2862                   case STARTED_MOVES:
2863                   case STARTED_MOVES_NOHIDE:
2864                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2865                     parse[parse_pos + i - oldi] = NULLCHAR;
2866                     ParseGameHistory(parse);
2867 #if ZIPPY
2868                     if (appData.zippyPlay && first.initDone) {
2869                         FeedMovesToProgram(&first, forwardMostMove);
2870                         if (gameMode == IcsPlayingWhite) {
2871                             if (WhiteOnMove(forwardMostMove)) {
2872                                 if (first.sendTime) {
2873                                   if (first.useColors) {
2874                                     SendToProgram("black\n", &first);
2875                                   }
2876                                   SendTimeRemaining(&first, TRUE);
2877                                 }
2878                                 if (first.useColors) {
2879                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2880                                 }
2881                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2882                                 first.maybeThinking = TRUE;
2883                             } else {
2884                                 if (first.usePlayother) {
2885                                   if (first.sendTime) {
2886                                     SendTimeRemaining(&first, TRUE);
2887                                   }
2888                                   SendToProgram("playother\n", &first);
2889                                   firstMove = FALSE;
2890                                 } else {
2891                                   firstMove = TRUE;
2892                                 }
2893                             }
2894                         } else if (gameMode == IcsPlayingBlack) {
2895                             if (!WhiteOnMove(forwardMostMove)) {
2896                                 if (first.sendTime) {
2897                                   if (first.useColors) {
2898                                     SendToProgram("white\n", &first);
2899                                   }
2900                                   SendTimeRemaining(&first, FALSE);
2901                                 }
2902                                 if (first.useColors) {
2903                                   SendToProgram("black\n", &first);
2904                                 }
2905                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2906                                 first.maybeThinking = TRUE;
2907                             } else {
2908                                 if (first.usePlayother) {
2909                                   if (first.sendTime) {
2910                                     SendTimeRemaining(&first, FALSE);
2911                                   }
2912                                   SendToProgram("playother\n", &first);
2913                                   firstMove = FALSE;
2914                                 } else {
2915                                   firstMove = TRUE;
2916                                 }
2917                             }
2918                         }
2919                     }
2920 #endif
2921                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2922                         /* Moves came from oldmoves or moves command
2923                            while we weren't doing anything else.
2924                            */
2925                         currentMove = forwardMostMove;
2926                         ClearHighlights();/*!!could figure this out*/
2927                         flipView = appData.flipView;
2928                         DrawPosition(TRUE, boards[currentMove]);
2929                         DisplayBothClocks();
2930                         sprintf(str, "%s vs. %s",
2931                                 gameInfo.white, gameInfo.black);
2932                         DisplayTitle(str);
2933                         gameMode = IcsIdle;
2934                     } else {
2935                         /* Moves were history of an active game */
2936                         if (gameInfo.resultDetails != NULL) {
2937                             free(gameInfo.resultDetails);
2938                             gameInfo.resultDetails = NULL;
2939                         }
2940                     }
2941                     HistorySet(parseList, backwardMostMove,
2942                                forwardMostMove, currentMove-1);
2943                     DisplayMove(currentMove - 1);
2944                     if (started == STARTED_MOVES) next_out = i;
2945                     started = STARTED_NONE;
2946                     ics_getting_history = H_FALSE;
2947                     break;
2948
2949                   case STARTED_OBSERVE:
2950                     started = STARTED_NONE;
2951                     SendToICS(ics_prefix);
2952                     SendToICS("refresh\n");
2953                     break;
2954
2955                   default:
2956                     break;
2957                 }
2958                 if(bookHit) { // [HGM] book: simulate book reply
2959                     static char bookMove[MSG_SIZ]; // a bit generous?
2960
2961                     programStats.nodes = programStats.depth = programStats.time =
2962                     programStats.score = programStats.got_only_move = 0;
2963                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2964
2965                     strcpy(bookMove, "move ");
2966                     strcat(bookMove, bookHit);
2967                     HandleMachineMove(bookMove, &first);
2968                 }
2969                 continue;
2970             }
2971
2972             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2973                  started == STARTED_HOLDINGS ||
2974                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2975                 /* Accumulate characters in move list or board */
2976                 parse[parse_pos++] = buf[i];
2977             }
2978
2979             /* Start of game messages.  Mostly we detect start of game
2980                when the first board image arrives.  On some versions
2981                of the ICS, though, we need to do a "refresh" after starting
2982                to observe in order to get the current board right away. */
2983             if (looking_at(buf, &i, "Adding game * to observation list")) {
2984                 started = STARTED_OBSERVE;
2985                 continue;
2986             }
2987
2988             /* Handle auto-observe */
2989             if (appData.autoObserve &&
2990                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2991                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2992                 char *player;
2993                 /* Choose the player that was highlighted, if any. */
2994                 if (star_match[0][0] == '\033' ||
2995                     star_match[1][0] != '\033') {
2996                     player = star_match[0];
2997                 } else {
2998                     player = star_match[2];
2999                 }
3000                 sprintf(str, "%sobserve %s\n",
3001                         ics_prefix, StripHighlightAndTitle(player));
3002                 SendToICS(str);
3003
3004                 /* Save ratings from notify string */
3005                 strcpy(player1Name, star_match[0]);
3006                 player1Rating = string_to_rating(star_match[1]);
3007                 strcpy(player2Name, star_match[2]);
3008                 player2Rating = string_to_rating(star_match[3]);
3009
3010                 if (appData.debugMode)
3011                   fprintf(debugFP,
3012                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3013                           player1Name, player1Rating,
3014                           player2Name, player2Rating);
3015
3016                 continue;
3017             }
3018
3019             /* Deal with automatic examine mode after a game,
3020                and with IcsObserving -> IcsExamining transition */
3021             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3022                 looking_at(buf, &i, "has made you an examiner of game *")) {
3023
3024                 int gamenum = atoi(star_match[0]);
3025                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3026                     gamenum == ics_gamenum) {
3027                     /* We were already playing or observing this game;
3028                        no need to refetch history */
3029                     gameMode = IcsExamining;
3030                     if (pausing) {
3031                         pauseExamForwardMostMove = forwardMostMove;
3032                     } else if (currentMove < forwardMostMove) {
3033                         ForwardInner(forwardMostMove);
3034                     }
3035                 } else {
3036                     /* I don't think this case really can happen */
3037                     SendToICS(ics_prefix);
3038                     SendToICS("refresh\n");
3039                 }
3040                 continue;
3041             }
3042
3043             /* Error messages */
3044 //          if (ics_user_moved) {
3045             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3046                 if (looking_at(buf, &i, "Illegal move") ||
3047                     looking_at(buf, &i, "Not a legal move") ||
3048                     looking_at(buf, &i, "Your king is in check") ||
3049                     looking_at(buf, &i, "It isn't your turn") ||
3050                     looking_at(buf, &i, "It is not your move")) {
3051                     /* Illegal move */
3052                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3053                         currentMove = --forwardMostMove;
3054                         DisplayMove(currentMove - 1); /* before DMError */
3055                         DrawPosition(FALSE, boards[currentMove]);
3056                         SwitchClocks();
3057                         DisplayBothClocks();
3058                     }
3059                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3060                     ics_user_moved = 0;
3061                     continue;
3062                 }
3063             }
3064
3065             if (looking_at(buf, &i, "still have time") ||
3066                 looking_at(buf, &i, "not out of time") ||
3067                 looking_at(buf, &i, "either player is out of time") ||
3068                 looking_at(buf, &i, "has timeseal; checking")) {
3069                 /* We must have called his flag a little too soon */
3070                 whiteFlag = blackFlag = FALSE;
3071                 continue;
3072             }
3073
3074             if (looking_at(buf, &i, "added * seconds to") ||
3075                 looking_at(buf, &i, "seconds were added to")) {
3076                 /* Update the clocks */
3077                 SendToICS(ics_prefix);
3078                 SendToICS("refresh\n");
3079                 continue;
3080             }
3081
3082             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3083                 ics_clock_paused = TRUE;
3084                 StopClocks();
3085                 continue;
3086             }
3087
3088             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3089                 ics_clock_paused = FALSE;
3090                 StartClocks();
3091                 continue;
3092             }
3093
3094             /* Grab player ratings from the Creating: message.
3095                Note we have to check for the special case when
3096                the ICS inserts things like [white] or [black]. */
3097             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3098                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3099                 /* star_matches:
3100                    0    player 1 name (not necessarily white)
3101                    1    player 1 rating
3102                    2    empty, white, or black (IGNORED)
3103                    3    player 2 name (not necessarily black)
3104                    4    player 2 rating
3105
3106                    The names/ratings are sorted out when the game
3107                    actually starts (below).
3108                 */
3109                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3110                 player1Rating = string_to_rating(star_match[1]);
3111                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3112                 player2Rating = string_to_rating(star_match[4]);
3113
3114                 if (appData.debugMode)
3115                   fprintf(debugFP,
3116                           "Ratings from 'Creating:' %s %d, %s %d\n",
3117                           player1Name, player1Rating,
3118                           player2Name, player2Rating);
3119
3120                 continue;
3121             }
3122
3123             /* Improved generic start/end-of-game messages */
3124             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3125                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3126                 /* If tkind == 0: */
3127                 /* star_match[0] is the game number */
3128                 /*           [1] is the white player's name */
3129                 /*           [2] is the black player's name */
3130                 /* For end-of-game: */
3131                 /*           [3] is the reason for the game end */
3132                 /*           [4] is a PGN end game-token, preceded by " " */
3133                 /* For start-of-game: */
3134                 /*           [3] begins with "Creating" or "Continuing" */
3135                 /*           [4] is " *" or empty (don't care). */
3136                 int gamenum = atoi(star_match[0]);
3137                 char *whitename, *blackname, *why, *endtoken;
3138                 ChessMove endtype = (ChessMove) 0;
3139
3140                 if (tkind == 0) {
3141                   whitename = star_match[1];
3142                   blackname = star_match[2];
3143                   why = star_match[3];
3144                   endtoken = star_match[4];
3145                 } else {
3146                   whitename = star_match[1];
3147                   blackname = star_match[3];
3148                   why = star_match[5];
3149                   endtoken = star_match[6];
3150                 }
3151
3152                 /* Game start messages */
3153                 if (strncmp(why, "Creating ", 9) == 0 ||
3154                     strncmp(why, "Continuing ", 11) == 0) {
3155                     gs_gamenum = gamenum;
3156                     strcpy(gs_kind, strchr(why, ' ') + 1);
3157                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
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       if (appData.premove)
4018           if (!gotPremove ||
4019              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4020              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4021               ClearPremoveHighlights();
4022
4023       DrawPosition(FALSE, boards[currentMove]);
4024       DisplayMove(moveNum - 1);
4025       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4026             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4027               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4028         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4029       }
4030     }
4031
4032     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4033 #if ZIPPY
4034     if(bookHit) { // [HGM] book: simulate book reply
4035         static char bookMove[MSG_SIZ]; // a bit generous?
4036
4037         programStats.nodes = programStats.depth = programStats.time =
4038         programStats.score = programStats.got_only_move = 0;
4039         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4040
4041         strcpy(bookMove, "move ");
4042         strcat(bookMove, bookHit);
4043         HandleMachineMove(bookMove, &first);
4044     }
4045 #endif
4046 }
4047
4048 void
4049 GetMoveListEvent()
4050 {
4051     char buf[MSG_SIZ];
4052     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4053         ics_getting_history = H_REQUESTED;
4054         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4055         SendToICS(buf);
4056     }
4057 }
4058
4059 void
4060 AnalysisPeriodicEvent(force)
4061      int force;
4062 {
4063     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4064          && !force) || !appData.periodicUpdates)
4065       return;
4066
4067     /* Send . command to Crafty to collect stats */
4068     SendToProgram(".\n", &first);
4069
4070     /* Don't send another until we get a response (this makes
4071        us stop sending to old Crafty's which don't understand
4072        the "." command (sending illegal cmds resets node count & time,
4073        which looks bad)) */
4074     programStats.ok_to_send = 0;
4075 }
4076
4077 void ics_update_width(new_width)
4078         int new_width;
4079 {
4080         ics_printf("set width %d\n", new_width);
4081 }
4082
4083 void
4084 SendMoveToProgram(moveNum, cps)
4085      int moveNum;
4086      ChessProgramState *cps;
4087 {
4088     char buf[MSG_SIZ];
4089
4090     if (cps->useUsermove) {
4091       SendToProgram("usermove ", cps);
4092     }
4093     if (cps->useSAN) {
4094       char *space;
4095       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4096         int len = space - parseList[moveNum];
4097         memcpy(buf, parseList[moveNum], len);
4098         buf[len++] = '\n';
4099         buf[len] = NULLCHAR;
4100       } else {
4101         sprintf(buf, "%s\n", parseList[moveNum]);
4102       }
4103       SendToProgram(buf, cps);
4104     } else {
4105       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4106         AlphaRank(moveList[moveNum], 4);
4107         SendToProgram(moveList[moveNum], cps);
4108         AlphaRank(moveList[moveNum], 4); // and back
4109       } else
4110       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4111        * the engine. It would be nice to have a better way to identify castle
4112        * moves here. */
4113       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4114                                                                          && cps->useOOCastle) {
4115         int fromX = moveList[moveNum][0] - AAA;
4116         int fromY = moveList[moveNum][1] - ONE;
4117         int toX = moveList[moveNum][2] - AAA;
4118         int toY = moveList[moveNum][3] - ONE;
4119         if((boards[moveNum][fromY][fromX] == WhiteKing
4120             && boards[moveNum][toY][toX] == WhiteRook)
4121            || (boards[moveNum][fromY][fromX] == BlackKing
4122                && boards[moveNum][toY][toX] == BlackRook)) {
4123           if(toX > fromX) SendToProgram("O-O\n", cps);
4124           else SendToProgram("O-O-O\n", cps);
4125         }
4126         else SendToProgram(moveList[moveNum], cps);
4127       }
4128       else SendToProgram(moveList[moveNum], cps);
4129       /* End of additions by Tord */
4130     }
4131
4132     /* [HGM] setting up the opening has brought engine in force mode! */
4133     /*       Send 'go' if we are in a mode where machine should play. */
4134     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4135         (gameMode == TwoMachinesPlay   ||
4136 #ifdef ZIPPY
4137          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4138 #endif
4139          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4140         SendToProgram("go\n", cps);
4141   if (appData.debugMode) {
4142     fprintf(debugFP, "(extra)\n");
4143   }
4144     }
4145     setboardSpoiledMachineBlack = 0;
4146 }
4147
4148 void
4149 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4150      ChessMove moveType;
4151      int fromX, fromY, toX, toY;
4152 {
4153     char user_move[MSG_SIZ];
4154
4155     switch (moveType) {
4156       default:
4157         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4158                 (int)moveType, fromX, fromY, toX, toY);
4159         DisplayError(user_move + strlen("say "), 0);
4160         break;
4161       case WhiteKingSideCastle:
4162       case BlackKingSideCastle:
4163       case WhiteQueenSideCastleWild:
4164       case BlackQueenSideCastleWild:
4165       /* PUSH Fabien */
4166       case WhiteHSideCastleFR:
4167       case BlackHSideCastleFR:
4168       /* POP Fabien */
4169         sprintf(user_move, "o-o\n");
4170         break;
4171       case WhiteQueenSideCastle:
4172       case BlackQueenSideCastle:
4173       case WhiteKingSideCastleWild:
4174       case BlackKingSideCastleWild:
4175       /* PUSH Fabien */
4176       case WhiteASideCastleFR:
4177       case BlackASideCastleFR:
4178       /* POP Fabien */
4179         sprintf(user_move, "o-o-o\n");
4180         break;
4181       case WhitePromotionQueen:
4182       case BlackPromotionQueen:
4183       case WhitePromotionRook:
4184       case BlackPromotionRook:
4185       case WhitePromotionBishop:
4186       case BlackPromotionBishop:
4187       case WhitePromotionKnight:
4188       case BlackPromotionKnight:
4189       case WhitePromotionKing:
4190       case BlackPromotionKing:
4191       case WhitePromotionChancellor:
4192       case BlackPromotionChancellor:
4193       case WhitePromotionArchbishop:
4194       case BlackPromotionArchbishop:
4195         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4196             sprintf(user_move, "%c%c%c%c=%c\n",
4197                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4198                 PieceToChar(WhiteFerz));
4199         else if(gameInfo.variant == VariantGreat)
4200             sprintf(user_move, "%c%c%c%c=%c\n",
4201                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4202                 PieceToChar(WhiteMan));
4203         else
4204             sprintf(user_move, "%c%c%c%c=%c\n",
4205                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4206                 PieceToChar(PromoPiece(moveType)));
4207         break;
4208       case WhiteDrop:
4209       case BlackDrop:
4210         sprintf(user_move, "%c@%c%c\n",
4211                 ToUpper(PieceToChar((ChessSquare) fromX)),
4212                 AAA + toX, ONE + toY);
4213         break;
4214       case NormalMove:
4215       case WhiteCapturesEnPassant:
4216       case BlackCapturesEnPassant:
4217       case IllegalMove:  /* could be a variant we don't quite understand */
4218         sprintf(user_move, "%c%c%c%c\n",
4219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4220         break;
4221     }
4222     SendToICS(user_move);
4223     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4224         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4225 }
4226
4227 void
4228 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4229      int rf, ff, rt, ft;
4230      char promoChar;
4231      char move[7];
4232 {
4233     if (rf == DROP_RANK) {
4234         sprintf(move, "%c@%c%c\n",
4235                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4236     } else {
4237         if (promoChar == 'x' || promoChar == NULLCHAR) {
4238             sprintf(move, "%c%c%c%c\n",
4239                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4240         } else {
4241             sprintf(move, "%c%c%c%c%c\n",
4242                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4243         }
4244     }
4245 }
4246
4247 void
4248 ProcessICSInitScript(f)
4249      FILE *f;
4250 {
4251     char buf[MSG_SIZ];
4252
4253     while (fgets(buf, MSG_SIZ, f)) {
4254         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4255     }
4256
4257     fclose(f);
4258 }
4259
4260
4261 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4262 void
4263 AlphaRank(char *move, int n)
4264 {
4265 //    char *p = move, c; int x, y;
4266
4267     if (appData.debugMode) {
4268         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4269     }
4270
4271     if(move[1]=='*' &&
4272        move[2]>='0' && move[2]<='9' &&
4273        move[3]>='a' && move[3]<='x'    ) {
4274         move[1] = '@';
4275         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4276         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4277     } else
4278     if(move[0]>='0' && move[0]<='9' &&
4279        move[1]>='a' && move[1]<='x' &&
4280        move[2]>='0' && move[2]<='9' &&
4281        move[3]>='a' && move[3]<='x'    ) {
4282         /* input move, Shogi -> normal */
4283         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4284         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4285         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4286         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4287     } else
4288     if(move[1]=='@' &&
4289        move[3]>='0' && move[3]<='9' &&
4290        move[2]>='a' && move[2]<='x'    ) {
4291         move[1] = '*';
4292         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4293         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4294     } else
4295     if(
4296        move[0]>='a' && move[0]<='x' &&
4297        move[3]>='0' && move[3]<='9' &&
4298        move[2]>='a' && move[2]<='x'    ) {
4299          /* output move, normal -> Shogi */
4300         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4301         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4302         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4303         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4304         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4305     }
4306     if (appData.debugMode) {
4307         fprintf(debugFP, "   out = '%s'\n", move);
4308     }
4309 }
4310
4311 /* Parser for moves from gnuchess, ICS, or user typein box */
4312 Boolean
4313 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4314      char *move;
4315      int moveNum;
4316      ChessMove *moveType;
4317      int *fromX, *fromY, *toX, *toY;
4318      char *promoChar;
4319 {
4320     if (appData.debugMode) {
4321         fprintf(debugFP, "move to parse: %s\n", move);
4322     }
4323     *moveType = yylexstr(moveNum, move);
4324
4325     switch (*moveType) {
4326       case WhitePromotionChancellor:
4327       case BlackPromotionChancellor:
4328       case WhitePromotionArchbishop:
4329       case BlackPromotionArchbishop:
4330       case WhitePromotionQueen:
4331       case BlackPromotionQueen:
4332       case WhitePromotionRook:
4333       case BlackPromotionRook:
4334       case WhitePromotionBishop:
4335       case BlackPromotionBishop:
4336       case WhitePromotionKnight:
4337       case BlackPromotionKnight:
4338       case WhitePromotionKing:
4339       case BlackPromotionKing:
4340       case NormalMove:
4341       case WhiteCapturesEnPassant:
4342       case BlackCapturesEnPassant:
4343       case WhiteKingSideCastle:
4344       case WhiteQueenSideCastle:
4345       case BlackKingSideCastle:
4346       case BlackQueenSideCastle:
4347       case WhiteKingSideCastleWild:
4348       case WhiteQueenSideCastleWild:
4349       case BlackKingSideCastleWild:
4350       case BlackQueenSideCastleWild:
4351       /* Code added by Tord: */
4352       case WhiteHSideCastleFR:
4353       case WhiteASideCastleFR:
4354       case BlackHSideCastleFR:
4355       case BlackASideCastleFR:
4356       /* End of code added by Tord */
4357       case IllegalMove:         /* bug or odd chess variant */
4358         *fromX = currentMoveString[0] - AAA;
4359         *fromY = currentMoveString[1] - ONE;
4360         *toX = currentMoveString[2] - AAA;
4361         *toY = currentMoveString[3] - ONE;
4362         *promoChar = currentMoveString[4];
4363         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4364             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4365     if (appData.debugMode) {
4366         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4367     }
4368             *fromX = *fromY = *toX = *toY = 0;
4369             return FALSE;
4370         }
4371         if (appData.testLegality) {
4372           return (*moveType != IllegalMove);
4373         } else {
4374           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4375                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4376         }
4377
4378       case WhiteDrop:
4379       case BlackDrop:
4380         *fromX = *moveType == WhiteDrop ?
4381           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4382           (int) CharToPiece(ToLower(currentMoveString[0]));
4383         *fromY = DROP_RANK;
4384         *toX = currentMoveString[2] - AAA;
4385         *toY = currentMoveString[3] - ONE;
4386         *promoChar = NULLCHAR;
4387         return TRUE;
4388
4389       case AmbiguousMove:
4390       case ImpossibleMove:
4391       case (ChessMove) 0:       /* end of file */
4392       case ElapsedTime:
4393       case Comment:
4394       case PGNTag:
4395       case NAG:
4396       case WhiteWins:
4397       case BlackWins:
4398       case GameIsDrawn:
4399       default:
4400     if (appData.debugMode) {
4401         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4402     }
4403         /* bug? */
4404         *fromX = *fromY = *toX = *toY = 0;
4405         *promoChar = NULLCHAR;
4406         return FALSE;
4407     }
4408 }
4409
4410
4411 void
4412 ParsePV(char *pv)
4413 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4414   int fromX, fromY, toX, toY; char promoChar;
4415   ChessMove moveType;
4416   Boolean valid;
4417   int nr = 0;
4418
4419   endPV = forwardMostMove;
4420   do {
4421     while(*pv == ' ') pv++;
4422     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4423     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4424 if(appData.debugMode){
4425 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4426 }
4427     if(!valid && nr == 0 &&
4428        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4429         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4430     }
4431     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4432     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4433     nr++;
4434     if(endPV+1 > framePtr) break; // no space, truncate
4435     if(!valid) break;
4436     endPV++;
4437     CopyBoard(boards[endPV], boards[endPV-1]);
4438     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4439     moveList[endPV-1][0] = fromX + AAA;
4440     moveList[endPV-1][1] = fromY + ONE;
4441     moveList[endPV-1][2] = toX + AAA;
4442     moveList[endPV-1][3] = toY + ONE;
4443     parseList[endPV-1][0] = NULLCHAR;
4444   } while(valid);
4445   currentMove = endPV;
4446   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4447   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4448                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4449   DrawPosition(TRUE, boards[currentMove]);
4450 }
4451
4452 static int lastX, lastY;
4453
4454 Boolean
4455 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4456 {
4457         int startPV;
4458
4459         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4460         lastX = x; lastY = y;
4461         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4462         startPV = index;
4463       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4464       index = startPV;
4465         while(buf[index] && buf[index] != '\n') index++;
4466         buf[index] = 0;
4467         ParsePV(buf+startPV);
4468         *start = startPV; *end = index-1;
4469         return TRUE;
4470 }
4471
4472 Boolean
4473 LoadPV(int x, int y)
4474 { // called on right mouse click to load PV
4475   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4476   lastX = x; lastY = y;
4477   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4478   return TRUE;
4479 }
4480
4481 void
4482 UnLoadPV()
4483 {
4484   if(endPV < 0) return;
4485   endPV = -1;
4486   currentMove = forwardMostMove;
4487   ClearPremoveHighlights();
4488   DrawPosition(TRUE, boards[currentMove]);
4489 }
4490
4491 void
4492 MovePV(int x, int y, int h)
4493 { // step through PV based on mouse coordinates (called on mouse move)
4494   int margin = h>>3, step = 0;
4495
4496   if(endPV < 0) return;
4497   // we must somehow check if right button is still down (might be released off board!)
4498   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4499   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4500   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4501   if(!step) return;
4502   lastX = x; lastY = y;
4503   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4504   currentMove += step;
4505   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4506   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4507                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4508   DrawPosition(FALSE, boards[currentMove]);
4509 }
4510
4511
4512 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4513 // All positions will have equal probability, but the current method will not provide a unique
4514 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4515 #define DARK 1
4516 #define LITE 2
4517 #define ANY 3
4518
4519 int squaresLeft[4];
4520 int piecesLeft[(int)BlackPawn];
4521 int seed, nrOfShuffles;
4522
4523 void GetPositionNumber()
4524 {       // sets global variable seed
4525         int i;
4526
4527         seed = appData.defaultFrcPosition;
4528         if(seed < 0) { // randomize based on time for negative FRC position numbers
4529                 for(i=0; i<50; i++) seed += random();
4530                 seed = random() ^ random() >> 8 ^ random() << 8;
4531                 if(seed<0) seed = -seed;
4532         }
4533 }
4534
4535 int put(Board board, int pieceType, int rank, int n, int shade)
4536 // put the piece on the (n-1)-th empty squares of the given shade
4537 {
4538         int i;
4539
4540         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4541                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4542                         board[rank][i] = (ChessSquare) pieceType;
4543                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4544                         squaresLeft[ANY]--;
4545                         piecesLeft[pieceType]--;
4546                         return i;
4547                 }
4548         }
4549         return -1;
4550 }
4551
4552
4553 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4554 // calculate where the next piece goes, (any empty square), and put it there
4555 {
4556         int i;
4557
4558         i = seed % squaresLeft[shade];
4559         nrOfShuffles *= squaresLeft[shade];
4560         seed /= squaresLeft[shade];
4561         put(board, pieceType, rank, i, shade);
4562 }
4563
4564 void AddTwoPieces(Board board, int pieceType, int rank)
4565 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4566 {
4567         int i, n=squaresLeft[ANY], j=n-1, k;
4568
4569         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4570         i = seed % k;  // pick one
4571         nrOfShuffles *= k;
4572         seed /= k;
4573         while(i >= j) i -= j--;
4574         j = n - 1 - j; i += j;
4575         put(board, pieceType, rank, j, ANY);
4576         put(board, pieceType, rank, i, ANY);
4577 }
4578
4579 void SetUpShuffle(Board board, int number)
4580 {
4581         int i, p, first=1;
4582
4583         GetPositionNumber(); nrOfShuffles = 1;
4584
4585         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4586         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4587         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4588
4589         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4590
4591         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4592             p = (int) board[0][i];
4593             if(p < (int) BlackPawn) piecesLeft[p] ++;
4594             board[0][i] = EmptySquare;
4595         }
4596
4597         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4598             // shuffles restricted to allow normal castling put KRR first
4599             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4600                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4601             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4602                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4603             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4604                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4605             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4606                 put(board, WhiteRook, 0, 0, ANY);
4607             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4608         }
4609
4610         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4611             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4612             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4613                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4614                 while(piecesLeft[p] >= 2) {
4615                     AddOnePiece(board, p, 0, LITE);
4616                     AddOnePiece(board, p, 0, DARK);
4617                 }
4618                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4619             }
4620
4621         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4622             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4623             // but we leave King and Rooks for last, to possibly obey FRC restriction
4624             if(p == (int)WhiteRook) continue;
4625             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4626             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4627         }
4628
4629         // now everything is placed, except perhaps King (Unicorn) and Rooks
4630
4631         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4632             // Last King gets castling rights
4633             while(piecesLeft[(int)WhiteUnicorn]) {
4634                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4635                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4636             }
4637
4638             while(piecesLeft[(int)WhiteKing]) {
4639                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4640                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4641             }
4642
4643
4644         } else {
4645             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4646             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4647         }
4648
4649         // Only Rooks can be left; simply place them all
4650         while(piecesLeft[(int)WhiteRook]) {
4651                 i = put(board, WhiteRook, 0, 0, ANY);
4652                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4653                         if(first) {
4654                                 first=0;
4655                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4656                         }
4657                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4658                 }
4659         }
4660         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4661             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4662         }
4663
4664         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4665 }
4666
4667 int SetCharTable( char *table, const char * map )
4668 /* [HGM] moved here from winboard.c because of its general usefulness */
4669 /*       Basically a safe strcpy that uses the last character as King */
4670 {
4671     int result = FALSE; int NrPieces;
4672
4673     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4674                     && NrPieces >= 12 && !(NrPieces&1)) {
4675         int i; /* [HGM] Accept even length from 12 to 34 */
4676
4677         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4678         for( i=0; i<NrPieces/2-1; i++ ) {
4679             table[i] = map[i];
4680             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4681         }
4682         table[(int) WhiteKing]  = map[NrPieces/2-1];
4683         table[(int) BlackKing]  = map[NrPieces-1];
4684
4685         result = TRUE;
4686     }
4687
4688     return result;
4689 }
4690
4691 void Prelude(Board board)
4692 {       // [HGM] superchess: random selection of exo-pieces
4693         int i, j, k; ChessSquare p;
4694         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4695
4696         GetPositionNumber(); // use FRC position number
4697
4698         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4699             SetCharTable(pieceToChar, appData.pieceToCharTable);
4700             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4701                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4702         }
4703
4704         j = seed%4;                 seed /= 4;
4705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4708         j = seed%3 + (seed%3 >= j); seed /= 3;
4709         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4712         j = seed%3;                 seed /= 3;
4713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4716         j = seed%2 + (seed%2 >= j); seed /= 2;
4717         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4718         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4719         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4720         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4721         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4722         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4723         put(board, exoPieces[0],    0, 0, ANY);
4724         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4725 }
4726
4727 void
4728 InitPosition(redraw)
4729      int redraw;
4730 {
4731     ChessSquare (* pieces)[BOARD_FILES];
4732     int i, j, pawnRow, overrule,
4733     oldx = gameInfo.boardWidth,
4734     oldy = gameInfo.boardHeight,
4735     oldh = gameInfo.holdingsWidth,
4736     oldv = gameInfo.variant;
4737
4738     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4739
4740     /* [AS] Initialize pv info list [HGM] and game status */
4741     {
4742         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4743             pvInfoList[i].depth = 0;
4744             boards[i][EP_STATUS] = EP_NONE;
4745             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4746         }
4747
4748         initialRulePlies = 0; /* 50-move counter start */
4749
4750         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4751         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4752     }
4753
4754
4755     /* [HGM] logic here is completely changed. In stead of full positions */
4756     /* the initialized data only consist of the two backranks. The switch */
4757     /* selects which one we will use, which is than copied to the Board   */
4758     /* initialPosition, which for the rest is initialized by Pawns and    */
4759     /* empty squares. This initial position is then copied to boards[0],  */
4760     /* possibly after shuffling, so that it remains available.            */
4761
4762     gameInfo.holdingsWidth = 0; /* default board sizes */
4763     gameInfo.boardWidth    = 8;
4764     gameInfo.boardHeight   = 8;
4765     gameInfo.holdingsSize  = 0;
4766     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4767     for(i=0; i<BOARD_FILES-2; i++)
4768       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4769     initialPosition[EP_STATUS] = EP_NONE;
4770     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4771
4772     switch (gameInfo.variant) {
4773     case VariantFischeRandom:
4774       shuffleOpenings = TRUE;
4775     default:
4776       pieces = FIDEArray;
4777       break;
4778     case VariantShatranj:
4779       pieces = ShatranjArray;
4780       nrCastlingRights = 0;
4781       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4782       break;
4783     case VariantTwoKings:
4784       pieces = twoKingsArray;
4785       break;
4786     case VariantCapaRandom:
4787       shuffleOpenings = TRUE;
4788     case VariantCapablanca:
4789       pieces = CapablancaArray;
4790       gameInfo.boardWidth = 10;
4791       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4792       break;
4793     case VariantGothic:
4794       pieces = GothicArray;
4795       gameInfo.boardWidth = 10;
4796       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4797       break;
4798     case VariantJanus:
4799       pieces = JanusArray;
4800       gameInfo.boardWidth = 10;
4801       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4802       nrCastlingRights = 6;
4803         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4804         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4805         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4806         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4807         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4808         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4809       break;
4810     case VariantFalcon:
4811       pieces = FalconArray;
4812       gameInfo.boardWidth = 10;
4813       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4814       break;
4815     case VariantXiangqi:
4816       pieces = XiangqiArray;
4817       gameInfo.boardWidth  = 9;
4818       gameInfo.boardHeight = 10;
4819       nrCastlingRights = 0;
4820       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4821       break;
4822     case VariantShogi:
4823       pieces = ShogiArray;
4824       gameInfo.boardWidth  = 9;
4825       gameInfo.boardHeight = 9;
4826       gameInfo.holdingsSize = 7;
4827       nrCastlingRights = 0;
4828       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4829       break;
4830     case VariantCourier:
4831       pieces = CourierArray;
4832       gameInfo.boardWidth  = 12;
4833       nrCastlingRights = 0;
4834       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4835       break;
4836     case VariantKnightmate:
4837       pieces = KnightmateArray;
4838       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4839       break;
4840     case VariantFairy:
4841       pieces = fairyArray;
4842       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4843       break;
4844     case VariantGreat:
4845       pieces = GreatArray;
4846       gameInfo.boardWidth = 10;
4847       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4848       gameInfo.holdingsSize = 8;
4849       break;
4850     case VariantSuper:
4851       pieces = FIDEArray;
4852       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4853       gameInfo.holdingsSize = 8;
4854       startedFromSetupPosition = TRUE;
4855       break;
4856     case VariantCrazyhouse:
4857     case VariantBughouse:
4858       pieces = FIDEArray;
4859       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4860       gameInfo.holdingsSize = 5;
4861       break;
4862     case VariantWildCastle:
4863       pieces = FIDEArray;
4864       /* !!?shuffle with kings guaranteed to be on d or e file */
4865       shuffleOpenings = 1;
4866       break;
4867     case VariantNoCastle:
4868       pieces = FIDEArray;
4869       nrCastlingRights = 0;
4870       /* !!?unconstrained back-rank shuffle */
4871       shuffleOpenings = 1;
4872       break;
4873     }
4874
4875     overrule = 0;
4876     if(appData.NrFiles >= 0) {
4877         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4878         gameInfo.boardWidth = appData.NrFiles;
4879     }
4880     if(appData.NrRanks >= 0) {
4881         gameInfo.boardHeight = appData.NrRanks;
4882     }
4883     if(appData.holdingsSize >= 0) {
4884         i = appData.holdingsSize;
4885         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4886         gameInfo.holdingsSize = i;
4887     }
4888     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4889     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4890         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4891
4892     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4893     if(pawnRow < 1) pawnRow = 1;
4894
4895     /* User pieceToChar list overrules defaults */
4896     if(appData.pieceToCharTable != NULL)
4897         SetCharTable(pieceToChar, appData.pieceToCharTable);
4898
4899     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4900
4901         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4902             s = (ChessSquare) 0; /* account holding counts in guard band */
4903         for( i=0; i<BOARD_HEIGHT; i++ )
4904             initialPosition[i][j] = s;
4905
4906         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4907         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4908         initialPosition[pawnRow][j] = WhitePawn;
4909         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4910         if(gameInfo.variant == VariantXiangqi) {
4911             if(j&1) {
4912                 initialPosition[pawnRow][j] =
4913                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4914                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4915                    initialPosition[2][j] = WhiteCannon;
4916                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4917                 }
4918             }
4919         }
4920         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4921     }
4922     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4923
4924             j=BOARD_LEFT+1;
4925             initialPosition[1][j] = WhiteBishop;
4926             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4927             j=BOARD_RGHT-2;
4928             initialPosition[1][j] = WhiteRook;
4929             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4930     }
4931
4932     if( nrCastlingRights == -1) {
4933         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4934         /*       This sets default castling rights from none to normal corners   */
4935         /* Variants with other castling rights must set them themselves above    */
4936         nrCastlingRights = 6;
4937         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4938         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4939         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4940         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4941         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4942         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4943      }
4944
4945      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4946      if(gameInfo.variant == VariantGreat) { // promotion commoners
4947         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4948         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4949         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4950         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4951      }
4952   if (appData.debugMode) {
4953     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4954   }
4955     if(shuffleOpenings) {
4956         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4957         startedFromSetupPosition = TRUE;
4958     }
4959     if(startedFromPositionFile) {
4960       /* [HGM] loadPos: use PositionFile for every new game */
4961       CopyBoard(initialPosition, filePosition);
4962       for(i=0; i<nrCastlingRights; i++)
4963           initialRights[i] = filePosition[CASTLING][i];
4964       startedFromSetupPosition = TRUE;
4965     }
4966
4967     CopyBoard(boards[0], initialPosition);
4968     if(oldx != gameInfo.boardWidth ||
4969        oldy != gameInfo.boardHeight ||
4970        oldh != gameInfo.holdingsWidth
4971 #ifdef GOTHIC
4972        || oldv == VariantGothic ||        // For licensing popups
4973        gameInfo.variant == VariantGothic
4974 #endif
4975 #ifdef FALCON
4976        || oldv == VariantFalcon ||
4977        gameInfo.variant == VariantFalcon
4978 #endif
4979                                          )
4980       {
4981             InitDrawingSizes(-2 ,0);
4982       }
4983
4984     if (redraw)
4985       DrawPosition(TRUE, boards[currentMove]);
4986
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     {
5466       // [HGM] superchess: suppress promotions to non-available piece
5467       int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5468       if(WhiteOnMove(currentMove))
5469         {
5470           if(!boards[currentMove][k][BOARD_WIDTH-2])
5471             return 0;
5472         }
5473       else
5474         {
5475           if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5476             return 0;
5477         }
5478     }
5479   
5480   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5481      move type in caller when we know the move is a legal promotion */
5482   if(moveType == NormalMove && promoChar)
5483     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5484   
5485   /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5486      move type in caller when we know the move is a legal promotion */
5487   if(moveType == NormalMove && promoChar)
5488     moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5489   
5490   /* [HGM] convert drag-and-drop piece drops to standard form */
5491   if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5492     {
5493       moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5494       if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5495                                     moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5496       // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5497       if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5498       fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5499       while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5500       fromY = DROP_RANK;
5501     }
5502   
5503   /* [HGM] <popupFix> The following if has been moved here from
5504      UserMoveEvent(). Because it seemed to belong here (why not allow
5505      piece drops in training games?), and because it can only be
5506      performed after it is known to what we promote. */
5507   if (gameMode == Training) 
5508     {
5509       /* compare the move played on the board to the next move in the
5510        * game. If they match, display the move and the opponent's response.
5511        * If they don't match, display an error message.
5512        */
5513       int saveAnimate;
5514       Board testBoard;
5515       CopyBoard(testBoard, boards[currentMove]);
5516       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5517
5518       if (CompareBoards(testBoard, boards[currentMove+1]))
5519         {
5520           ForwardInner(currentMove+1);
5521
5522           /* Autoplay the opponent's response.
5523            * if appData.animate was TRUE when Training mode was entered,
5524            * the response will be animated.
5525            */
5526           saveAnimate = appData.animate;
5527           appData.animate = animateTraining;
5528           ForwardInner(currentMove+1);
5529           appData.animate = saveAnimate;
5530
5531           /* check for the end of the game */
5532           if (currentMove >= forwardMostMove)
5533             {
5534               gameMode = PlayFromGameFile;
5535               ModeHighlight();
5536               SetTrainingModeOff();
5537               DisplayInformation(_("End of game"));
5538             }
5539         }
5540       else
5541         {
5542           DisplayError(_("Incorrect move"), 0);
5543         }
5544       return 1;
5545     }
5546
5547   /* Ok, now we know that the move is good, so we can kill
5548      the previous line in Analysis Mode */
5549   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5550                                 && currentMove < forwardMostMove) {
5551     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5552   }
5553
5554   /* If we need the chess program but it's dead, restart it */
5555   ResurrectChessProgram();
5556
5557   /* A user move restarts a paused game*/
5558   if (pausing)
5559     PauseEvent();
5560
5561   thinkOutput[0] = NULLCHAR;
5562
5563   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5564
5565   if (gameMode == BeginningOfGame)
5566     {
5567       if (appData.noChessProgram)
5568         {
5569           gameMode = EditGame;
5570           SetGameInfo();
5571         }
5572       else
5573         {
5574           char buf[MSG_SIZ];
5575           gameMode = MachinePlaysBlack;
5576           StartClocks();
5577           SetGameInfo();
5578           sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5579           DisplayTitle(buf);
5580           if (first.sendName)
5581             {
5582               sprintf(buf, "name %s\n", gameInfo.white);
5583               SendToProgram(buf, &first);
5584             }
5585           StartClocks();
5586         }
5587       ModeHighlight();
5588     }
5589
5590   /* Relay move to ICS or chess engine */
5591   if (appData.icsActive)
5592     {
5593       if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5594           gameMode == IcsExamining)
5595         {
5596           SendMoveToICS(moveType, fromX, fromY, toX, toY);
5597           ics_user_moved = 1;
5598         }
5599     }
5600   else
5601     {
5602       if (first.sendTime && (gameMode == BeginningOfGame ||
5603                              gameMode == MachinePlaysWhite ||
5604                              gameMode == MachinePlaysBlack))
5605         {
5606           SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5607         }
5608       if (gameMode != EditGame && gameMode != PlayFromGameFile)
5609         {
5610           // [HGM] book: if program might be playing, let it use book
5611           bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5612           first.maybeThinking = TRUE;
5613         }
5614       else
5615         SendMoveToProgram(forwardMostMove-1, &first);
5616       if (currentMove == cmailOldMove + 1)
5617         {
5618           cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5619         }
5620     }
5621
5622   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5623
5624   switch (gameMode) 
5625     {
5626     case EditGame:
5627       switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) 
5628         {
5629         case MT_NONE:
5630         case MT_CHECK:
5631           break;
5632         case MT_CHECKMATE:
5633         case MT_STAINMATE:
5634           if (WhiteOnMove(currentMove)) {
5635             GameEnds(BlackWins, "Black mates", GE_PLAYER);
5636           } else {
5637             GameEnds(WhiteWins, "White mates", GE_PLAYER);
5638           }
5639           break;
5640         case MT_STALEMATE:
5641           GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5642           break;
5643         }
5644       break;
5645       
5646     case MachinePlaysBlack:
5647     case MachinePlaysWhite:
5648       /* disable certain menu options while machine is thinking */
5649       SetMachineThinkingEnables();
5650       break;
5651       
5652     default:
5653       break;
5654     }
5655   
5656   if(bookHit)
5657     { // [HGM] book: simulate book reply
5658       static char bookMove[MSG_SIZ]; // a bit generous?
5659
5660       programStats.nodes = programStats.depth = programStats.time =
5661         programStats.score = programStats.got_only_move = 0;
5662       sprintf(programStats.movelist, "%s (xbook)", bookHit);
5663
5664       strcpy(bookMove, "move ");
5665       strcat(bookMove, bookHit);
5666       HandleMachineMove(bookMove, &first);
5667     }
5668
5669   return 1;
5670 }
5671
5672 void
5673 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5674      int fromX, fromY, toX, toY;
5675      int promoChar;
5676 {
5677     /* [HGM] This routine was added to allow calling of its two logical
5678        parts from other modules in the old way. Before, UserMoveEvent()
5679        automatically called FinishMove() if the move was OK, and returned
5680        otherwise. I separated the two, in order to make it possible to
5681        slip a promotion popup in between. But that it always needs two
5682        calls, to the first part, (now called UserMoveTest() ), and to
5683        FinishMove if the first part succeeded. Calls that do not need
5684        to do anything in between, can call this routine the old way.
5685     */
5686   ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5687   if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5688   if(moveType == AmbiguousMove)
5689     DrawPosition(FALSE, boards[currentMove]);
5690   else if(moveType != ImpossibleMove && moveType != Comment)
5691     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5692 }
5693
5694 void
5695 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5696      Board board;
5697      int flags;
5698      ChessMove kind;
5699      int rf, ff, rt, ft;
5700      VOIDSTAR closure;
5701 {
5702     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5703     Markers *m = (Markers *) closure;
5704     if(rf == fromY && ff == fromX)
5705         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5706                          || kind == WhiteCapturesEnPassant
5707                          || kind == BlackCapturesEnPassant);
5708     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5709 }
5710
5711 void
5712 MarkTargetSquares(int clear)
5713 {
5714   int x, y;
5715   if(!appData.markers || !appData.highlightDragging || 
5716      !appData.testLegality || gameMode == EditPosition) return;
5717   if(clear) {
5718     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5719   } else {
5720     int capt = 0;
5721     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5722     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5723       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5724       if(capt)
5725       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5726     }
5727   }
5728   DrawPosition(TRUE, NULL);
5729 }
5730
5731 void LeftClick(ClickType clickType, int xPix, int yPix)
5732 {
5733     int x, y;
5734     Boolean saveAnimate;
5735     static int second = 0, promotionChoice = 0;
5736     char promoChoice = NULLCHAR;
5737
5738     if (clickType == Press) ErrorPopDown();
5739     MarkTargetSquares(1);
5740
5741     x = EventToSquare(xPix, BOARD_WIDTH);
5742     y = EventToSquare(yPix, BOARD_HEIGHT);
5743     if (!flipView && y >= 0) {
5744         y = BOARD_HEIGHT - 1 - y;
5745     }
5746     if (flipView && x >= 0) {
5747         x = BOARD_WIDTH - 1 - x;
5748     }
5749
5750     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5751         if(clickType == Release) return; // ignore upclick of click-click destination
5752         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5753         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5754         if(gameInfo.holdingsWidth && 
5755                 (WhiteOnMove(currentMove) 
5756                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5757                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5758             // click in right holdings, for determining promotion piece
5759             ChessSquare p = boards[currentMove][y][x];
5760             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5761             if(p != EmptySquare) {
5762                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5763                 fromX = fromY = -1;
5764                 return;
5765             }
5766         }
5767         DrawPosition(FALSE, boards[currentMove]);
5768         return;
5769     }
5770
5771     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5772     if(clickType == Press
5773             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5774               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5775               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5776         return;
5777
5778     if (fromX == -1) {
5779         if (clickType == Press) {
5780             /* First square */
5781             if (OKToStartUserMove(x, y)) {
5782                 fromX = x;
5783                 fromY = y;
5784                 second = 0;
5785                 MarkTargetSquares(0);
5786                 DragPieceBegin(xPix, yPix);
5787                 if (appData.highlightDragging) {
5788                     SetHighlights(x, y, -1, -1);
5789                 }
5790             }
5791         }
5792         return;
5793     }
5794
5795     /* fromX != -1 */
5796     if (clickType == Press && gameMode != EditPosition) {
5797         ChessSquare fromP;
5798         ChessSquare toP;
5799         int frc;
5800
5801         // ignore off-board to clicks
5802         if(y < 0 || x < 0) return;
5803
5804         /* Check if clicking again on the same color piece */
5805         fromP = boards[currentMove][fromY][fromX];
5806         toP = boards[currentMove][y][x];
5807         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5808         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5809              WhitePawn <= toP && toP <= WhiteKing &&
5810              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5811              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5812             (BlackPawn <= fromP && fromP <= BlackKing && 
5813              BlackPawn <= toP && toP <= BlackKing &&
5814              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5815              !(fromP == BlackKing && toP == BlackRook && frc))) {
5816             /* Clicked again on same color piece -- changed his mind */
5817             second = (x == fromX && y == fromY);
5818             if (appData.highlightDragging) {
5819                 SetHighlights(x, y, -1, -1);
5820             } else {
5821                 ClearHighlights();
5822             }
5823             if (OKToStartUserMove(x, y)) {
5824                 fromX = x;
5825                 fromY = y;
5826                 MarkTargetSquares(0);
5827                 DragPieceBegin(xPix, yPix);
5828             }
5829             return;
5830         }
5831         // ignore clicks on holdings
5832         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5833     }
5834
5835     if (clickType == Release && x == fromX && y == fromY) {
5836         DragPieceEnd(xPix, yPix);
5837         if (appData.animateDragging) {
5838             /* Undo animation damage if any */
5839             DrawPosition(FALSE, NULL);
5840         }
5841         if (second) {
5842             /* Second up/down in same square; just abort move */
5843             second = 0;
5844             fromX = fromY = -1;
5845             ClearHighlights();
5846             gotPremove = 0;
5847             ClearPremoveHighlights();
5848         } else {
5849             /* First upclick in same square; start click-click mode */
5850             SetHighlights(x, y, -1, -1);
5851         }
5852         return;
5853     }
5854
5855     /* we now have a different from- and (possibly off-board) to-square */
5856     /* Completed move */
5857     toX = x;
5858     toY = y;
5859     saveAnimate = appData.animate;
5860     if (clickType == Press) {
5861         /* Finish clickclick move */
5862         if (appData.animate || appData.highlightLastMove) {
5863             SetHighlights(fromX, fromY, toX, toY);
5864         } else {
5865             ClearHighlights();
5866         }
5867     } else {
5868         /* Finish drag move */
5869         if (appData.highlightLastMove) {
5870             SetHighlights(fromX, fromY, toX, toY);
5871         } else {
5872             ClearHighlights();
5873         }
5874         DragPieceEnd(xPix, yPix);
5875         /* Don't animate move and drag both */
5876         appData.animate = FALSE;
5877     }
5878
5879     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5880     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5881         ChessSquare piece = boards[currentMove][fromY][fromX];
5882         if(gameMode == EditPosition && piece != EmptySquare &&
5883            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5884             int n;
5885              
5886             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5887                 n = PieceToNumber(piece - (int)BlackPawn);
5888                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5889                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5890                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5891             } else
5892             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5893                 n = PieceToNumber(piece);
5894                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5895                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5896                 boards[currentMove][n][BOARD_WIDTH-2]++;
5897             }
5898             boards[currentMove][fromY][fromX] = EmptySquare;
5899         }
5900         ClearHighlights();
5901         fromX = fromY = -1;
5902         DrawPosition(TRUE, boards[currentMove]);
5903         return;
5904     }
5905
5906     // off-board moves should not be highlighted
5907     if(x < 0 || x < 0) ClearHighlights();
5908
5909     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5910         SetHighlights(fromX, fromY, toX, toY);
5911         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5912             // [HGM] super: promotion to captured piece selected from holdings
5913             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5914             promotionChoice = TRUE;
5915             // kludge follows to temporarily execute move on display, without promoting yet
5916             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5917             boards[currentMove][toY][toX] = p;
5918             DrawPosition(FALSE, boards[currentMove]);
5919             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5920             boards[currentMove][toY][toX] = q;
5921             DisplayMessage("Click in holdings to choose piece", "");
5922             return;
5923         }
5924         PromotionPopUp();
5925     } else {
5926         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5927         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5928         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5929         fromX = fromY = -1;
5930     }
5931     appData.animate = saveAnimate;
5932     if (appData.animate || appData.animateDragging) {
5933         /* Undo animation damage if needed */
5934         DrawPosition(FALSE, NULL);
5935     }
5936 }
5937
5938 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5939 {
5940 //    char * hint = lastHint;
5941     FrontEndProgramStats stats;
5942
5943     stats.which = cps == &first ? 0 : 1;
5944     stats.depth = cpstats->depth;
5945     stats.nodes = cpstats->nodes;
5946     stats.score = cpstats->score;
5947     stats.time = cpstats->time;
5948     stats.pv = cpstats->movelist;
5949     stats.hint = lastHint;
5950     stats.an_move_index = 0;
5951     stats.an_move_count = 0;
5952
5953     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5954         stats.hint = cpstats->move_name;
5955         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5956         stats.an_move_count = cpstats->nr_moves;
5957     }
5958
5959     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5960
5961     SetProgramStats( &stats );
5962 }
5963
5964 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5965 {   // [HGM] book: this routine intercepts moves to simulate book replies
5966     char *bookHit = NULL;
5967
5968     //first determine if the incoming move brings opponent into his book
5969     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5970         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5971     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5972     if(bookHit != NULL && !cps->bookSuspend) {
5973         // make sure opponent is not going to reply after receiving move to book position
5974         SendToProgram("force\n", cps);
5975         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5976     }
5977     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5978     // now arrange restart after book miss
5979     if(bookHit) {
5980         // after a book hit we never send 'go', and the code after the call to this routine
5981         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5982         char buf[MSG_SIZ];
5983         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5984         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5985         SendToProgram(buf, cps);
5986         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5987     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5988         SendToProgram("go\n", cps);
5989         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5990     } else { // 'go' might be sent based on 'firstMove' after this routine returns
5991         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5992             SendToProgram("go\n", cps);
5993         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5994     }
5995     return bookHit; // notify caller of hit, so it can take action to send move to opponent
5996 }
5997
5998 char *savedMessage;
5999 ChessProgramState *savedState;
6000 void DeferredBookMove(void)
6001 {
6002         if(savedState->lastPing != savedState->lastPong)
6003                     ScheduleDelayedEvent(DeferredBookMove, 10);
6004         else
6005         HandleMachineMove(savedMessage, savedState);
6006 }
6007
6008 void
6009 HandleMachineMove(message, cps)
6010      char *message;
6011      ChessProgramState *cps;
6012 {
6013     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6014     char realname[MSG_SIZ];
6015     int fromX, fromY, toX, toY;
6016     ChessMove moveType;
6017     char promoChar;
6018     char *p;
6019     int machineWhite;
6020     char *bookHit;
6021
6022     cps->userError = 0;
6023
6024 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6025     /*
6026      * Kludge to ignore BEL characters
6027      */
6028     while (*message == '\007') message++;
6029
6030     /*
6031      * [HGM] engine debug message: ignore lines starting with '#' character
6032      */
6033     if(cps->debug && *message == '#') return;
6034
6035     /*
6036      * Look for book output
6037      */
6038     if (cps == &first && bookRequested) {
6039         if (message[0] == '\t' || message[0] == ' ') {
6040             /* Part of the book output is here; append it */
6041             strcat(bookOutput, message);
6042             strcat(bookOutput, "  \n");
6043             return;
6044         } else if (bookOutput[0] != NULLCHAR) {
6045             /* All of book output has arrived; display it */
6046             char *p = bookOutput;
6047             while (*p != NULLCHAR) {
6048                 if (*p == '\t') *p = ' ';
6049                 p++;
6050             }
6051             DisplayInformation(bookOutput);
6052             bookRequested = FALSE;
6053             /* Fall through to parse the current output */
6054         }
6055     }
6056
6057     /*
6058      * Look for machine move.
6059      */
6060     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6061         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6062     {
6063         /* This method is only useful on engines that support ping */
6064         if (cps->lastPing != cps->lastPong) {
6065           if (gameMode == BeginningOfGame) {
6066             /* Extra move from before last new; ignore */
6067             if (appData.debugMode) {
6068                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6069             }
6070           } else {
6071             if (appData.debugMode) {
6072                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6073                         cps->which, gameMode);
6074             }
6075
6076             SendToProgram("undo\n", cps);
6077           }
6078           return;
6079         }
6080
6081         switch (gameMode) {
6082           case BeginningOfGame:
6083             /* Extra move from before last reset; ignore */
6084             if (appData.debugMode) {
6085                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6086             }
6087             return;
6088
6089           case EndOfGame:
6090           case IcsIdle:
6091           default:
6092             /* Extra move after we tried to stop.  The mode test is
6093                not a reliable way of detecting this problem, but it's
6094                the best we can do on engines that don't support ping.
6095             */
6096             if (appData.debugMode) {
6097                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6098                         cps->which, gameMode);
6099             }
6100             SendToProgram("undo\n", cps);
6101             return;
6102
6103           case MachinePlaysWhite:
6104           case IcsPlayingWhite:
6105             machineWhite = TRUE;
6106             break;
6107
6108           case MachinePlaysBlack:
6109           case IcsPlayingBlack:
6110             machineWhite = FALSE;
6111             break;
6112
6113           case TwoMachinesPlay:
6114             machineWhite = (cps->twoMachinesColor[0] == 'w');
6115             break;
6116         }
6117         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6118             if (appData.debugMode) {
6119                 fprintf(debugFP,
6120                         "Ignoring move out of turn by %s, gameMode %d"
6121                         ", forwardMost %d\n",
6122                         cps->which, gameMode, forwardMostMove);
6123             }
6124             return;
6125         }
6126
6127     if (appData.debugMode) { int f = forwardMostMove;
6128         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6129                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6130                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6131     }
6132         if(cps->alphaRank) AlphaRank(machineMove, 4);
6133         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6134                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6135             /* Machine move could not be parsed; ignore it. */
6136             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6137                     machineMove, cps->which);
6138             DisplayError(buf1, 0);
6139             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6140                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6141             if (gameMode == TwoMachinesPlay) {
6142               GameEnds(machineWhite ? BlackWins : WhiteWins,
6143                        buf1, GE_XBOARD);
6144             }
6145             return;
6146         }
6147
6148         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6149         /* So we have to redo legality test with true e.p. status here,  */
6150         /* to make sure an illegal e.p. capture does not slip through,   */
6151         /* to cause a forfeit on a justified illegal-move complaint      */
6152         /* of the opponent.                                              */
6153         if( gameMode==TwoMachinesPlay && appData.testLegality
6154             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6155                                                               ) {
6156            ChessMove moveType;
6157            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6158                              fromY, fromX, toY, toX, promoChar);
6159             if (appData.debugMode) {
6160                 int i;
6161                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6162                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6163                 fprintf(debugFP, "castling rights\n");
6164             }
6165             if(moveType == IllegalMove) {
6166                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6167                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6168                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6169                            buf1, GE_XBOARD);
6170                 return;
6171            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6172            /* [HGM] Kludge to handle engines that send FRC-style castling
6173               when they shouldn't (like TSCP-Gothic) */
6174            switch(moveType) {
6175              case WhiteASideCastleFR:
6176              case BlackASideCastleFR:
6177                toX+=2;
6178                currentMoveString[2]++;
6179                break;
6180              case WhiteHSideCastleFR:
6181              case BlackHSideCastleFR:
6182                toX--;
6183                currentMoveString[2]--;
6184                break;
6185              default: ; // nothing to do, but suppresses warning of pedantic compilers
6186            }
6187         }
6188         hintRequested = FALSE;
6189         lastHint[0] = NULLCHAR;
6190         bookRequested = FALSE;
6191         /* Program may be pondering now */
6192         cps->maybeThinking = TRUE;
6193         if (cps->sendTime == 2) cps->sendTime = 1;
6194         if (cps->offeredDraw) cps->offeredDraw--;
6195
6196 #if ZIPPY
6197         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6198             first.initDone) {
6199           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6200           ics_user_moved = 1;
6201           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6202                 char buf[3*MSG_SIZ];
6203
6204                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6205                         programStats.score / 100.,
6206                         programStats.depth,
6207                         programStats.time / 100.,
6208                         (unsigned int)programStats.nodes,
6209                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6210                         programStats.movelist);
6211                 SendToICS(buf);
6212 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6213           }
6214         }
6215 #endif
6216         /* currentMoveString is set as a side-effect of ParseOneMove */
6217         strcpy(machineMove, currentMoveString);
6218         strcat(machineMove, "\n");
6219         strcpy(moveList[forwardMostMove], machineMove);
6220
6221         /* [AS] Save move info and clear stats for next move */
6222         pvInfoList[ forwardMostMove ].score = programStats.score;
6223         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6224         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6225         ClearProgramStats();
6226         thinkOutput[0] = NULLCHAR;
6227         hiddenThinkOutputState = 0;
6228
6229         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6230
6231         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6232         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6233             int count = 0;
6234
6235             while( count < adjudicateLossPlies ) {
6236                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6237
6238                 if( count & 1 ) {
6239                     score = -score; /* Flip score for winning side */
6240                 }
6241
6242                 if( score > adjudicateLossThreshold ) {
6243                     break;
6244                 }
6245
6246                 count++;
6247             }
6248
6249             if( count >= adjudicateLossPlies ) {
6250                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6251
6252                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6253                     "Xboard adjudication",
6254                     GE_XBOARD );
6255
6256                 return;
6257             }
6258         }
6259
6260         if( gameMode == TwoMachinesPlay ) {
6261           // [HGM] some adjudications useful with buggy engines
6262             int k, count = 0; static int bare = 1;
6263           if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6264
6265
6266             if( appData.testLegality )
6267             {   /* [HGM] Some more adjudications for obstinate engines */
6268                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6269                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6270                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6271                 static int moveCount = 6;
6272                 ChessMove result;
6273                 char *reason = NULL;
6274
6275                 /* Count what is on board. */
6276                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6277                 {   ChessSquare p = boards[forwardMostMove][i][j];
6278                     int m=i;
6279
6280                     switch((int) p)
6281                     {   /* count B,N,R and other of each side */
6282                         case WhiteKing:
6283                         case BlackKing:
6284                              NrK++; break; // [HGM] atomic: count Kings
6285                         case WhiteKnight:
6286                              NrWN++; break;
6287                         case WhiteBishop:
6288                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6289                              bishopsColor |= 1 << ((i^j)&1);
6290                              NrWB++; break;
6291                         case BlackKnight:
6292                              NrBN++; break;
6293                         case BlackBishop:
6294                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6295                              bishopsColor |= 1 << ((i^j)&1);
6296                              NrBB++; break;
6297                         case WhiteRook:
6298                              NrWR++; break;
6299                         case BlackRook:
6300                              NrBR++; break;
6301                         case WhiteQueen:
6302                              NrWQ++; break;
6303                         case BlackQueen:
6304                              NrBQ++; break;
6305                         case EmptySquare:
6306                              break;
6307                         case BlackPawn:
6308                              m = 7-i;
6309                         case WhitePawn:
6310                              PawnAdvance += m; NrPawns++;
6311                     }
6312                     NrPieces += (p != EmptySquare);
6313                     NrW += ((int)p < (int)BlackPawn);
6314                     if(gameInfo.variant == VariantXiangqi &&
6315                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6316                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6317                         NrW -= ((int)p < (int)BlackPawn);
6318                     }
6319                 }
6320
6321                 /* Some material-based adjudications that have to be made before stalemate test */
6322                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6323                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6324                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6325                      if(appData.checkMates) {
6326                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6327                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6328                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6329                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6330                          return;
6331                      }
6332                 }
6333
6334                 /* Bare King in Shatranj (loses) or Losers (wins) */
6335                 if( NrW == 1 || NrPieces - NrW == 1) {
6336                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6337                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6338                      if(appData.checkMates) {
6339                          SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6340                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6341                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6342                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6343                          return;
6344                      }
6345                   } else
6346                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6347                   {    /* bare King */
6348                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6349                         if(appData.checkMates) {
6350                             /* but only adjudicate if adjudication enabled */
6351                             SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6352                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6353                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6354                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6355                             return;
6356                         }
6357                   }
6358                 } else bare = 1;
6359
6360
6361             // don't wait for engine to announce game end if we can judge ourselves
6362             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6363               case MT_CHECK:
6364                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6365                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6366                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6367                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6368                             checkCnt++;
6369                         if(checkCnt >= 2) {
6370                             reason = "Xboard adjudication: 3rd check";
6371                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6372                             break;
6373                         }
6374                     }
6375                 }
6376               case MT_NONE:
6377               default:
6378                 break;
6379               case MT_STALEMATE:
6380               case MT_STAINMATE:
6381                 reason = "Xboard adjudication: Stalemate";
6382                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6383                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6384                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6385                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6386                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6387                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6388                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6389                                                                         EP_CHECKMATE : EP_WINS);
6390                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6391                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6392                 }
6393                 break;
6394               case MT_CHECKMATE:
6395                 reason = "Xboard adjudication: Checkmate";
6396                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6397                 break;
6398             }
6399
6400                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6401                     case EP_STALEMATE:
6402                         result = GameIsDrawn; break;
6403                     case EP_CHECKMATE:
6404                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6405                     case EP_WINS:
6406                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6407                     default:
6408                         result = (ChessMove) 0;
6409                 }
6410                 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6411                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6412                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6413                     GameEnds( result, reason, GE_XBOARD );
6414                     return;
6415                 }
6416
6417                 /* Next absolutely insufficient mating material. */
6418                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6419                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6420                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6421                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6422                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6423
6424                      /* always flag draws, for judging claims */
6425                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6426
6427                      if(appData.materialDraws) {
6428                          /* but only adjudicate them if adjudication enabled */
6429                          SendToProgram("force\n", cps->other); // suppress reply
6430                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6431                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6432                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6433                          return;
6434                      }
6435                 }
6436
6437                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6438                 if(NrPieces == 4 &&
6439                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6440                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6441                    || NrWN==2 || NrBN==2     /* KNNK */
6442                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6443                   ) ) {
6444                      if(--moveCount < 0 && appData.trivialDraws)
6445                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6446                           SendToProgram("force\n", cps->other); // suppress reply
6447                           SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6448                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6449                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6450                           return;
6451                      }
6452                 } else moveCount = 6;
6453             }
6454           }
6455           
6456           if (appData.debugMode) { int i;
6457             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6458                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6459                     appData.drawRepeats);
6460             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6461               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6462             
6463           }
6464
6465                 /* Check for rep-draws */
6466                 count = 0;
6467                 for(k = forwardMostMove-2;
6468                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6469                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6470                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6471                     k-=2)
6472                 {   int rights=0;
6473                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6474                         /* compare castling rights */
6475                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6476                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6477                                 rights++; /* King lost rights, while rook still had them */
6478                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6479                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6480                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6481                                    rights++; /* but at least one rook lost them */
6482                         }
6483                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6484                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6485                                 rights++; 
6486                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6487                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6488                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6489                                    rights++;
6490                         }
6491                         if( rights == 0 && ++count > appData.drawRepeats-2
6492                             && appData.drawRepeats > 1) {
6493                              /* adjudicate after user-specified nr of repeats */
6494                              SendToProgram("force\n", cps->other); // suppress reply
6495                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6496                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6497                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6498                                 // [HGM] xiangqi: check for forbidden perpetuals
6499                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6500                                 for(m=forwardMostMove; m>k; m-=2) {
6501                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6502                                         ourPerpetual = 0; // the current mover did not always check
6503                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6504                                         hisPerpetual = 0; // the opponent did not always check
6505                                 }
6506                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6507                                                                         ourPerpetual, hisPerpetual);
6508                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6509                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6510                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6511                                     return;
6512                                 }
6513                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6514                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6515                                 // Now check for perpetual chases
6516                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6517                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6518                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6519                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6520                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6521                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6522                                         return;
6523                                     }
6524                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6525                                         break; // Abort repetition-checking loop.
6526                                 }
6527                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6528                              }
6529                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6530                              return;
6531                         }
6532                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6533                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6534                     }
6535                 }
6536
6537                 /* Now we test for 50-move draws. Determine ply count */
6538                 count = forwardMostMove;
6539                 /* look for last irreversble move */
6540                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6541                     count--;
6542                 /* if we hit starting position, add initial plies */
6543                 if( count == backwardMostMove )
6544                     count -= initialRulePlies;
6545                 count = forwardMostMove - count;
6546                 if( count >= 100)
6547                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6548                          /* this is used to judge if draw claims are legal */
6549                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6550                          SendToProgram("force\n", cps->other); // suppress reply
6551                          SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6552                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6553                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6554                          return;
6555                 }
6556
6557                 /* if draw offer is pending, treat it as a draw claim
6558                  * when draw condition present, to allow engines a way to
6559                  * claim draws before making their move to avoid a race
6560                  * condition occurring after their move
6561                  */
6562                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6563                          char *p = NULL;
6564                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6565                              p = "Draw claim: 50-move rule";
6566                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6567                              p = "Draw claim: 3-fold repetition";
6568                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6569                              p = "Draw claim: insufficient mating material";
6570                          if( p != NULL ) {
6571                              SendToProgram("force\n", cps->other); // suppress reply
6572                              SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6573                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6574                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6575                              return;
6576                          }
6577                 }
6578
6579
6580                 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6581                     SendToProgram("force\n", cps->other); // suppress reply
6582                     SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6583                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6584
6585                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6586
6587                     return;
6588                 }
6589         }
6590
6591         bookHit = NULL;
6592         if (gameMode == TwoMachinesPlay) {
6593             /* [HGM] relaying draw offers moved to after reception of move */
6594             /* and interpreting offer as claim if it brings draw condition */
6595             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6596                 SendToProgram("draw\n", cps->other);
6597             }
6598             if (cps->other->sendTime) {
6599                 SendTimeRemaining(cps->other,
6600                                   cps->other->twoMachinesColor[0] == 'w');
6601             }
6602             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6603             if (firstMove && !bookHit) {
6604                 firstMove = FALSE;
6605                 if (cps->other->useColors) {
6606                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6607                 }
6608                 SendToProgram("go\n", cps->other);
6609             }
6610             cps->other->maybeThinking = TRUE;
6611         }
6612
6613         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6614
6615         if (!pausing && appData.ringBellAfterMoves) {
6616             RingBell();
6617         }
6618
6619         /*
6620          * Reenable menu items that were disabled while
6621          * machine was thinking
6622          */
6623         if (gameMode != TwoMachinesPlay)
6624             SetUserThinkingEnables();
6625
6626         // [HGM] book: after book hit opponent has received move and is now in force mode
6627         // force the book reply into it, and then fake that it outputted this move by jumping
6628         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6629         if(bookHit) {
6630                 static char bookMove[MSG_SIZ]; // a bit generous?
6631
6632                 strcpy(bookMove, "move ");
6633                 strcat(bookMove, bookHit);
6634                 message = bookMove;
6635                 cps = cps->other;
6636                 programStats.nodes = programStats.depth = programStats.time =
6637                 programStats.score = programStats.got_only_move = 0;
6638                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6639
6640                 if(cps->lastPing != cps->lastPong) {
6641                     savedMessage = message; // args for deferred call
6642                     savedState = cps;
6643                     ScheduleDelayedEvent(DeferredBookMove, 10);
6644                     return;
6645                 }
6646                 goto FakeBookMove;
6647         }
6648
6649         return;
6650     }
6651
6652     /* Set special modes for chess engines.  Later something general
6653      *  could be added here; for now there is just one kludge feature,
6654      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6655      *  when "xboard" is given as an interactive command.
6656      */
6657     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6658         cps->useSigint = FALSE;
6659         cps->useSigterm = FALSE;
6660     }
6661     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6662       ParseFeatures(message+8, cps);
6663       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6664     }
6665
6666     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6667      * want this, I was asked to put it in, and obliged.
6668      */
6669     if (!strncmp(message, "setboard ", 9)) {
6670         Board initial_position;
6671
6672         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6673
6674         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6675             DisplayError(_("Bad FEN received from engine"), 0);
6676             return ;
6677         } else {
6678            Reset(TRUE, FALSE);
6679            CopyBoard(boards[0], initial_position);
6680            initialRulePlies = FENrulePlies;
6681            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6682            else gameMode = MachinePlaysBlack;
6683            DrawPosition(FALSE, boards[currentMove]);
6684         }
6685         return;
6686     }
6687
6688     /*
6689      * Look for communication commands
6690      */
6691     if (!strncmp(message, "telluser ", 9)) {
6692         DisplayNote(message + 9);
6693         return;
6694     }
6695     if (!strncmp(message, "tellusererror ", 14)) {
6696         cps->userError = 1;
6697         DisplayError(message + 14, 0);
6698         return;
6699     }
6700     if (!strncmp(message, "tellopponent ", 13)) {
6701       if (appData.icsActive) {
6702         if (loggedOn) {
6703           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6704           SendToICS(buf1);
6705         }
6706       } else {
6707         DisplayNote(message + 13);
6708       }
6709       return;
6710     }
6711     if (!strncmp(message, "tellothers ", 11)) {
6712       if (appData.icsActive) {
6713         if (loggedOn) {
6714           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6715           SendToICS(buf1);
6716         }
6717       }
6718       return;
6719     }
6720     if (!strncmp(message, "tellall ", 8)) {
6721       if (appData.icsActive) {
6722         if (loggedOn) {
6723           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6724           SendToICS(buf1);
6725         }
6726       } else {
6727         DisplayNote(message + 8);
6728       }
6729       return;
6730     }
6731     if (strncmp(message, "warning", 7) == 0) {
6732         /* Undocumented feature, use tellusererror in new code */
6733         DisplayError(message, 0);
6734         return;
6735     }
6736     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6737         strcpy(realname, cps->tidy);
6738         strcat(realname, " query");
6739         AskQuestion(realname, buf2, buf1, cps->pr);
6740         return;
6741     }
6742     /* Commands from the engine directly to ICS.  We don't allow these to be
6743      *  sent until we are logged on. Crafty kibitzes have been known to
6744      *  interfere with the login process.
6745      */
6746     if (loggedOn) {
6747         if (!strncmp(message, "tellics ", 8)) {
6748             SendToICS(message + 8);
6749             SendToICS("\n");
6750             return;
6751         }
6752         if (!strncmp(message, "tellicsnoalias ", 15)) {
6753             SendToICS(ics_prefix);
6754             SendToICS(message + 15);
6755             SendToICS("\n");
6756             return;
6757         }
6758         /* The following are for backward compatibility only */
6759         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6760             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6761             SendToICS(ics_prefix);
6762             SendToICS(message);
6763             SendToICS("\n");
6764             return;
6765         }
6766     }
6767     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6768         return;
6769     }
6770     /*
6771      * If the move is illegal, cancel it and redraw the board.
6772      * Also deal with other error cases.  Matching is rather loose
6773      * here to accommodate engines written before the spec.
6774      */
6775     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6776         strncmp(message, "Error", 5) == 0) {
6777         if (StrStr(message, "name") ||
6778             StrStr(message, "rating") || StrStr(message, "?") ||
6779             StrStr(message, "result") || StrStr(message, "board") ||
6780             StrStr(message, "bk") || StrStr(message, "computer") ||
6781             StrStr(message, "variant") || StrStr(message, "hint") ||
6782             StrStr(message, "random") || StrStr(message, "depth") ||
6783             StrStr(message, "accepted")) {
6784             return;
6785         }
6786         if (StrStr(message, "protover")) {
6787           /* Program is responding to input, so it's apparently done
6788              initializing, and this error message indicates it is
6789              protocol version 1.  So we don't need to wait any longer
6790              for it to initialize and send feature commands. */
6791           FeatureDone(cps, 1);
6792           cps->protocolVersion = 1;
6793           return;
6794         }
6795         cps->maybeThinking = FALSE;
6796
6797         if (StrStr(message, "draw")) {
6798             /* Program doesn't have "draw" command */
6799             cps->sendDrawOffers = 0;
6800             return;
6801         }
6802         if (cps->sendTime != 1 &&
6803             (StrStr(message, "time") || StrStr(message, "otim"))) {
6804           /* Program apparently doesn't have "time" or "otim" command */
6805           cps->sendTime = 0;
6806           return;
6807         }
6808         if (StrStr(message, "analyze")) {
6809             cps->analysisSupport = FALSE;
6810             cps->analyzing = FALSE;
6811             Reset(FALSE, TRUE);
6812             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6813             DisplayError(buf2, 0);
6814             return;
6815         }
6816         if (StrStr(message, "(no matching move)st")) {
6817           /* Special kludge for GNU Chess 4 only */
6818           cps->stKludge = TRUE;
6819           SendTimeControl(cps, movesPerSession, timeControl,
6820                           timeIncrement, appData.searchDepth,
6821                           searchTime);
6822           return;
6823         }
6824         if (StrStr(message, "(no matching move)sd")) {
6825           /* Special kludge for GNU Chess 4 only */
6826           cps->sdKludge = TRUE;
6827           SendTimeControl(cps, movesPerSession, timeControl,
6828                           timeIncrement, appData.searchDepth,
6829                           searchTime);
6830           return;
6831         }
6832         if (!StrStr(message, "llegal")) {
6833             return;
6834         }
6835         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6836             gameMode == IcsIdle) return;
6837         if (forwardMostMove <= backwardMostMove) return;
6838         if (pausing) PauseEvent();
6839       if(appData.forceIllegal) {
6840             // [HGM] illegal: machine refused move; force position after move into it
6841           SendToProgram("force\n", cps);
6842           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6843                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6844                 // when black is to move, while there might be nothing on a2 or black
6845                 // might already have the move. So send the board as if white has the move.
6846                 // But first we must change the stm of the engine, as it refused the last move
6847                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6848                 if(WhiteOnMove(forwardMostMove)) {
6849                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6850                     SendBoard(cps, forwardMostMove); // kludgeless board
6851                 } else {
6852                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6853                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6854                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6855                 }
6856           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6857             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6858                  gameMode == TwoMachinesPlay)
6859               SendToProgram("go\n", cps);
6860             return;
6861       } else
6862         if (gameMode == PlayFromGameFile) {
6863             /* Stop reading this game file */
6864             gameMode = EditGame;
6865             ModeHighlight();
6866         }
6867         currentMove = --forwardMostMove;
6868         DisplayMove(currentMove-1); /* before DisplayMoveError */
6869         SwitchClocks();
6870         DisplayBothClocks();
6871         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6872                 parseList[currentMove], cps->which);
6873         DisplayMoveError(buf1);
6874         DrawPosition(FALSE, boards[currentMove]);
6875
6876         /* [HGM] illegal-move claim should forfeit game when Xboard */
6877         /* only passes fully legal moves                            */
6878         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6879             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6880                                 "False illegal-move claim", GE_XBOARD );
6881         }
6882         return;
6883     }
6884     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6885         /* Program has a broken "time" command that
6886            outputs a string not ending in newline.
6887            Don't use it. */
6888         cps->sendTime = 0;
6889     }
6890
6891     /*
6892      * If chess program startup fails, exit with an error message.
6893      * Attempts to recover here are futile.
6894      */
6895     if ((StrStr(message, "unknown host") != NULL)
6896         || (StrStr(message, "No remote directory") != NULL)
6897         || (StrStr(message, "not found") != NULL)
6898         || (StrStr(message, "No such file") != NULL)
6899         || (StrStr(message, "can't alloc") != NULL)
6900         || (StrStr(message, "Permission denied") != NULL)) {
6901
6902         cps->maybeThinking = FALSE;
6903         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6904                 cps->which, cps->program, cps->host, message);
6905         RemoveInputSource(cps->isr);
6906         DisplayFatalError(buf1, 0, 1);
6907         return;
6908     }
6909
6910     /*
6911      * Look for hint output
6912      */
6913     if (sscanf(message, "Hint: %s", buf1) == 1) {
6914         if (cps == &first && hintRequested) {
6915             hintRequested = FALSE;
6916             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6917                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6918                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6919                                     PosFlags(forwardMostMove),
6920                                     fromY, fromX, toY, toX, promoChar, buf1);
6921                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6922                 DisplayInformation(buf2);
6923             } else {
6924                 /* Hint move could not be parsed!? */
6925               snprintf(buf2, sizeof(buf2),
6926                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6927                         buf1, cps->which);
6928                 DisplayError(buf2, 0);
6929             }
6930         } else {
6931             strcpy(lastHint, buf1);
6932         }
6933         return;
6934     }
6935
6936     /*
6937      * Ignore other messages if game is not in progress
6938      */
6939     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6940         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6941
6942     /*
6943      * look for win, lose, draw, or draw offer
6944      */
6945     if (strncmp(message, "1-0", 3) == 0) {
6946         char *p, *q, *r = "";
6947         p = strchr(message, '{');
6948         if (p) {
6949             q = strchr(p, '}');
6950             if (q) {
6951                 *q = NULLCHAR;
6952                 r = p + 1;
6953             }
6954         }
6955         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6956         return;
6957     } else if (strncmp(message, "0-1", 3) == 0) {
6958         char *p, *q, *r = "";
6959         p = strchr(message, '{');
6960         if (p) {
6961             q = strchr(p, '}');
6962             if (q) {
6963                 *q = NULLCHAR;
6964                 r = p + 1;
6965             }
6966         }
6967         /* Kludge for Arasan 4.1 bug */
6968         if (strcmp(r, "Black resigns") == 0) {
6969             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6970             return;
6971         }
6972         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6973         return;
6974     } else if (strncmp(message, "1/2", 3) == 0) {
6975         char *p, *q, *r = "";
6976         p = strchr(message, '{');
6977         if (p) {
6978             q = strchr(p, '}');
6979             if (q) {
6980                 *q = NULLCHAR;
6981                 r = p + 1;
6982             }
6983         }
6984
6985         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6986         return;
6987
6988     } else if (strncmp(message, "White resign", 12) == 0) {
6989         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6990         return;
6991     } else if (strncmp(message, "Black resign", 12) == 0) {
6992         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6993         return;
6994     } else if (strncmp(message, "White matches", 13) == 0 ||
6995                strncmp(message, "Black matches", 13) == 0   ) {
6996         /* [HGM] ignore GNUShogi noises */
6997         return;
6998     } else if (strncmp(message, "White", 5) == 0 &&
6999                message[5] != '(' &&
7000                StrStr(message, "Black") == NULL) {
7001         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7002         return;
7003     } else if (strncmp(message, "Black", 5) == 0 &&
7004                message[5] != '(') {
7005         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7006         return;
7007     } else if (strcmp(message, "resign") == 0 ||
7008                strcmp(message, "computer resigns") == 0) {
7009         switch (gameMode) {
7010           case MachinePlaysBlack:
7011           case IcsPlayingBlack:
7012             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7013             break;
7014           case MachinePlaysWhite:
7015           case IcsPlayingWhite:
7016             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7017             break;
7018           case TwoMachinesPlay:
7019             if (cps->twoMachinesColor[0] == 'w')
7020               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7021             else
7022               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7023             break;
7024           default:
7025             /* can't happen */
7026             break;
7027         }
7028         return;
7029     } else if (strncmp(message, "opponent mates", 14) == 0) {
7030         switch (gameMode) {
7031           case MachinePlaysBlack:
7032           case IcsPlayingBlack:
7033             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7034             break;
7035           case MachinePlaysWhite:
7036           case IcsPlayingWhite:
7037             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7038             break;
7039           case TwoMachinesPlay:
7040             if (cps->twoMachinesColor[0] == 'w')
7041               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7042             else
7043               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7044             break;
7045           default:
7046             /* can't happen */
7047             break;
7048         }
7049         return;
7050     } else if (strncmp(message, "computer mates", 14) == 0) {
7051         switch (gameMode) {
7052           case MachinePlaysBlack:
7053           case IcsPlayingBlack:
7054             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7055             break;
7056           case MachinePlaysWhite:
7057           case IcsPlayingWhite:
7058             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7059             break;
7060           case TwoMachinesPlay:
7061             if (cps->twoMachinesColor[0] == 'w')
7062               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7063             else
7064               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7065             break;
7066           default:
7067             /* can't happen */
7068             break;
7069         }
7070         return;
7071     } else if (strncmp(message, "checkmate", 9) == 0) {
7072         if (WhiteOnMove(forwardMostMove)) {
7073             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7074         } else {
7075             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7076         }
7077         return;
7078     } else if (strstr(message, "Draw") != NULL ||
7079                strstr(message, "game is a draw") != NULL) {
7080         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7081         return;
7082     } else if (strstr(message, "offer") != NULL &&
7083                strstr(message, "draw") != NULL) {
7084 #if ZIPPY
7085         if (appData.zippyPlay && first.initDone) {
7086             /* Relay offer to ICS */
7087             SendToICS(ics_prefix);
7088             SendToICS("draw\n");
7089         }
7090 #endif
7091         cps->offeredDraw = 2; /* valid until this engine moves twice */
7092         if (gameMode == TwoMachinesPlay) {
7093             if (cps->other->offeredDraw) {
7094                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7095             /* [HGM] in two-machine mode we delay relaying draw offer      */
7096             /* until after we also have move, to see if it is really claim */
7097             }
7098         } else if (gameMode == MachinePlaysWhite ||
7099                    gameMode == MachinePlaysBlack) {
7100           if (userOfferedDraw) {
7101             DisplayInformation(_("Machine accepts your draw offer"));
7102             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7103           } else {
7104             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7105           }
7106         }
7107     }
7108
7109
7110     /*
7111      * Look for thinking output
7112      */
7113     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7114           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7115                                 ) {
7116         int plylev, mvleft, mvtot, curscore, time;
7117         char mvname[MOVE_LEN];
7118         u64 nodes; // [DM]
7119         char plyext;
7120         int ignore = FALSE;
7121         int prefixHint = FALSE;
7122         mvname[0] = NULLCHAR;
7123
7124         switch (gameMode) {
7125           case MachinePlaysBlack:
7126           case IcsPlayingBlack:
7127             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7128             break;
7129           case MachinePlaysWhite:
7130           case IcsPlayingWhite:
7131             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7132             break;
7133           case AnalyzeMode:
7134           case AnalyzeFile:
7135             break;
7136           case IcsObserving: /* [DM] icsEngineAnalyze */
7137             if (!appData.icsEngineAnalyze) ignore = TRUE;
7138             break;
7139           case TwoMachinesPlay:
7140             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7141                 ignore = TRUE;
7142             }
7143             break;
7144           default:
7145             ignore = TRUE;
7146             break;
7147         }
7148
7149         if (!ignore) {
7150             buf1[0] = NULLCHAR;
7151             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7152                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7153
7154                 if (plyext != ' ' && plyext != '\t') {
7155                     time *= 100;
7156                 }
7157
7158                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7159                 if( cps->scoreIsAbsolute && 
7160                     ( gameMode == MachinePlaysBlack ||
7161                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7162                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7163                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7164                      !WhiteOnMove(currentMove)
7165                     ) )
7166                 {
7167                     curscore = -curscore;
7168                 }
7169
7170
7171                 programStats.depth = plylev;
7172                 programStats.nodes = nodes;
7173                 programStats.time = time;
7174                 programStats.score = curscore;
7175                 programStats.got_only_move = 0;
7176
7177                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7178                         int ticklen;
7179
7180                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7181                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7182                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7183                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7184                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7185                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7186                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7187                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7188                 }
7189
7190                 /* Buffer overflow protection */
7191                 if (buf1[0] != NULLCHAR) {
7192                     if (strlen(buf1) >= sizeof(programStats.movelist)
7193                         && appData.debugMode) {
7194                         fprintf(debugFP,
7195                                 "PV is too long; using the first %u bytes.\n",
7196                                 (unsigned) sizeof(programStats.movelist) - 1);
7197                     }
7198
7199                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7200                 } else {
7201                     sprintf(programStats.movelist, " no PV\n");
7202                 }
7203
7204                 if (programStats.seen_stat) {
7205                     programStats.ok_to_send = 1;
7206                 }
7207
7208                 if (strchr(programStats.movelist, '(') != NULL) {
7209                     programStats.line_is_book = 1;
7210                     programStats.nr_moves = 0;
7211                     programStats.moves_left = 0;
7212                 } else {
7213                     programStats.line_is_book = 0;
7214                 }
7215
7216                 SendProgramStatsToFrontend( cps, &programStats );
7217
7218                 /*
7219                     [AS] Protect the thinkOutput buffer from overflow... this
7220                     is only useful if buf1 hasn't overflowed first!
7221                 */
7222                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7223                         plylev,
7224                         (gameMode == TwoMachinesPlay ?
7225                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7226                         ((double) curscore) / 100.0,
7227                         prefixHint ? lastHint : "",
7228                         prefixHint ? " " : "" );
7229
7230                 if( buf1[0] != NULLCHAR ) {
7231                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7232
7233                     if( strlen(buf1) > max_len ) {
7234                         if( appData.debugMode) {
7235                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7236                         }
7237                         buf1[max_len+1] = '\0';
7238                     }
7239
7240                     strcat( thinkOutput, buf1 );
7241                 }
7242
7243                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7244                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7245                     DisplayMove(currentMove - 1);
7246                 }
7247                 return;
7248
7249             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7250                 /* crafty (9.25+) says "(only move) <move>"
7251                  * if there is only 1 legal move
7252                  */
7253                 sscanf(p, "(only move) %s", buf1);
7254                 sprintf(thinkOutput, "%s (only move)", buf1);
7255                 sprintf(programStats.movelist, "%s (only move)", buf1);
7256                 programStats.depth = 1;
7257                 programStats.nr_moves = 1;
7258                 programStats.moves_left = 1;
7259                 programStats.nodes = 1;
7260                 programStats.time = 1;
7261                 programStats.got_only_move = 1;
7262
7263                 /* Not really, but we also use this member to
7264                    mean "line isn't going to change" (Crafty
7265                    isn't searching, so stats won't change) */
7266                 programStats.line_is_book = 1;
7267
7268                 SendProgramStatsToFrontend( cps, &programStats );
7269
7270                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7271                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7272                     DisplayMove(currentMove - 1);
7273                 }
7274                 return;
7275             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7276                               &time, &nodes, &plylev, &mvleft,
7277                               &mvtot, mvname) >= 5) {
7278                 /* The stat01: line is from Crafty (9.29+) in response
7279                    to the "." command */
7280                 programStats.seen_stat = 1;
7281                 cps->maybeThinking = TRUE;
7282
7283                 if (programStats.got_only_move || !appData.periodicUpdates)
7284                   return;
7285
7286                 programStats.depth = plylev;
7287                 programStats.time = time;
7288                 programStats.nodes = nodes;
7289                 programStats.moves_left = mvleft;
7290                 programStats.nr_moves = mvtot;
7291                 strcpy(programStats.move_name, mvname);
7292                 programStats.ok_to_send = 1;
7293                 programStats.movelist[0] = '\0';
7294
7295                 SendProgramStatsToFrontend( cps, &programStats );
7296
7297                 return;
7298
7299             } else if (strncmp(message,"++",2) == 0) {
7300                 /* Crafty 9.29+ outputs this */
7301                 programStats.got_fail = 2;
7302                 return;
7303
7304             } else if (strncmp(message,"--",2) == 0) {
7305                 /* Crafty 9.29+ outputs this */
7306                 programStats.got_fail = 1;
7307                 return;
7308
7309             } else if (thinkOutput[0] != NULLCHAR &&
7310                        strncmp(message, "    ", 4) == 0) {
7311                 unsigned message_len;
7312
7313                 p = message;
7314                 while (*p && *p == ' ') p++;
7315
7316                 message_len = strlen( p );
7317
7318                 /* [AS] Avoid buffer overflow */
7319                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7320                     strcat(thinkOutput, " ");
7321                     strcat(thinkOutput, p);
7322                 }
7323
7324                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7325                     strcat(programStats.movelist, " ");
7326                     strcat(programStats.movelist, p);
7327                 }
7328
7329                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7330                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7331                     DisplayMove(currentMove - 1);
7332                 }
7333                 return;
7334             }
7335         }
7336         else {
7337             buf1[0] = NULLCHAR;
7338
7339             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7340                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7341             {
7342                 ChessProgramStats cpstats;
7343
7344                 if (plyext != ' ' && plyext != '\t') {
7345                     time *= 100;
7346                 }
7347
7348                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7349                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7350                     curscore = -curscore;
7351                 }
7352
7353                 cpstats.depth = plylev;
7354                 cpstats.nodes = nodes;
7355                 cpstats.time = time;
7356                 cpstats.score = curscore;
7357                 cpstats.got_only_move = 0;
7358                 cpstats.movelist[0] = '\0';
7359
7360                 if (buf1[0] != NULLCHAR) {
7361                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7362                 }
7363
7364                 cpstats.ok_to_send = 0;
7365                 cpstats.line_is_book = 0;
7366                 cpstats.nr_moves = 0;
7367                 cpstats.moves_left = 0;
7368
7369                 SendProgramStatsToFrontend( cps, &cpstats );
7370             }
7371         }
7372     }
7373 }
7374
7375
7376 /* Parse a game score from the character string "game", and
7377    record it as the history of the current game.  The game
7378    score is NOT assumed to start from the standard position.
7379    The display is not updated in any way.
7380    */
7381 void
7382 ParseGameHistory(game)
7383      char *game;
7384 {
7385     ChessMove moveType;
7386     int fromX, fromY, toX, toY, boardIndex;
7387     char promoChar;
7388     char *p, *q;
7389     char buf[MSG_SIZ];
7390
7391     if (appData.debugMode)
7392       fprintf(debugFP, "Parsing game history: %s\n", game);
7393
7394     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7395     gameInfo.site = StrSave(appData.icsHost);
7396     gameInfo.date = PGNDate();
7397     gameInfo.round = StrSave("-");
7398
7399     /* Parse out names of players */
7400     while (*game == ' ') game++;
7401     p = buf;
7402     while (*game != ' ') *p++ = *game++;
7403     *p = NULLCHAR;
7404     gameInfo.white = StrSave(buf);
7405     while (*game == ' ') game++;
7406     p = buf;
7407     while (*game != ' ' && *game != '\n') *p++ = *game++;
7408     *p = NULLCHAR;
7409     gameInfo.black = StrSave(buf);
7410
7411     /* Parse moves */
7412     boardIndex = blackPlaysFirst ? 1 : 0;
7413     yynewstr(game);
7414     for (;;) {
7415         yyboardindex = boardIndex;
7416         moveType = (ChessMove) yylex();
7417         switch (moveType) {
7418           case IllegalMove:             /* maybe suicide chess, etc. */
7419   if (appData.debugMode) {
7420     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7421     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7422     setbuf(debugFP, NULL);
7423   }
7424           case WhitePromotionChancellor:
7425           case BlackPromotionChancellor:
7426           case WhitePromotionArchbishop:
7427           case BlackPromotionArchbishop:
7428           case WhitePromotionQueen:
7429           case BlackPromotionQueen:
7430           case WhitePromotionRook:
7431           case BlackPromotionRook:
7432           case WhitePromotionBishop:
7433           case BlackPromotionBishop:
7434           case WhitePromotionKnight:
7435           case BlackPromotionKnight:
7436           case WhitePromotionKing:
7437           case BlackPromotionKing:
7438           case NormalMove:
7439           case WhiteCapturesEnPassant:
7440           case BlackCapturesEnPassant:
7441           case WhiteKingSideCastle:
7442           case WhiteQueenSideCastle:
7443           case BlackKingSideCastle:
7444           case BlackQueenSideCastle:
7445           case WhiteKingSideCastleWild:
7446           case WhiteQueenSideCastleWild:
7447           case BlackKingSideCastleWild:
7448           case BlackQueenSideCastleWild:
7449           /* PUSH Fabien */
7450           case WhiteHSideCastleFR:
7451           case WhiteASideCastleFR:
7452           case BlackHSideCastleFR:
7453           case BlackASideCastleFR:
7454           /* POP Fabien */
7455             fromX = currentMoveString[0] - AAA;
7456             fromY = currentMoveString[1] - ONE;
7457             toX = currentMoveString[2] - AAA;
7458             toY = currentMoveString[3] - ONE;
7459             promoChar = currentMoveString[4];
7460             break;
7461           case WhiteDrop:
7462           case BlackDrop:
7463             fromX = moveType == WhiteDrop ?
7464               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7465             (int) CharToPiece(ToLower(currentMoveString[0]));
7466             fromY = DROP_RANK;
7467             toX = currentMoveString[2] - AAA;
7468             toY = currentMoveString[3] - ONE;
7469             promoChar = NULLCHAR;
7470             break;
7471           case AmbiguousMove:
7472             /* bug? */
7473             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7474   if (appData.debugMode) {
7475     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7476     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7477     setbuf(debugFP, NULL);
7478   }
7479             DisplayError(buf, 0);
7480             return;
7481           case ImpossibleMove:
7482             /* bug? */
7483             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7484   if (appData.debugMode) {
7485     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7486     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7487     setbuf(debugFP, NULL);
7488   }
7489             DisplayError(buf, 0);
7490             return;
7491           case (ChessMove) 0:   /* end of file */
7492             if (boardIndex < backwardMostMove) {
7493                 /* Oops, gap.  How did that happen? */
7494                 DisplayError(_("Gap in move list"), 0);
7495                 return;
7496             }
7497             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7498             if (boardIndex > forwardMostMove) {
7499                 forwardMostMove = boardIndex;
7500             }
7501             return;
7502           case ElapsedTime:
7503             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7504                 strcat(parseList[boardIndex-1], " ");
7505                 strcat(parseList[boardIndex-1], yy_text);
7506             }
7507             continue;
7508           case Comment:
7509           case PGNTag:
7510           case NAG:
7511           default:
7512             /* ignore */
7513             continue;
7514           case WhiteWins:
7515           case BlackWins:
7516           case GameIsDrawn:
7517           case GameUnfinished:
7518             if (gameMode == IcsExamining) {
7519                 if (boardIndex < backwardMostMove) {
7520                     /* Oops, gap.  How did that happen? */
7521                     return;
7522                 }
7523                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7524                 return;
7525             }
7526             gameInfo.result = moveType;
7527             p = strchr(yy_text, '{');
7528             if (p == NULL) p = strchr(yy_text, '(');
7529             if (p == NULL) {
7530                 p = yy_text;
7531                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7532             } else {
7533                 q = strchr(p, *p == '{' ? '}' : ')');
7534                 if (q != NULL) *q = NULLCHAR;
7535                 p++;
7536             }
7537             gameInfo.resultDetails = StrSave(p);
7538             continue;
7539         }
7540         if (boardIndex >= forwardMostMove &&
7541             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7542             backwardMostMove = blackPlaysFirst ? 1 : 0;
7543             return;
7544         }
7545         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7546                                  fromY, fromX, toY, toX, promoChar,
7547                                  parseList[boardIndex]);
7548         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7549         /* currentMoveString is set as a side-effect of yylex */
7550         strcpy(moveList[boardIndex], currentMoveString);
7551         strcat(moveList[boardIndex], "\n");
7552         boardIndex++;
7553         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7554         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7555           case MT_NONE:
7556           case MT_STALEMATE:
7557           default:
7558             break;
7559           case MT_CHECK:
7560             if(gameInfo.variant != VariantShogi)
7561                 strcat(parseList[boardIndex - 1], "+");
7562             break;
7563           case MT_CHECKMATE:
7564           case MT_STAINMATE:
7565             strcat(parseList[boardIndex - 1], "#");
7566             break;
7567         }
7568     }
7569 }
7570
7571
7572 /* Apply a move to the given board  */
7573 void
7574 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7575      int fromX, fromY, toX, toY;
7576      int promoChar;
7577      Board board;
7578 {
7579   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7580
7581     /* [HGM] compute & store e.p. status and castling rights for new position */
7582     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7583     { int i;
7584
7585       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7586       oldEP = (signed char)board[EP_STATUS];
7587       board[EP_STATUS] = EP_NONE;
7588
7589       if( board[toY][toX] != EmptySquare ) 
7590            board[EP_STATUS] = EP_CAPTURE;  
7591
7592       if( board[fromY][fromX] == WhitePawn ) {
7593            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7594                board[EP_STATUS] = EP_PAWN_MOVE;
7595            if( toY-fromY==2) {
7596                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7597                         gameInfo.variant != VariantBerolina || toX < fromX)
7598                       board[EP_STATUS] = toX | berolina;
7599                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7600                   gameInfo.variant != VariantBerolina || toX > fromX) 
7601                  board[EP_STATUS] = toX;
7602            }
7603       } else
7604       if( board[fromY][fromX] == BlackPawn ) {
7605            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7606                board[EP_STATUS] = EP_PAWN_MOVE; 
7607            if( toY-fromY== -2) {
7608                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7609                         gameInfo.variant != VariantBerolina || toX < fromX)
7610                       board[EP_STATUS] = toX | berolina;
7611                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7612                         gameInfo.variant != VariantBerolina || toX > fromX) 
7613                       board[EP_STATUS] = toX;
7614            }
7615        }
7616
7617        for(i=0; i<nrCastlingRights; i++) {
7618            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7619               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7620              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7621        }
7622
7623     }
7624
7625   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7626   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7627        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7628
7629   if (fromX == toX && fromY == toY) return;
7630
7631   if (fromY == DROP_RANK) {
7632         /* must be first */
7633         piece = board[toY][toX] = (ChessSquare) fromX;
7634   } else {
7635      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7636      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7637      if(gameInfo.variant == VariantKnightmate)
7638          king += (int) WhiteUnicorn - (int) WhiteKing;
7639
7640     /* Code added by Tord: */
7641     /* FRC castling assumed when king captures friendly rook. */
7642     if (board[fromY][fromX] == WhiteKing &&
7643              board[toY][toX] == WhiteRook) {
7644       board[fromY][fromX] = EmptySquare;
7645       board[toY][toX] = EmptySquare;
7646       if(toX > fromX) {
7647         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7648       } else {
7649         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7650       }
7651     } else if (board[fromY][fromX] == BlackKing &&
7652                board[toY][toX] == BlackRook) {
7653       board[fromY][fromX] = EmptySquare;
7654       board[toY][toX] = EmptySquare;
7655       if(toX > fromX) {
7656         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7657       } else {
7658         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7659       }
7660     /* End of code added by Tord */
7661
7662     } else if (board[fromY][fromX] == king
7663         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7664         && toY == fromY && toX > fromX+1) {
7665         board[fromY][fromX] = EmptySquare;
7666         board[toY][toX] = king;
7667         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7668         board[fromY][BOARD_RGHT-1] = EmptySquare;
7669     } else if (board[fromY][fromX] == king
7670         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7671                && toY == fromY && toX < fromX-1) {
7672         board[fromY][fromX] = EmptySquare;
7673         board[toY][toX] = king;
7674         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7675         board[fromY][BOARD_LEFT] = EmptySquare;
7676     } else if (board[fromY][fromX] == WhitePawn
7677                && toY == BOARD_HEIGHT-1
7678                && gameInfo.variant != VariantXiangqi
7679                ) {
7680         /* white pawn promotion */
7681         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7682         if (board[toY][toX] == EmptySquare) {
7683             board[toY][toX] = WhiteQueen;
7684         }
7685         if(gameInfo.variant==VariantBughouse ||
7686            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7687             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7688         board[fromY][fromX] = EmptySquare;
7689     } else if ((fromY == BOARD_HEIGHT-4)
7690                && (toX != fromX)
7691                && gameInfo.variant != VariantXiangqi
7692                && gameInfo.variant != VariantBerolina
7693                && (board[fromY][fromX] == WhitePawn)
7694                && (board[toY][toX] == EmptySquare)) {
7695         board[fromY][fromX] = EmptySquare;
7696         board[toY][toX] = WhitePawn;
7697         captured = board[toY - 1][toX];
7698         board[toY - 1][toX] = EmptySquare;
7699     } else if ((fromY == BOARD_HEIGHT-4)
7700                && (toX == fromX)
7701                && gameInfo.variant == VariantBerolina
7702                && (board[fromY][fromX] == WhitePawn)
7703                && (board[toY][toX] == EmptySquare)) {
7704         board[fromY][fromX] = EmptySquare;
7705         board[toY][toX] = WhitePawn;
7706         if(oldEP & EP_BEROLIN_A) {
7707                 captured = board[fromY][fromX-1];
7708                 board[fromY][fromX-1] = EmptySquare;
7709         }else{  captured = board[fromY][fromX+1];
7710                 board[fromY][fromX+1] = EmptySquare;
7711         }
7712     } else if (board[fromY][fromX] == king
7713         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7714                && toY == fromY && toX > fromX+1) {
7715         board[fromY][fromX] = EmptySquare;
7716         board[toY][toX] = king;
7717         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7718         board[fromY][BOARD_RGHT-1] = EmptySquare;
7719     } else if (board[fromY][fromX] == king
7720         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7721                && toY == fromY && toX < fromX-1) {
7722         board[fromY][fromX] = EmptySquare;
7723         board[toY][toX] = king;
7724         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7725         board[fromY][BOARD_LEFT] = EmptySquare;
7726     } else if (fromY == 7 && fromX == 3
7727                && board[fromY][fromX] == BlackKing
7728                && toY == 7 && toX == 5) {
7729         board[fromY][fromX] = EmptySquare;
7730         board[toY][toX] = BlackKing;
7731         board[fromY][7] = EmptySquare;
7732         board[toY][4] = BlackRook;
7733     } else if (fromY == 7 && fromX == 3
7734                && board[fromY][fromX] == BlackKing
7735                && toY == 7 && toX == 1) {
7736         board[fromY][fromX] = EmptySquare;
7737         board[toY][toX] = BlackKing;
7738         board[fromY][0] = EmptySquare;
7739         board[toY][2] = BlackRook;
7740     } else if (board[fromY][fromX] == BlackPawn
7741                && toY == 0
7742                && gameInfo.variant != VariantXiangqi
7743                ) {
7744         /* black pawn promotion */
7745         board[0][toX] = CharToPiece(ToLower(promoChar));
7746         if (board[0][toX] == EmptySquare) {
7747             board[0][toX] = BlackQueen;
7748         }
7749         if(gameInfo.variant==VariantBughouse ||
7750            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7751             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7752         board[fromY][fromX] = EmptySquare;
7753     } else if ((fromY == 3)
7754                && (toX != fromX)
7755                && gameInfo.variant != VariantXiangqi
7756                && gameInfo.variant != VariantBerolina
7757                && (board[fromY][fromX] == BlackPawn)
7758                && (board[toY][toX] == EmptySquare)) {
7759         board[fromY][fromX] = EmptySquare;
7760         board[toY][toX] = BlackPawn;
7761         captured = board[toY + 1][toX];
7762         board[toY + 1][toX] = EmptySquare;
7763     } else if ((fromY == 3)
7764                && (toX == fromX)
7765                && gameInfo.variant == VariantBerolina
7766                && (board[fromY][fromX] == BlackPawn)
7767                && (board[toY][toX] == EmptySquare)) {
7768         board[fromY][fromX] = EmptySquare;
7769         board[toY][toX] = BlackPawn;
7770         if(oldEP & EP_BEROLIN_A) {
7771                 captured = board[fromY][fromX-1];
7772                 board[fromY][fromX-1] = EmptySquare;
7773         }else{  captured = board[fromY][fromX+1];
7774                 board[fromY][fromX+1] = EmptySquare;
7775         }
7776     } else {
7777         board[toY][toX] = board[fromY][fromX];
7778         board[fromY][fromX] = EmptySquare;
7779     }
7780
7781     /* [HGM] now we promote for Shogi, if needed */
7782     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7783         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7784   }
7785
7786     if (gameInfo.holdingsWidth != 0) {
7787
7788       /* !!A lot more code needs to be written to support holdings  */
7789       /* [HGM] OK, so I have written it. Holdings are stored in the */
7790       /* penultimate board files, so they are automaticlly stored   */
7791       /* in the game history.                                       */
7792       if (fromY == DROP_RANK) {
7793         /* Delete from holdings, by decreasing count */
7794         /* and erasing image if necessary            */
7795         p = (int) fromX;
7796         if(p < (int) BlackPawn) { /* white drop */
7797              p -= (int)WhitePawn;
7798                  p = PieceToNumber((ChessSquare)p);
7799              if(p >= gameInfo.holdingsSize) p = 0;
7800              if(--board[p][BOARD_WIDTH-2] <= 0)
7801                   board[p][BOARD_WIDTH-1] = EmptySquare;
7802              if((int)board[p][BOARD_WIDTH-2] < 0)
7803                         board[p][BOARD_WIDTH-2] = 0;
7804         } else {                  /* black drop */
7805              p -= (int)BlackPawn;
7806                  p = PieceToNumber((ChessSquare)p);
7807              if(p >= gameInfo.holdingsSize) p = 0;
7808              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7809                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7810              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7811                         board[BOARD_HEIGHT-1-p][1] = 0;
7812         }
7813       }
7814       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7815           && gameInfo.variant != VariantBughouse        ) {
7816         /* [HGM] holdings: Add to holdings, if holdings exist */
7817         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7818                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7819                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7820         }
7821         p = (int) captured;
7822         if (p >= (int) BlackPawn) {
7823           p -= (int)BlackPawn;
7824           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7825                   /* in Shogi restore piece to its original  first */
7826                   captured = (ChessSquare) (DEMOTED captured);
7827                   p = DEMOTED p;
7828           }
7829           p = PieceToNumber((ChessSquare)p);
7830           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7831           board[p][BOARD_WIDTH-2]++;
7832           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7833         } else {
7834           p -= (int)WhitePawn;
7835           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7836                   captured = (ChessSquare) (DEMOTED captured);
7837                   p = DEMOTED p;
7838           }
7839           p = PieceToNumber((ChessSquare)p);
7840           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7841           board[BOARD_HEIGHT-1-p][1]++;
7842           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7843         }
7844       }
7845     } else if (gameInfo.variant == VariantAtomic) {
7846       if (captured != EmptySquare) {
7847         int y, x;
7848         for (y = toY-1; y <= toY+1; y++) {
7849           for (x = toX-1; x <= toX+1; x++) {
7850             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7851                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7852               board[y][x] = EmptySquare;
7853             }
7854           }
7855         }
7856         board[toY][toX] = EmptySquare;
7857       }
7858     }
7859     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7860         /* [HGM] Shogi promotions */
7861         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7862     }
7863
7864     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7865                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7866         // [HGM] superchess: take promotion piece out of holdings
7867         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7868         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7869             if(!--board[k][BOARD_WIDTH-2])
7870                 board[k][BOARD_WIDTH-1] = EmptySquare;
7871         } else {
7872             if(!--board[BOARD_HEIGHT-1-k][1])
7873                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7874         }
7875     }
7876
7877 }
7878
7879 /* Updates forwardMostMove */
7880 void
7881 MakeMove(fromX, fromY, toX, toY, promoChar)
7882      int fromX, fromY, toX, toY;
7883      int promoChar;
7884 {
7885 //    forwardMostMove++; // [HGM] bare: moved downstream
7886
7887     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7888         int timeLeft; static int lastLoadFlag=0; int king, piece;
7889         piece = boards[forwardMostMove][fromY][fromX];
7890         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7891         if(gameInfo.variant == VariantKnightmate)
7892             king += (int) WhiteUnicorn - (int) WhiteKing;
7893         if(forwardMostMove == 0) {
7894             if(blackPlaysFirst)
7895                 fprintf(serverMoves, "%s;", second.tidy);
7896             fprintf(serverMoves, "%s;", first.tidy);
7897             if(!blackPlaysFirst)
7898                 fprintf(serverMoves, "%s;", second.tidy);
7899         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7900         lastLoadFlag = loadFlag;
7901         // print base move
7902         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7903         // print castling suffix
7904         if( toY == fromY && piece == king ) {
7905             if(toX-fromX > 1)
7906                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7907             if(fromX-toX >1)
7908                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7909         }
7910         // e.p. suffix
7911         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7912              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7913              boards[forwardMostMove][toY][toX] == EmptySquare
7914              && fromX != toX )
7915                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7916         // promotion suffix
7917         if(promoChar != NULLCHAR)
7918                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7919         if(!loadFlag) {
7920             fprintf(serverMoves, "/%d/%d",
7921                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7922             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7923             else                      timeLeft = blackTimeRemaining/1000;
7924             fprintf(serverMoves, "/%d", timeLeft);
7925         }
7926         fflush(serverMoves);
7927     }
7928
7929     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7930       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7931                         0, 1);
7932       return;
7933     }
7934     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7935     if (commentList[forwardMostMove+1] != NULL) {
7936         free(commentList[forwardMostMove+1]);
7937         commentList[forwardMostMove+1] = NULL;
7938     }
7939     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7940     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7941     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7942     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7943     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7944     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7945     gameInfo.result = GameUnfinished;
7946     if (gameInfo.resultDetails != NULL) {
7947         free(gameInfo.resultDetails);
7948         gameInfo.resultDetails = NULL;
7949     }
7950     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7951                               moveList[forwardMostMove - 1]);
7952     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7953                              PosFlags(forwardMostMove - 1),
7954                              fromY, fromX, toY, toX, promoChar,
7955                              parseList[forwardMostMove - 1]);
7956     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7957       case MT_NONE:
7958       case MT_STALEMATE:
7959       default:
7960         break;
7961       case MT_CHECK:
7962         if(gameInfo.variant != VariantShogi)
7963             strcat(parseList[forwardMostMove - 1], "+");
7964         break;
7965       case MT_CHECKMATE:
7966       case MT_STAINMATE:
7967         strcat(parseList[forwardMostMove - 1], "#");
7968         break;
7969     }
7970     if (appData.debugMode) {
7971         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7972     }
7973
7974 }
7975
7976 /* Updates currentMove if not pausing */
7977 void
7978 ShowMove(fromX, fromY, toX, toY)
7979 {
7980     int instant = (gameMode == PlayFromGameFile) ?
7981         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7982
7983     if(appData.noGUI) return;
7984
7985     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7986       {
7987         if (!instant)
7988           {
7989             if (forwardMostMove == currentMove + 1)
7990               {
7991 //TODO
7992 //              AnimateMove(boards[forwardMostMove - 1],
7993 //                          fromX, fromY, toX, toY);
7994               }
7995             if (appData.highlightLastMove)
7996               {
7997                 SetHighlights(fromX, fromY, toX, toY);
7998               }
7999           }
8000         currentMove = forwardMostMove;
8001     }
8002
8003     if (instant) return;
8004
8005     DisplayMove(currentMove - 1);
8006     DrawPosition(FALSE, boards[currentMove]);
8007     DisplayBothClocks();
8008     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8009
8010     return;
8011 }
8012
8013 void SendEgtPath(ChessProgramState *cps)
8014 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8015         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8016
8017         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8018
8019         while(*p) {
8020             char c, *q = name+1, *r, *s;
8021
8022             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8023             while(*p && *p != ',') *q++ = *p++;
8024             *q++ = ':'; *q = 0;
8025             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8026                 strcmp(name, ",nalimov:") == 0 ) {
8027                 // take nalimov path from the menu-changeable option first, if it is defined
8028                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8029                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8030             } else
8031             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8032                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8033                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8034                 s = r = StrStr(s, ":") + 1; // beginning of path info
8035                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8036                 c = *r; *r = 0;             // temporarily null-terminate path info
8037                     *--q = 0;               // strip of trailig ':' from name
8038                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8039                 *r = c;
8040                 SendToProgram(buf,cps);     // send egtbpath command for this format
8041             }
8042             if(*p == ',') p++; // read away comma to position for next format name
8043         }
8044 }
8045
8046 void
8047 InitChessProgram(cps, setup)
8048      ChessProgramState *cps;
8049      int setup; /* [HGM] needed to setup FRC opening position */
8050 {
8051     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8052     if (appData.noChessProgram) return;
8053     hintRequested = FALSE;
8054     bookRequested = FALSE;
8055
8056     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8057     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8058     if(cps->memSize) { /* [HGM] memory */
8059         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8060         SendToProgram(buf, cps);
8061     }
8062     SendEgtPath(cps); /* [HGM] EGT */
8063     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8064         sprintf(buf, "cores %d\n", appData.smpCores);
8065         SendToProgram(buf, cps);
8066     }
8067
8068     SendToProgram(cps->initString, cps);
8069     if (gameInfo.variant != VariantNormal &&
8070         gameInfo.variant != VariantLoadable
8071         /* [HGM] also send variant if board size non-standard */
8072         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8073                                             ) {
8074       char *v = VariantName(gameInfo.variant);
8075       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8076         /* [HGM] in protocol 1 we have to assume all variants valid */
8077         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8078         DisplayFatalError(buf, 0, 1);
8079         return;
8080       }
8081
8082       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8083       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8084       if( gameInfo.variant == VariantXiangqi )
8085            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8086       if( gameInfo.variant == VariantShogi )
8087            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8088       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8089            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8090       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8091                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8092            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8093       if( gameInfo.variant == VariantCourier )
8094            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8095       if( gameInfo.variant == VariantSuper )
8096            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8097       if( gameInfo.variant == VariantGreat )
8098            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8099
8100       if(overruled) {
8101            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8102                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8103            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8104            if(StrStr(cps->variants, b) == NULL) {
8105                // specific sized variant not known, check if general sizing allowed
8106                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8107                    if(StrStr(cps->variants, "boardsize") == NULL) {
8108                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8109                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8110                        DisplayFatalError(buf, 0, 1);
8111                        return;
8112                    }
8113                    /* [HGM] here we really should compare with the maximum supported board size */
8114                }
8115            }
8116       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8117       sprintf(buf, "variant %s\n", b);
8118       SendToProgram(buf, cps);
8119     }
8120     currentlyInitializedVariant = gameInfo.variant;
8121
8122     /* [HGM] send opening position in FRC to first engine */
8123     if(setup) {
8124           SendToProgram("force\n", cps);
8125           SendBoard(cps, 0);
8126           /* engine is now in force mode! Set flag to wake it up after first move. */
8127           setboardSpoiledMachineBlack = 1;
8128     }
8129
8130     if (cps->sendICS) {
8131       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8132       SendToProgram(buf, cps);
8133     }
8134     cps->maybeThinking = FALSE;
8135     cps->offeredDraw = 0;
8136     if (!appData.icsActive) {
8137         SendTimeControl(cps, movesPerSession, timeControl,
8138                         timeIncrement, appData.searchDepth,
8139                         searchTime);
8140     }
8141     if (appData.showThinking
8142         // [HGM] thinking: four options require thinking output to be sent
8143         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8144                                 ) {
8145         SendToProgram("post\n", cps);
8146     }
8147     SendToProgram("hard\n", cps);
8148     if (!appData.ponderNextMove) {
8149         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8150            it without being sure what state we are in first.  "hard"
8151            is not a toggle, so that one is OK.
8152          */
8153         SendToProgram("easy\n", cps);
8154     }
8155     if (cps->usePing) {
8156       sprintf(buf, "ping %d\n", ++cps->lastPing);
8157       SendToProgram(buf, cps);
8158     }
8159     cps->initDone = TRUE;
8160 }
8161
8162
8163 void
8164 StartChessProgram(cps)
8165      ChessProgramState *cps;
8166 {
8167     char buf[MSG_SIZ];
8168     int err;
8169
8170     if (appData.noChessProgram) return;
8171     cps->initDone = FALSE;
8172
8173     if (strcmp(cps->host, "localhost") == 0) {
8174         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8175     } else if (*appData.remoteShell == NULLCHAR) {
8176         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8177     } else {
8178         if (*appData.remoteUser == NULLCHAR) {
8179           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8180                     cps->program);
8181         } else {
8182           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8183                     cps->host, appData.remoteUser, cps->program);
8184         }
8185         err = StartChildProcess(buf, "", &cps->pr);
8186     }
8187
8188     if (err != 0) {
8189         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8190         DisplayFatalError(buf, err, 1);
8191         cps->pr = NoProc;
8192         cps->isr = NULL;
8193         return;
8194     }
8195
8196     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8197     if (cps->protocolVersion > 1) {
8198       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8199       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8200       cps->comboCnt = 0;  //                and values of combo boxes
8201       SendToProgram(buf, cps);
8202     } else {
8203       SendToProgram("xboard\n", cps);
8204     }
8205 }
8206
8207
8208 void
8209 TwoMachinesEventIfReady P((void))
8210 {
8211   if (first.lastPing != first.lastPong) {
8212     DisplayMessage("", _("Waiting for first chess program"));
8213     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8214     return;
8215   }
8216   if (second.lastPing != second.lastPong) {
8217     DisplayMessage("", _("Waiting for second chess program"));
8218     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8219     return;
8220   }
8221   ThawUI();
8222   TwoMachinesEvent();
8223 }
8224
8225 void
8226 NextMatchGame P((void))
8227 {
8228     int index; /* [HGM] autoinc: step load index during match */
8229     Reset(FALSE, TRUE);
8230     if (*appData.loadGameFile != NULLCHAR) {
8231         index = appData.loadGameIndex;
8232         if(index < 0) { // [HGM] autoinc
8233             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8234             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8235         }
8236         LoadGameFromFile(appData.loadGameFile,
8237                          index,
8238                          appData.loadGameFile, FALSE);
8239     } else if (*appData.loadPositionFile != NULLCHAR) {
8240         index = appData.loadPositionIndex;
8241         if(index < 0) { // [HGM] autoinc
8242             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8243             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8244         }
8245         LoadPositionFromFile(appData.loadPositionFile,
8246                              index,
8247                              appData.loadPositionFile);
8248     }
8249     TwoMachinesEventIfReady();
8250 }
8251
8252 void UserAdjudicationEvent( int result )
8253 {
8254     ChessMove gameResult = GameIsDrawn;
8255
8256     if( result > 0 ) {
8257         gameResult = WhiteWins;
8258     }
8259     else if( result < 0 ) {
8260         gameResult = BlackWins;
8261     }
8262
8263     if( gameMode == TwoMachinesPlay ) {
8264         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8265     }
8266 }
8267
8268
8269 // [HGM] save: calculate checksum of game to make games easily identifiable
8270 int StringCheckSum(char *s)
8271 {
8272         int i = 0;
8273         if(s==NULL) return 0;
8274         while(*s) i = i*259 + *s++;
8275         return i;
8276 }
8277
8278 int GameCheckSum()
8279 {
8280         int i, sum=0;
8281         for(i=backwardMostMove; i<forwardMostMove; i++) {
8282                 sum += pvInfoList[i].depth;
8283                 sum += StringCheckSum(parseList[i]);
8284                 sum += StringCheckSum(commentList[i]);
8285                 sum *= 261;
8286         }
8287         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8288         return sum + StringCheckSum(commentList[i]);
8289 } // end of save patch
8290
8291 void
8292 GameEnds(result, resultDetails, whosays)
8293      ChessMove result;
8294      char *resultDetails;
8295      int whosays;
8296 {
8297     GameMode nextGameMode;
8298     int isIcsGame;
8299     char buf[MSG_SIZ];
8300
8301     if(endingGame) return; /* [HGM] crash: forbid recursion */
8302     endingGame = 1;
8303
8304     if (appData.debugMode) {
8305       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8306               result, resultDetails ? resultDetails : "(null)", whosays);
8307     }
8308
8309     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8310         /* If we are playing on ICS, the server decides when the
8311            game is over, but the engine can offer to draw, claim
8312            a draw, or resign.
8313          */
8314 #if ZIPPY
8315         if (appData.zippyPlay && first.initDone) {
8316             if (result == GameIsDrawn) {
8317                 /* In case draw still needs to be claimed */
8318                 SendToICS(ics_prefix);
8319                 SendToICS("draw\n");
8320             } else if (StrCaseStr(resultDetails, "resign")) {
8321                 SendToICS(ics_prefix);
8322                 SendToICS("resign\n");
8323             }
8324         }
8325 #endif
8326         endingGame = 0; /* [HGM] crash */
8327         return;
8328     }
8329
8330     /* If we're loading the game from a file, stop */
8331     if (whosays == GE_FILE) {
8332       (void) StopLoadGameTimer();
8333       gameFileFP = NULL;
8334     }
8335
8336     /* Cancel draw offers */
8337     first.offeredDraw = second.offeredDraw = 0;
8338
8339     /* If this is an ICS game, only ICS can really say it's done;
8340        if not, anyone can. */
8341     isIcsGame = (gameMode == IcsPlayingWhite ||
8342                  gameMode == IcsPlayingBlack ||
8343                  gameMode == IcsObserving    ||
8344                  gameMode == IcsExamining);
8345
8346     if (!isIcsGame || whosays == GE_ICS) {
8347         /* OK -- not an ICS game, or ICS said it was done */
8348         StopClocks();
8349         if (!isIcsGame && !appData.noChessProgram)
8350           SetUserThinkingEnables();
8351
8352         /* [HGM] if a machine claims the game end we verify this claim */
8353         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8354             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8355                 char claimer;
8356                 ChessMove trueResult = (ChessMove) -1;
8357
8358                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8359                                             first.twoMachinesColor[0] :
8360                                             second.twoMachinesColor[0] ;
8361
8362                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8363                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8364                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8365                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8366                 } else
8367                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8368                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8369                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8370                 } else
8371                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8372                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8373                 }
8374
8375                 // now verify win claims, but not in drop games, as we don't understand those yet
8376                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8377                                                  || gameInfo.variant == VariantGreat) &&
8378                     (result == WhiteWins && claimer == 'w' ||
8379                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8380                       if (appData.debugMode) {
8381                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8382                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8383                       }
8384                       if(result != trueResult) {
8385                               sprintf(buf, "False win claim: '%s'", resultDetails);
8386                               result = claimer == 'w' ? BlackWins : WhiteWins;
8387                               resultDetails = buf;
8388                       }
8389                 } else
8390                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8391                     && (forwardMostMove <= backwardMostMove ||
8392                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8393                         (claimer=='b')==(forwardMostMove&1))
8394                                                                                   ) {
8395                       /* [HGM] verify: draws that were not flagged are false claims */
8396                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8397                       result = claimer == 'w' ? BlackWins : WhiteWins;
8398                       resultDetails = buf;
8399                 }
8400                 /* (Claiming a loss is accepted no questions asked!) */
8401             }
8402
8403             /* [HGM] bare: don't allow bare King to win */
8404             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8405                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8406                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8407                && result != GameIsDrawn)
8408             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8409                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8410                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8411                         if(p >= 0 && p <= (int)WhiteKing) k++;
8412                 }
8413                 if (appData.debugMode) {
8414                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8415                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8416                 }
8417                 if(k <= 1) {
8418                         result = GameIsDrawn;
8419                         sprintf(buf, "%s but bare king", resultDetails);
8420                         resultDetails = buf;
8421                 }
8422             }
8423         }
8424
8425         if(serverMoves != NULL && !loadFlag) { char c = '=';
8426             if(result==WhiteWins) c = '+';
8427             if(result==BlackWins) c = '-';
8428             if(resultDetails != NULL)
8429                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8430         }
8431         if (resultDetails != NULL) {
8432             gameInfo.result = result;
8433             gameInfo.resultDetails = StrSave(resultDetails);
8434
8435             /* display last move only if game was not loaded from file */
8436             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8437                 DisplayMove(currentMove - 1);
8438
8439             if (forwardMostMove != 0) {
8440                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8441                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8442                                                                 ) {
8443                     if (*appData.saveGameFile != NULLCHAR) {
8444                         SaveGameToFile(appData.saveGameFile, TRUE);
8445                     } else if (appData.autoSaveGames) {
8446                         AutoSaveGame();
8447                     }
8448                     if (*appData.savePositionFile != NULLCHAR) {
8449                         SavePositionToFile(appData.savePositionFile);
8450                     }
8451                 }
8452             }
8453
8454             /* Tell program how game ended in case it is learning */
8455             /* [HGM] Moved this to after saving the PGN, just in case */
8456             /* engine died and we got here through time loss. In that */
8457             /* case we will get a fatal error writing the pipe, which */
8458             /* would otherwise lose us the PGN.                       */
8459             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8460             /* output during GameEnds should never be fatal anymore   */
8461             if (gameMode == MachinePlaysWhite ||
8462                 gameMode == MachinePlaysBlack ||
8463                 gameMode == TwoMachinesPlay ||
8464                 gameMode == IcsPlayingWhite ||
8465                 gameMode == IcsPlayingBlack ||
8466                 gameMode == BeginningOfGame) {
8467                 char buf[MSG_SIZ];
8468                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8469                         resultDetails);
8470                 if (first.pr != NoProc) {
8471                     SendToProgram(buf, &first);
8472                 }
8473                 if (second.pr != NoProc &&
8474                     gameMode == TwoMachinesPlay) {
8475                     SendToProgram(buf, &second);
8476                 }
8477             }
8478         }
8479
8480         if (appData.icsActive) {
8481             if (appData.quietPlay &&
8482                 (gameMode == IcsPlayingWhite ||
8483                  gameMode == IcsPlayingBlack)) {
8484                 SendToICS(ics_prefix);
8485                 SendToICS("set shout 1\n");
8486             }
8487             nextGameMode = IcsIdle;
8488             ics_user_moved = FALSE;
8489             /* clean up premove.  It's ugly when the game has ended and the
8490              * premove highlights are still on the board.
8491              */
8492             if (gotPremove) {
8493               gotPremove = FALSE;
8494               ClearPremoveHighlights();
8495               DrawPosition(FALSE, boards[currentMove]);
8496             }
8497             if (whosays == GE_ICS) {
8498                 switch (result) {
8499                 case WhiteWins:
8500                     if (gameMode == IcsPlayingWhite)
8501                         PlayIcsWinSound();
8502                     else if(gameMode == IcsPlayingBlack)
8503                         PlayIcsLossSound();
8504                     break;
8505                 case BlackWins:
8506                     if (gameMode == IcsPlayingBlack)
8507                         PlayIcsWinSound();
8508                     else if(gameMode == IcsPlayingWhite)
8509                         PlayIcsLossSound();
8510                     break;
8511                 case GameIsDrawn:
8512                     PlayIcsDrawSound();
8513                     break;
8514                 default:
8515                     PlayIcsUnfinishedSound();
8516                 }
8517             }
8518         } else if (gameMode == EditGame ||
8519                    gameMode == PlayFromGameFile ||
8520                    gameMode == AnalyzeMode ||
8521                    gameMode == AnalyzeFile) {
8522             nextGameMode = gameMode;
8523         } else {
8524             nextGameMode = EndOfGame;
8525         }
8526         pausing = FALSE;
8527         ModeHighlight();
8528     } else {
8529         nextGameMode = gameMode;
8530     }
8531
8532     if (appData.noChessProgram) {
8533         gameMode = nextGameMode;
8534         ModeHighlight();
8535         endingGame = 0; /* [HGM] crash */
8536         return;
8537     }
8538
8539     if (first.reuse) {
8540         /* Put first chess program into idle state */
8541         if (first.pr != NoProc &&
8542             (gameMode == MachinePlaysWhite ||
8543              gameMode == MachinePlaysBlack ||
8544              gameMode == TwoMachinesPlay ||
8545              gameMode == IcsPlayingWhite ||
8546              gameMode == IcsPlayingBlack ||
8547              gameMode == BeginningOfGame)) {
8548             SendToProgram("force\n", &first);
8549             if (first.usePing) {
8550               char buf[MSG_SIZ];
8551               sprintf(buf, "ping %d\n", ++first.lastPing);
8552               SendToProgram(buf, &first);
8553             }
8554         }
8555     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8556         /* Kill off first chess program */
8557         if (first.isr != NULL)
8558           RemoveInputSource(first.isr);
8559         first.isr = NULL;
8560
8561         if (first.pr != NoProc) {
8562             ExitAnalyzeMode();
8563             DoSleep( appData.delayBeforeQuit );
8564             SendToProgram("quit\n", &first);
8565             DoSleep( appData.delayAfterQuit );
8566             DestroyChildProcess(first.pr, first.useSigterm);
8567         }
8568         first.pr = NoProc;
8569     }
8570     if (second.reuse) {
8571         /* Put second chess program into idle state */
8572         if (second.pr != NoProc &&
8573             gameMode == TwoMachinesPlay) {
8574             SendToProgram("force\n", &second);
8575             if (second.usePing) {
8576               char buf[MSG_SIZ];
8577               sprintf(buf, "ping %d\n", ++second.lastPing);
8578               SendToProgram(buf, &second);
8579             }
8580         }
8581     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8582         /* Kill off second chess program */
8583         if (second.isr != NULL)
8584           RemoveInputSource(second.isr);
8585         second.isr = NULL;
8586
8587         if (second.pr != NoProc) {
8588             DoSleep( appData.delayBeforeQuit );
8589             SendToProgram("quit\n", &second);
8590             DoSleep( appData.delayAfterQuit );
8591             DestroyChildProcess(second.pr, second.useSigterm);
8592         }
8593         second.pr = NoProc;
8594     }
8595
8596     if (matchMode && gameMode == TwoMachinesPlay) {
8597         switch (result) {
8598         case WhiteWins:
8599           if (first.twoMachinesColor[0] == 'w') {
8600             first.matchWins++;
8601           } else {
8602             second.matchWins++;
8603           }
8604           break;
8605         case BlackWins:
8606           if (first.twoMachinesColor[0] == 'b') {
8607             first.matchWins++;
8608           } else {
8609             second.matchWins++;
8610           }
8611           break;
8612         default:
8613           break;
8614         }
8615         if (matchGame < appData.matchGames) {
8616             char *tmp;
8617             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8618                 tmp = first.twoMachinesColor;
8619                 first.twoMachinesColor = second.twoMachinesColor;
8620                 second.twoMachinesColor = tmp;
8621             }
8622             gameMode = nextGameMode;
8623             matchGame++;
8624             if(appData.matchPause>10000 || appData.matchPause<10)
8625                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8626             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8627             endingGame = 0; /* [HGM] crash */
8628             return;
8629         } else {
8630             char buf[MSG_SIZ];
8631             gameMode = nextGameMode;
8632             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8633                     first.tidy, second.tidy,
8634                     first.matchWins, second.matchWins,
8635                     appData.matchGames - (first.matchWins + second.matchWins));
8636             DisplayFatalError(buf, 0, 0);
8637         }
8638     }
8639     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8640         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8641       ExitAnalyzeMode();
8642     gameMode = nextGameMode;
8643     ModeHighlight();
8644     endingGame = 0;  /* [HGM] crash */
8645 }
8646
8647 /* Assumes program was just initialized (initString sent).
8648    Leaves program in force mode. */
8649 void
8650 FeedMovesToProgram(cps, upto)
8651      ChessProgramState *cps;
8652      int upto;
8653 {
8654     int i;
8655
8656     if (appData.debugMode)
8657       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8658               startedFromSetupPosition ? "position and " : "",
8659               backwardMostMove, upto, cps->which);
8660     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8661         // [HGM] variantswitch: make engine aware of new variant
8662         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8663                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8664         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8665         SendToProgram(buf, cps);
8666         currentlyInitializedVariant = gameInfo.variant;
8667     }
8668     SendToProgram("force\n", cps);
8669     if (startedFromSetupPosition) {
8670         SendBoard(cps, backwardMostMove);
8671     if (appData.debugMode) {
8672         fprintf(debugFP, "feedMoves\n");
8673     }
8674     }
8675     for (i = backwardMostMove; i < upto; i++) {
8676         SendMoveToProgram(i, cps);
8677     }
8678 }
8679
8680
8681 void
8682 ResurrectChessProgram()
8683 {
8684      /* The chess program may have exited.
8685         If so, restart it and feed it all the moves made so far. */
8686
8687     if (appData.noChessProgram || first.pr != NoProc) return;
8688
8689     StartChessProgram(&first);
8690     InitChessProgram(&first, FALSE);
8691     FeedMovesToProgram(&first, currentMove);
8692
8693     if (!first.sendTime) {
8694         /* can't tell gnuchess what its clock should read,
8695            so we bow to its notion. */
8696         ResetClocks();
8697         timeRemaining[0][currentMove] = whiteTimeRemaining;
8698         timeRemaining[1][currentMove] = blackTimeRemaining;
8699     }
8700
8701     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8702                 appData.icsEngineAnalyze) && first.analysisSupport) {
8703       SendToProgram("analyze\n", &first);
8704       first.analyzing = TRUE;
8705     }
8706 }
8707
8708 /*
8709  * Button procedures
8710  */
8711 void
8712 Reset(redraw, init)
8713      int redraw, init;
8714 {
8715     int i;
8716
8717     if (appData.debugMode) {
8718         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8719                 redraw, init, gameMode);
8720     }
8721     CleanupTail(); // [HGM] vari: delete any stored variations
8722     pausing = pauseExamInvalid = FALSE;
8723     startedFromSetupPosition = blackPlaysFirst = FALSE;
8724     firstMove = TRUE;
8725     whiteFlag = blackFlag = FALSE;
8726     userOfferedDraw = FALSE;
8727     hintRequested = bookRequested = FALSE;
8728     first.maybeThinking = FALSE;
8729     second.maybeThinking = FALSE;
8730     first.bookSuspend = FALSE; // [HGM] book
8731     second.bookSuspend = FALSE;
8732     thinkOutput[0] = NULLCHAR;
8733     lastHint[0] = NULLCHAR;
8734     ClearGameInfo(&gameInfo);
8735     gameInfo.variant = StringToVariant(appData.variant);
8736     ics_user_moved = ics_clock_paused = FALSE;
8737     ics_getting_history = H_FALSE;
8738     ics_gamenum = -1;
8739     white_holding[0] = black_holding[0] = NULLCHAR;
8740     ClearProgramStats();
8741     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8742
8743     ResetFrontEnd();
8744     ClearHighlights();
8745     flipView = appData.flipView;
8746     ClearPremoveHighlights();
8747     gotPremove = FALSE;
8748     alarmSounded = FALSE;
8749
8750     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8751     if(appData.serverMovesName != NULL) {
8752         /* [HGM] prepare to make moves file for broadcasting */
8753         clock_t t = clock();
8754         if(serverMoves != NULL) fclose(serverMoves);
8755         serverMoves = fopen(appData.serverMovesName, "r");
8756         if(serverMoves != NULL) {
8757             fclose(serverMoves);
8758             /* delay 15 sec before overwriting, so all clients can see end */
8759             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8760         }
8761         serverMoves = fopen(appData.serverMovesName, "w");
8762     }
8763
8764     ExitAnalyzeMode();
8765     gameMode = BeginningOfGame;
8766     ModeHighlight();
8767
8768     if(appData.icsActive) gameInfo.variant = VariantNormal;
8769     currentMove = forwardMostMove = backwardMostMove = 0;
8770     InitPosition(redraw);
8771     for (i = 0; i < MAX_MOVES; i++) {
8772         if (commentList[i] != NULL) {
8773             free(commentList[i]);
8774             commentList[i] = NULL;
8775         }
8776     }
8777
8778     ResetClocks();
8779     timeRemaining[0][0] = whiteTimeRemaining;
8780     timeRemaining[1][0] = blackTimeRemaining;
8781     if (first.pr == NULL) {
8782         StartChessProgram(&first);
8783     }
8784     if (init) {
8785             InitChessProgram(&first, startedFromSetupPosition);
8786     }
8787
8788     DisplayTitle("");
8789     DisplayMessage("", "");
8790     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8791     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8792     return;
8793 }
8794
8795 void
8796 AutoPlayGameLoop()
8797 {
8798     for (;;) {
8799         if (!AutoPlayOneMove())
8800           return;
8801         if (matchMode || appData.timeDelay == 0)
8802           continue;
8803         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8804           return;
8805         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8806         break;
8807     }
8808 }
8809
8810
8811 int
8812 AutoPlayOneMove()
8813 {
8814     int fromX, fromY, toX, toY;
8815
8816     if (appData.debugMode) {
8817       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8818     }
8819
8820     if (gameMode != PlayFromGameFile)
8821       return FALSE;
8822
8823     if (currentMove >= forwardMostMove) {
8824       gameMode = EditGame;
8825       ModeHighlight();
8826
8827       /* [AS] Clear current move marker at the end of a game */
8828       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8829
8830       return FALSE;
8831     }
8832
8833     toX = moveList[currentMove][2] - AAA;
8834     toY = moveList[currentMove][3] - ONE;
8835
8836     if (moveList[currentMove][1] == '@') {
8837         if (appData.highlightLastMove) {
8838             SetHighlights(-1, -1, toX, toY);
8839         }
8840     } else {
8841         fromX = moveList[currentMove][0] - AAA;
8842         fromY = moveList[currentMove][1] - ONE;
8843
8844         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8845
8846         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8847
8848         if (appData.highlightLastMove) {
8849             SetHighlights(fromX, fromY, toX, toY);
8850         }
8851     }
8852     DisplayMove(currentMove);
8853     SendMoveToProgram(currentMove++, &first);
8854     DisplayBothClocks();
8855     DrawPosition(FALSE, boards[currentMove]);
8856     // [HGM] PV info: always display, routine tests if empty
8857     DisplayComment(currentMove - 1, commentList[currentMove]);
8858     return TRUE;
8859 }
8860
8861
8862 int
8863 LoadGameOneMove(readAhead)
8864      ChessMove readAhead;
8865 {
8866     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8867     char promoChar = NULLCHAR;
8868     ChessMove moveType;
8869     char move[MSG_SIZ];
8870     char *p, *q;
8871
8872     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8873         gameMode != AnalyzeMode && gameMode != Training) {
8874         gameFileFP = NULL;
8875         return FALSE;
8876     }
8877
8878     yyboardindex = forwardMostMove;
8879     if (readAhead != (ChessMove)0) {
8880       moveType = readAhead;
8881     } else {
8882       if (gameFileFP == NULL)
8883           return FALSE;
8884       moveType = (ChessMove) yylex();
8885     }
8886
8887     done = FALSE;
8888     switch (moveType) {
8889       case Comment:
8890         if (appData.debugMode)
8891           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8892         p = yy_text;
8893
8894         /* append the comment but don't display it */
8895         AppendComment(currentMove, p, FALSE);
8896         return TRUE;
8897
8898       case WhiteCapturesEnPassant:
8899       case BlackCapturesEnPassant:
8900       case WhitePromotionChancellor:
8901       case BlackPromotionChancellor:
8902       case WhitePromotionArchbishop:
8903       case BlackPromotionArchbishop:
8904       case WhitePromotionCentaur:
8905       case BlackPromotionCentaur:
8906       case WhitePromotionQueen:
8907       case BlackPromotionQueen:
8908       case WhitePromotionRook:
8909       case BlackPromotionRook:
8910       case WhitePromotionBishop:
8911       case BlackPromotionBishop:
8912       case WhitePromotionKnight:
8913       case BlackPromotionKnight:
8914       case WhitePromotionKing:
8915       case BlackPromotionKing:
8916       case NormalMove:
8917       case WhiteKingSideCastle:
8918       case WhiteQueenSideCastle:
8919       case BlackKingSideCastle:
8920       case BlackQueenSideCastle:
8921       case WhiteKingSideCastleWild:
8922       case WhiteQueenSideCastleWild:
8923       case BlackKingSideCastleWild:
8924       case BlackQueenSideCastleWild:
8925       /* PUSH Fabien */
8926       case WhiteHSideCastleFR:
8927       case WhiteASideCastleFR:
8928       case BlackHSideCastleFR:
8929       case BlackASideCastleFR:
8930       /* POP Fabien */
8931         if (appData.debugMode)
8932           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8933         fromX = currentMoveString[0] - AAA;
8934         fromY = currentMoveString[1] - ONE;
8935         toX = currentMoveString[2] - AAA;
8936         toY = currentMoveString[3] - ONE;
8937         promoChar = currentMoveString[4];
8938         break;
8939
8940       case WhiteDrop:
8941       case BlackDrop:
8942         if (appData.debugMode)
8943           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8944         fromX = moveType == WhiteDrop ?
8945           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8946         (int) CharToPiece(ToLower(currentMoveString[0]));
8947         fromY = DROP_RANK;
8948         toX = currentMoveString[2] - AAA;
8949         toY = currentMoveString[3] - ONE;
8950         break;
8951
8952       case WhiteWins:
8953       case BlackWins:
8954       case GameIsDrawn:
8955       case GameUnfinished:
8956         if (appData.debugMode)
8957           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8958         p = strchr(yy_text, '{');
8959         if (p == NULL) p = strchr(yy_text, '(');
8960         if (p == NULL) {
8961             p = yy_text;
8962             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8963         } else {
8964             q = strchr(p, *p == '{' ? '}' : ')');
8965             if (q != NULL) *q = NULLCHAR;
8966             p++;
8967         }
8968         GameEnds(moveType, p, GE_FILE);
8969         done = TRUE;
8970         if (cmailMsgLoaded) {
8971             ClearHighlights();
8972             flipView = WhiteOnMove(currentMove);
8973             if (moveType == GameUnfinished) flipView = !flipView;
8974             if (appData.debugMode)
8975               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8976         }
8977         break;
8978
8979       case (ChessMove) 0:       /* end of file */
8980         if (appData.debugMode)
8981           fprintf(debugFP, "Parser hit end of file\n");
8982         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8983           case MT_NONE:
8984           case MT_CHECK:
8985             break;
8986           case MT_CHECKMATE:
8987           case MT_STAINMATE:
8988             if (WhiteOnMove(currentMove)) {
8989                 GameEnds(BlackWins, "Black mates", GE_FILE);
8990             } else {
8991                 GameEnds(WhiteWins, "White mates", GE_FILE);
8992             }
8993             break;
8994           case MT_STALEMATE:
8995             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8996             break;
8997         }
8998         done = TRUE;
8999         break;
9000
9001       case MoveNumberOne:
9002         if (lastLoadGameStart == GNUChessGame) {
9003             /* GNUChessGames have numbers, but they aren't move numbers */
9004             if (appData.debugMode)
9005               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9006                       yy_text, (int) moveType);
9007             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9008         }
9009         /* else fall thru */
9010
9011       case XBoardGame:
9012       case GNUChessGame:
9013       case PGNTag:
9014         /* Reached start of next game in file */
9015         if (appData.debugMode)
9016           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9017         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9018           case MT_NONE:
9019           case MT_CHECK:
9020             break;
9021           case MT_CHECKMATE:
9022           case MT_STAINMATE:
9023             if (WhiteOnMove(currentMove)) {
9024                 GameEnds(BlackWins, "Black mates", GE_FILE);
9025             } else {
9026                 GameEnds(WhiteWins, "White mates", GE_FILE);
9027             }
9028             break;
9029           case MT_STALEMATE:
9030             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9031             break;
9032         }
9033         done = TRUE;
9034         break;
9035
9036       case PositionDiagram:     /* should not happen; ignore */
9037       case ElapsedTime:         /* ignore */
9038       case NAG:                 /* ignore */
9039         if (appData.debugMode)
9040           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9041                   yy_text, (int) moveType);
9042         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9043
9044       case IllegalMove:
9045         if (appData.testLegality) {
9046             if (appData.debugMode)
9047               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9048             sprintf(move, _("Illegal move: %d.%s%s"),
9049                     (forwardMostMove / 2) + 1,
9050                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9051             DisplayError(move, 0);
9052             done = TRUE;
9053         } else {
9054             if (appData.debugMode)
9055               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9056                       yy_text, currentMoveString);
9057             fromX = currentMoveString[0] - AAA;
9058             fromY = currentMoveString[1] - ONE;
9059             toX = currentMoveString[2] - AAA;
9060             toY = currentMoveString[3] - ONE;
9061             promoChar = currentMoveString[4];
9062         }
9063         break;
9064
9065       case AmbiguousMove:
9066         if (appData.debugMode)
9067           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9068         sprintf(move, _("Ambiguous move: %d.%s%s"),
9069                 (forwardMostMove / 2) + 1,
9070                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9071         DisplayError(move, 0);
9072         done = TRUE;
9073         break;
9074
9075       default:
9076       case ImpossibleMove:
9077         if (appData.debugMode)
9078           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9079         sprintf(move, _("Illegal move: %d.%s%s"),
9080                 (forwardMostMove / 2) + 1,
9081                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9082         DisplayError(move, 0);
9083         done = TRUE;
9084         break;
9085     }
9086
9087     if (done) {
9088         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9089             DrawPosition(FALSE, boards[currentMove]);
9090             DisplayBothClocks();
9091             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9092               DisplayComment(currentMove - 1, commentList[currentMove]);
9093         }
9094         (void) StopLoadGameTimer();
9095         gameFileFP = NULL;
9096         cmailOldMove = forwardMostMove;
9097         return FALSE;
9098     } else {
9099         /* currentMoveString is set as a side-effect of yylex */
9100         strcat(currentMoveString, "\n");
9101         strcpy(moveList[forwardMostMove], currentMoveString);
9102
9103         thinkOutput[0] = NULLCHAR;
9104         MakeMove(fromX, fromY, toX, toY, promoChar);
9105         currentMove = forwardMostMove;
9106         return TRUE;
9107     }
9108 }
9109
9110 /* Load the nth game from the given file */
9111 int
9112 LoadGameFromFile(filename, n, title, useList)
9113      char *filename;
9114      int n;
9115      char *title;
9116      /*Boolean*/ int useList;
9117 {
9118     FILE *f;
9119     char buf[MSG_SIZ];
9120
9121     if (strcmp(filename, "-") == 0) {
9122         f = stdin;
9123         title = "stdin";
9124     } else {
9125         f = fopen(filename, "rb");
9126         if (f == NULL) {
9127           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9128             DisplayError(buf, errno);
9129             return FALSE;
9130         }
9131     }
9132     if (fseek(f, 0, 0) == -1) {
9133         /* f is not seekable; probably a pipe */
9134         useList = FALSE;
9135     }
9136     if (useList && n == 0) {
9137         int error = GameListBuild(f);
9138         if (error) {
9139             DisplayError(_("Cannot build game list"), error);
9140         } else if (!ListEmpty(&gameList) &&
9141                    ((ListGame *) gameList.tailPred)->number > 1) {
9142           // TODO convert to GTK
9143           //        GameListPopUp(f, title);
9144             return TRUE;
9145         }
9146         GameListDestroy();
9147         n = 1;
9148     }
9149     if (n == 0) n = 1;
9150     return LoadGame(f, n, title, FALSE);
9151 }
9152
9153
9154 void
9155 MakeRegisteredMove()
9156 {
9157     int fromX, fromY, toX, toY;
9158     char promoChar;
9159     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9160         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9161           case CMAIL_MOVE:
9162           case CMAIL_DRAW:
9163             if (appData.debugMode)
9164               fprintf(debugFP, "Restoring %s for game %d\n",
9165                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9166
9167             thinkOutput[0] = NULLCHAR;
9168             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9169             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9170             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9171             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9172             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9173             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9174             MakeMove(fromX, fromY, toX, toY, promoChar);
9175             ShowMove(fromX, fromY, toX, toY);
9176             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9177               case MT_NONE:
9178               case MT_CHECK:
9179                 break;
9180
9181               case MT_CHECKMATE:
9182               case MT_STAINMATE:
9183                 if (WhiteOnMove(currentMove)) {
9184                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9185                 } else {
9186                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9187                 }
9188                 break;
9189
9190               case MT_STALEMATE:
9191                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9192                 break;
9193             }
9194
9195             break;
9196
9197           case CMAIL_RESIGN:
9198             if (WhiteOnMove(currentMove)) {
9199                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9200             } else {
9201                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9202             }
9203             break;
9204
9205           case CMAIL_ACCEPT:
9206             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9207             break;
9208
9209           default:
9210             break;
9211         }
9212     }
9213
9214     return;
9215 }
9216
9217 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9218 int
9219 CmailLoadGame(f, gameNumber, title, useList)
9220      FILE *f;
9221      int gameNumber;
9222      char *title;
9223      int useList;
9224 {
9225     int retVal;
9226
9227     if (gameNumber > nCmailGames) {
9228         DisplayError(_("No more games in this message"), 0);
9229         return FALSE;
9230     }
9231     if (f == lastLoadGameFP) {
9232         int offset = gameNumber - lastLoadGameNumber;
9233         if (offset == 0) {
9234             cmailMsg[0] = NULLCHAR;
9235             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9236                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9237                 nCmailMovesRegistered--;
9238             }
9239             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9240             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9241                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9242             }
9243         } else {
9244             if (! RegisterMove()) return FALSE;
9245         }
9246     }
9247
9248     retVal = LoadGame(f, gameNumber, title, useList);
9249
9250     /* Make move registered during previous look at this game, if any */
9251     MakeRegisteredMove();
9252
9253     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9254         commentList[currentMove]
9255           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9256         DisplayComment(currentMove - 1, commentList[currentMove]);
9257     }
9258
9259     return retVal;
9260 }
9261
9262 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9263 int
9264 ReloadGame(offset)
9265      int offset;
9266 {
9267     int gameNumber = lastLoadGameNumber + offset;
9268     if (lastLoadGameFP == NULL) {
9269         DisplayError(_("No game has been loaded yet"), 0);
9270         return FALSE;
9271     }
9272     if (gameNumber <= 0) {
9273         DisplayError(_("Can't back up any further"), 0);
9274         return FALSE;
9275     }
9276     if (cmailMsgLoaded) {
9277         return CmailLoadGame(lastLoadGameFP, gameNumber,
9278                              lastLoadGameTitle, lastLoadGameUseList);
9279     } else {
9280         return LoadGame(lastLoadGameFP, gameNumber,
9281                         lastLoadGameTitle, lastLoadGameUseList);
9282     }
9283 }
9284
9285
9286
9287 /* Load the nth game from open file f */
9288 int
9289 LoadGame(f, gameNumber, title, useList)
9290      FILE *f;
9291      int gameNumber;
9292      char *title;
9293      int useList;
9294 {
9295     ChessMove cm;
9296     char buf[MSG_SIZ];
9297     int gn = gameNumber;
9298     ListGame *lg = NULL;
9299     int numPGNTags = 0;
9300     int err;
9301     GameMode oldGameMode;
9302     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9303
9304     if (appData.debugMode)
9305         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9306
9307     if (gameMode == Training )
9308         SetTrainingModeOff();
9309
9310     oldGameMode = gameMode;
9311     if (gameMode != BeginningOfGame) 
9312       {
9313         Reset(FALSE, TRUE);
9314       };
9315
9316     gameFileFP = f;
9317     if (lastLoadGameFP != NULL && lastLoadGameFP != f) 
9318       {
9319         fclose(lastLoadGameFP);
9320       };
9321
9322     if (useList) 
9323       {
9324         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9325         
9326         if (lg) 
9327           {
9328             fseek(f, lg->offset, 0);
9329             GameListHighlight(gameNumber);
9330             gn = 1;
9331           }
9332         else 
9333           {
9334             DisplayError(_("Game number out of range"), 0);
9335             return FALSE;
9336           };
9337       } 
9338     else 
9339       {
9340         GameListDestroy();
9341         if (fseek(f, 0, 0) == -1) 
9342           {
9343             if (f == lastLoadGameFP ?
9344                 gameNumber == lastLoadGameNumber + 1 :
9345                 gameNumber == 1) 
9346               {
9347                 gn = 1;
9348               } 
9349             else 
9350               {
9351                 DisplayError(_("Can't seek on game file"), 0);
9352                 return FALSE;
9353               };
9354           };
9355       };
9356
9357     lastLoadGameFP      = f;
9358     lastLoadGameNumber  = gameNumber;
9359     strcpy(lastLoadGameTitle, title);
9360     lastLoadGameUseList = useList;
9361
9362     yynewfile(f);
9363
9364     if (lg && lg->gameInfo.white && lg->gameInfo.black) 
9365       {
9366         snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9367                  lg->gameInfo.black);
9368         DisplayTitle(buf);
9369       } 
9370     else if (*title != NULLCHAR) 
9371       {
9372         if (gameNumber > 1) 
9373           {
9374             sprintf(buf, "%s %d", title, gameNumber);
9375             DisplayTitle(buf);
9376           } 
9377         else 
9378           {
9379             DisplayTitle(title);
9380           };
9381       };
9382
9383     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) 
9384       {
9385         gameMode = PlayFromGameFile;
9386         ModeHighlight();
9387       };
9388
9389     currentMove = forwardMostMove = backwardMostMove = 0;
9390     CopyBoard(boards[0], initialPosition);
9391     StopClocks();
9392
9393     /*
9394      * Skip the first gn-1 games in the file.
9395      * Also skip over anything that precedes an identifiable
9396      * start of game marker, to avoid being confused by
9397      * garbage at the start of the file.  Currently
9398      * recognized start of game markers are the move number "1",
9399      * the pattern "gnuchess .* game", the pattern
9400      * "^[#;%] [^ ]* game file", and a PGN tag block.
9401      * A game that starts with one of the latter two patterns
9402      * will also have a move number 1, possibly
9403      * following a position diagram.
9404      * 5-4-02: Let's try being more lenient and allowing a game to
9405      * start with an unnumbered move.  Does that break anything?
9406      */
9407     cm = lastLoadGameStart = (ChessMove) 0;
9408     while (gn > 0) {
9409         yyboardindex = forwardMostMove;
9410         cm = (ChessMove) yylex();
9411         switch (cm) {
9412           case (ChessMove) 0:
9413             if (cmailMsgLoaded) {
9414                 nCmailGames = CMAIL_MAX_GAMES - gn;
9415             } else {
9416                 Reset(TRUE, TRUE);
9417                 DisplayError(_("Game not found in file"), 0);
9418             }
9419             return FALSE;
9420
9421           case GNUChessGame:
9422           case XBoardGame:
9423             gn--;
9424             lastLoadGameStart = cm;
9425             break;
9426
9427           case MoveNumberOne:
9428             switch (lastLoadGameStart) {
9429               case GNUChessGame:
9430               case XBoardGame:
9431               case PGNTag:
9432                 break;
9433               case MoveNumberOne:
9434               case (ChessMove) 0:
9435                 gn--;           /* count this game */
9436                 lastLoadGameStart = cm;
9437                 break;
9438               default:
9439                 /* impossible */
9440                 break;
9441             }
9442             break;
9443
9444           case PGNTag:
9445             switch (lastLoadGameStart) {
9446               case GNUChessGame:
9447               case PGNTag:
9448               case MoveNumberOne:
9449               case (ChessMove) 0:
9450                 gn--;           /* count this game */
9451                 lastLoadGameStart = cm;
9452                 break;
9453               case XBoardGame:
9454                 lastLoadGameStart = cm; /* game counted already */
9455                 break;
9456               default:
9457                 /* impossible */
9458                 break;
9459             }
9460             if (gn > 0) {
9461                 do {
9462                     yyboardindex = forwardMostMove;
9463                     cm = (ChessMove) yylex();
9464                 } while (cm == PGNTag || cm == Comment);
9465             }
9466             break;
9467
9468           case WhiteWins:
9469           case BlackWins:
9470           case GameIsDrawn:
9471             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9472                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9473                     != CMAIL_OLD_RESULT) {
9474                     nCmailResults ++ ;
9475                     cmailResult[  CMAIL_MAX_GAMES
9476                                 - gn - 1] = CMAIL_OLD_RESULT;
9477                 }
9478             }
9479             break;
9480
9481           case NormalMove:
9482             /* Only a NormalMove can be at the start of a game
9483              * without a position diagram. */
9484             if (lastLoadGameStart == (ChessMove) 0) {
9485               gn--;
9486               lastLoadGameStart = MoveNumberOne;
9487             }
9488             break;
9489
9490           default:
9491             break;
9492         }
9493     }
9494
9495     if (appData.debugMode)
9496       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9497
9498     if (cm == XBoardGame) {
9499         /* Skip any header junk before position diagram and/or move 1 */
9500         for (;;) {
9501             yyboardindex = forwardMostMove;
9502             cm = (ChessMove) yylex();
9503
9504             if (cm == (ChessMove) 0 ||
9505                 cm == GNUChessGame || cm == XBoardGame) {
9506                 /* Empty game; pretend end-of-file and handle later */
9507                 cm = (ChessMove) 0;
9508                 break;
9509             }
9510
9511             if (cm == MoveNumberOne || cm == PositionDiagram ||
9512                 cm == PGNTag || cm == Comment)
9513               break;
9514         }
9515     } else if (cm == GNUChessGame) {
9516         if (gameInfo.event != NULL) {
9517             free(gameInfo.event);
9518         }
9519         gameInfo.event = StrSave(yy_text);
9520     }
9521
9522     startedFromSetupPosition = FALSE;
9523     while (cm == PGNTag) {
9524         if (appData.debugMode)
9525           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9526         err = ParsePGNTag(yy_text, &gameInfo);
9527         if (!err) numPGNTags++;
9528
9529         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9530         if(gameInfo.variant != oldVariant) {
9531             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9532             InitPosition(TRUE);
9533             oldVariant = gameInfo.variant;
9534             if (appData.debugMode)
9535               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9536         }
9537
9538
9539         if (gameInfo.fen != NULL) {
9540           Board initial_position;
9541           startedFromSetupPosition = TRUE;
9542           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9543             Reset(TRUE, TRUE);
9544             DisplayError(_("Bad FEN position in file"), 0);
9545             return FALSE;
9546           }
9547           CopyBoard(boards[0], initial_position);
9548           if (blackPlaysFirst) {
9549             currentMove = forwardMostMove = backwardMostMove = 1;
9550             CopyBoard(boards[1], initial_position);
9551             strcpy(moveList[0], "");
9552             strcpy(parseList[0], "");
9553             timeRemaining[0][1] = whiteTimeRemaining;
9554             timeRemaining[1][1] = blackTimeRemaining;
9555             if (commentList[0] != NULL) {
9556               commentList[1] = commentList[0];
9557               commentList[0] = NULL;
9558             }
9559           } else {
9560             currentMove = forwardMostMove = backwardMostMove = 0;
9561           }
9562           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9563           {   int i;
9564               initialRulePlies = FENrulePlies;
9565               for( i=0; i< nrCastlingRights; i++ )
9566                   initialRights[i] = initial_position[CASTLING][i];
9567           }
9568           yyboardindex = forwardMostMove;
9569           free(gameInfo.fen);
9570           gameInfo.fen = NULL;
9571         }
9572
9573         yyboardindex = forwardMostMove;
9574         cm = (ChessMove) yylex();
9575
9576         /* Handle comments interspersed among the tags */
9577         while (cm == Comment) {
9578             char *p;
9579             if (appData.debugMode)
9580               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9581             p = yy_text;
9582             AppendComment(currentMove, p, FALSE);
9583             yyboardindex = forwardMostMove;
9584             cm = (ChessMove) yylex();
9585         }
9586     }
9587
9588     /* don't rely on existence of Event tag since if game was
9589      * pasted from clipboard the Event tag may not exist
9590      */
9591     if (numPGNTags > 0){
9592         char *tags;
9593         if (gameInfo.variant == VariantNormal) {
9594           gameInfo.variant = StringToVariant(gameInfo.event);
9595         }
9596         if (!matchMode) {
9597           if( appData.autoDisplayTags ) {
9598             tags = PGNTags(&gameInfo);
9599             TagsPopUp(tags, CmailMsg());
9600             free(tags);
9601           }
9602         }
9603     } else {
9604         /* Make something up, but don't display it now */
9605         SetGameInfo();
9606         TagsPopDown();
9607     }
9608
9609     if (cm == PositionDiagram) {
9610         int i, j;
9611         char *p;
9612         Board initial_position;
9613
9614         if (appData.debugMode)
9615           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9616
9617         if (!startedFromSetupPosition) {
9618             p = yy_text;
9619             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9620               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9621                 switch (*p) {
9622                   case '[':
9623                   case '-':
9624                   case ' ':
9625                   case '\t':
9626                   case '\n':
9627                   case '\r':
9628                     break;
9629                   default:
9630                     initial_position[i][j++] = CharToPiece(*p);
9631                     break;
9632                 }
9633             while (*p == ' ' || *p == '\t' ||
9634                    *p == '\n' || *p == '\r') p++;
9635
9636             if (strncmp(p, "black", strlen("black"))==0)
9637               blackPlaysFirst = TRUE;
9638             else
9639               blackPlaysFirst = FALSE;
9640             startedFromSetupPosition = TRUE;
9641
9642             CopyBoard(boards[0], initial_position);
9643             if (blackPlaysFirst) {
9644                 currentMove = forwardMostMove = backwardMostMove = 1;
9645                 CopyBoard(boards[1], initial_position);
9646                 strcpy(moveList[0], "");
9647                 strcpy(parseList[0], "");
9648                 timeRemaining[0][1] = whiteTimeRemaining;
9649                 timeRemaining[1][1] = blackTimeRemaining;
9650                 if (commentList[0] != NULL) {
9651                     commentList[1] = commentList[0];
9652                     commentList[0] = NULL;
9653                 }
9654             } else {
9655                 currentMove = forwardMostMove = backwardMostMove = 0;
9656             }
9657         }
9658         yyboardindex = forwardMostMove;
9659         cm = (ChessMove) yylex();
9660     }
9661
9662     if (first.pr == NoProc) {
9663         StartChessProgram(&first);
9664     }
9665     InitChessProgram(&first, FALSE);
9666     SendToProgram("force\n", &first);
9667     if (startedFromSetupPosition) {
9668         SendBoard(&first, forwardMostMove);
9669     if (appData.debugMode) {
9670         fprintf(debugFP, "Load Game\n");
9671     }
9672         DisplayBothClocks();
9673     }
9674
9675     /* [HGM] server: flag to write setup moves in broadcast file as one */
9676     loadFlag = appData.suppressLoadMoves;
9677
9678     while (cm == Comment) {
9679         char *p;
9680         if (appData.debugMode)
9681           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9682         p = yy_text;
9683         AppendComment(currentMove, p, FALSE);
9684         yyboardindex = forwardMostMove;
9685         cm = (ChessMove) yylex();
9686     }
9687
9688     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9689         cm == WhiteWins || cm == BlackWins ||
9690         cm == GameIsDrawn || cm == GameUnfinished) {
9691         DisplayMessage("", _("No moves in game"));
9692         if (cmailMsgLoaded) {
9693             if (appData.debugMode)
9694               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9695             ClearHighlights();
9696             flipView = FALSE;
9697         }
9698         DrawPosition(FALSE, boards[currentMove]);
9699         DisplayBothClocks();
9700         gameMode = EditGame;
9701         ModeHighlight();
9702         gameFileFP = NULL;
9703         cmailOldMove = 0;
9704         return TRUE;
9705     }
9706
9707     // [HGM] PV info: routine tests if comment empty
9708     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9709         DisplayComment(currentMove - 1, commentList[currentMove]);
9710     }
9711     if (!matchMode && appData.timeDelay != 0)
9712       DrawPosition(FALSE, boards[currentMove]);
9713
9714     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9715       programStats.ok_to_send = 1;
9716     }
9717
9718     /* if the first token after the PGN tags is a move
9719      * and not move number 1, retrieve it from the parser
9720      */
9721     if (cm != MoveNumberOne)
9722         LoadGameOneMove(cm);
9723
9724     /* load the remaining moves from the file */
9725     while (LoadGameOneMove((ChessMove)0)) {
9726       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9727       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9728     }
9729
9730     /* rewind to the start of the game */
9731     currentMove = backwardMostMove;
9732
9733     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9734
9735     if (oldGameMode == AnalyzeFile ||
9736         oldGameMode == AnalyzeMode) {
9737       AnalyzeFileEvent();
9738     }
9739
9740     if (matchMode || appData.timeDelay == 0) {
9741       ToEndEvent();
9742       gameMode = EditGame;
9743       ModeHighlight();
9744     } else if (appData.timeDelay > 0) {
9745       AutoPlayGameLoop();
9746     }
9747
9748     if (appData.debugMode)
9749         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9750
9751     loadFlag = 0; /* [HGM] true game starts */
9752     return TRUE;
9753 }
9754
9755 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9756 int
9757 ReloadPosition(offset)
9758      int offset;
9759 {
9760     int positionNumber = lastLoadPositionNumber + offset;
9761     if (lastLoadPositionFP == NULL) {
9762         DisplayError(_("No position has been loaded yet"), 0);
9763         return FALSE;
9764     }
9765     if (positionNumber <= 0) {
9766         DisplayError(_("Can't back up any further"), 0);
9767         return FALSE;
9768     }
9769     return LoadPosition(lastLoadPositionFP, positionNumber,
9770                         lastLoadPositionTitle);
9771 }
9772
9773 /* Load the nth position from the given file */
9774 int
9775 LoadPositionFromFile(filename, n, title)
9776      char *filename;
9777      int n;
9778      char *title;
9779 {
9780     FILE *f;
9781     char buf[MSG_SIZ];
9782
9783     if (strcmp(filename, "-") == 0) {
9784         return LoadPosition(stdin, n, "stdin");
9785     } else {
9786         f = fopen(filename, "rb");
9787         if (f == NULL) {
9788             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9789             DisplayError(buf, errno);
9790             return FALSE;
9791         } else {
9792             return LoadPosition(f, n, title);
9793         }
9794     }
9795 }
9796
9797 /* Load the nth position from the given open file, and close it */
9798 int
9799 LoadPosition(f, positionNumber, title)
9800      FILE *f;
9801      int positionNumber;
9802      char *title;
9803 {
9804     char *p, line[MSG_SIZ];
9805     Board initial_position;
9806     int i, j, fenMode, pn;
9807
9808     if (gameMode == Training )
9809         SetTrainingModeOff();
9810
9811     if (gameMode != BeginningOfGame) {
9812         Reset(FALSE, TRUE);
9813     }
9814     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9815         fclose(lastLoadPositionFP);
9816     }
9817     if (positionNumber == 0) positionNumber = 1;
9818     lastLoadPositionFP = f;
9819     lastLoadPositionNumber = positionNumber;
9820     strcpy(lastLoadPositionTitle, title);
9821     if (first.pr == NoProc) {
9822       StartChessProgram(&first);
9823       InitChessProgram(&first, FALSE);
9824     }
9825     pn = positionNumber;
9826     if (positionNumber < 0) {
9827         /* Negative position number means to seek to that byte offset */
9828         if (fseek(f, -positionNumber, 0) == -1) {
9829             DisplayError(_("Can't seek on position file"), 0);
9830             return FALSE;
9831         };
9832         pn = 1;
9833     } else {
9834         if (fseek(f, 0, 0) == -1) {
9835             if (f == lastLoadPositionFP ?
9836                 positionNumber == lastLoadPositionNumber + 1 :
9837                 positionNumber == 1) {
9838                 pn = 1;
9839             } else {
9840                 DisplayError(_("Can't seek on position file"), 0);
9841                 return FALSE;
9842             }
9843         }
9844     }
9845     /* See if this file is FEN or old-style xboard */
9846     if (fgets(line, MSG_SIZ, f) == NULL) {
9847         DisplayError(_("Position not found in file"), 0);
9848         return FALSE;
9849     }
9850     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9851     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9852
9853     if (pn >= 2) {
9854         if (fenMode || line[0] == '#') pn--;
9855         while (pn > 0) {
9856             /* skip positions before number pn */
9857             if (fgets(line, MSG_SIZ, f) == NULL) {
9858                 Reset(TRUE, TRUE);
9859                 DisplayError(_("Position not found in file"), 0);
9860                 return FALSE;
9861             }
9862             if (fenMode || line[0] == '#') pn--;
9863         }
9864     }
9865
9866     if (fenMode) {
9867         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9868             DisplayError(_("Bad FEN position in file"), 0);
9869             return FALSE;
9870         }
9871     } else {
9872         (void) fgets(line, MSG_SIZ, f);
9873         (void) fgets(line, MSG_SIZ, f);
9874
9875         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9876             (void) fgets(line, MSG_SIZ, f);
9877             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9878                 if (*p == ' ')
9879                   continue;
9880                 initial_position[i][j++] = CharToPiece(*p);
9881             }
9882         }
9883
9884         blackPlaysFirst = FALSE;
9885         if (!feof(f)) {
9886             (void) fgets(line, MSG_SIZ, f);
9887             if (strncmp(line, "black", strlen("black"))==0)
9888               blackPlaysFirst = TRUE;
9889         }
9890     }
9891     startedFromSetupPosition = TRUE;
9892
9893     SendToProgram("force\n", &first);
9894     CopyBoard(boards[0], initial_position);
9895     if (blackPlaysFirst) {
9896         currentMove = forwardMostMove = backwardMostMove = 1;
9897         strcpy(moveList[0], "");
9898         strcpy(parseList[0], "");
9899         CopyBoard(boards[1], initial_position);
9900         DisplayMessage("", _("Black to play"));
9901     } else {
9902         currentMove = forwardMostMove = backwardMostMove = 0;
9903         DisplayMessage("", _("White to play"));
9904     }
9905     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9906     SendBoard(&first, forwardMostMove);
9907     if (appData.debugMode) {
9908 int i, j;
9909   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9910   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9911         fprintf(debugFP, "Load Position\n");
9912     }
9913
9914     if (positionNumber > 1) {
9915         sprintf(line, "%s %d", title, positionNumber);
9916         DisplayTitle(line);
9917     } else {
9918         DisplayTitle(title);
9919     }
9920     gameMode = EditGame;
9921     ModeHighlight();
9922     ResetClocks();
9923     timeRemaining[0][1] = whiteTimeRemaining;
9924     timeRemaining[1][1] = blackTimeRemaining;
9925     DrawPosition(FALSE, boards[currentMove]);
9926
9927     return TRUE;
9928 }
9929
9930
9931 void
9932 CopyPlayerNameIntoFileName(dest, src)
9933      char **dest, *src;
9934 {
9935     while (*src != NULLCHAR && *src != ',') {
9936         if (*src == ' ') {
9937             *(*dest)++ = '_';
9938             src++;
9939         } else {
9940             *(*dest)++ = *src++;
9941         }
9942     }
9943 }
9944
9945 char *DefaultFileName(ext)
9946      char *ext;
9947 {
9948     static char def[MSG_SIZ];
9949     char *p;
9950
9951     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9952         p = def;
9953         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9954         *p++ = '-';
9955         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9956         *p++ = '.';
9957         strcpy(p, ext);
9958     } else {
9959         def[0] = NULLCHAR;
9960     }
9961     return def;
9962 }
9963
9964 /* Save the current game to the given file */
9965 int
9966 SaveGameToFile(filename, append)
9967      char *filename;
9968      int append;
9969 {
9970     FILE *f;
9971     char buf[MSG_SIZ];
9972
9973     if (strcmp(filename, "-") == 0) {
9974         return SaveGame(stdout, 0, NULL);
9975     } else {
9976         f = fopen(filename, append ? "a" : "w");
9977         if (f == NULL) {
9978             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9979             DisplayError(buf, errno);
9980             return FALSE;
9981         } else {
9982             return SaveGame(f, 0, NULL);
9983         }
9984     }
9985 }
9986
9987 char *
9988 SavePart(str)
9989      char *str;
9990 {
9991     static char buf[MSG_SIZ];
9992     char *p;
9993
9994     p = strchr(str, ' ');
9995     if (p == NULL) return str;
9996     strncpy(buf, str, p - str);
9997     buf[p - str] = NULLCHAR;
9998     return buf;
9999 }
10000
10001 #define PGN_MAX_LINE 75
10002
10003 #define PGN_SIDE_WHITE  0
10004 #define PGN_SIDE_BLACK  1
10005
10006 /* [AS] */
10007 static int FindFirstMoveOutOfBook( int side )
10008 {
10009     int result = -1;
10010
10011     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10012         int index = backwardMostMove;
10013         int has_book_hit = 0;
10014
10015         if( (index % 2) != side ) {
10016             index++;
10017         }
10018
10019         while( index < forwardMostMove ) {
10020             /* Check to see if engine is in book */
10021             int depth = pvInfoList[index].depth;
10022             int score = pvInfoList[index].score;
10023             int in_book = 0;
10024
10025             if( depth <= 2 ) {
10026                 in_book = 1;
10027             }
10028             else if( score == 0 && depth == 63 ) {
10029                 in_book = 1; /* Zappa */
10030             }
10031             else if( score == 2 && depth == 99 ) {
10032                 in_book = 1; /* Abrok */
10033             }
10034
10035             has_book_hit += in_book;
10036
10037             if( ! in_book ) {
10038                 result = index;
10039
10040                 break;
10041             }
10042
10043             index += 2;
10044         }
10045     }
10046
10047     return result;
10048 }
10049
10050 /* [AS] */
10051 void GetOutOfBookInfo( char * buf )
10052 {
10053     int oob[2];
10054     int i;
10055     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10056
10057     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10058     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10059
10060     *buf = '\0';
10061
10062     if( oob[0] >= 0 || oob[1] >= 0 ) {
10063         for( i=0; i<2; i++ ) {
10064             int idx = oob[i];
10065
10066             if( idx >= 0 ) {
10067                 if( i > 0 && oob[0] >= 0 ) {
10068                     strcat( buf, "   " );
10069                 }
10070
10071                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10072                 sprintf( buf+strlen(buf), "%s%.2f",
10073                     pvInfoList[idx].score >= 0 ? "+" : "",
10074                     pvInfoList[idx].score / 100.0 );
10075             }
10076         }
10077     }
10078 }
10079
10080 /* Save game in PGN style and close the file */
10081 int
10082 SaveGamePGN(f)
10083      FILE *f;
10084 {
10085     int i, offset, linelen, newblock;
10086     time_t tm;
10087 //    char *movetext;
10088     char numtext[32];
10089     int movelen, numlen, blank;
10090     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10091
10092     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10093
10094     tm = time((time_t *) NULL);
10095
10096     PrintPGNTags(f, &gameInfo);
10097
10098     if (backwardMostMove > 0 || startedFromSetupPosition) {
10099         char *fen = PositionToFEN(backwardMostMove, NULL);
10100         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10101         fprintf(f, "\n{--------------\n");
10102         PrintPosition(f, backwardMostMove);
10103         fprintf(f, "--------------}\n");
10104         free(fen);
10105     }
10106     else {
10107         /* [AS] Out of book annotation */
10108         if( appData.saveOutOfBookInfo ) {
10109             char buf[64];
10110
10111             GetOutOfBookInfo( buf );
10112
10113             if( buf[0] != '\0' ) {
10114                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10115             }
10116         }
10117
10118         fprintf(f, "\n");
10119     }
10120
10121     i = backwardMostMove;
10122     linelen = 0;
10123     newblock = TRUE;
10124
10125     while (i < forwardMostMove) {
10126         /* Print comments preceding this move */
10127         if (commentList[i] != NULL) {
10128             if (linelen > 0) fprintf(f, "\n");
10129             fprintf(f, "%s", commentList[i]);
10130             linelen = 0;
10131             newblock = TRUE;
10132         }
10133
10134         /* Format move number */
10135         if ((i % 2) == 0) {
10136             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10137         } else {
10138             if (newblock) {
10139                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10140             } else {
10141                 numtext[0] = NULLCHAR;
10142             }
10143         }
10144         numlen = strlen(numtext);
10145         newblock = FALSE;
10146
10147         /* Print move number */
10148         blank = linelen > 0 && numlen > 0;
10149         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10150             fprintf(f, "\n");
10151             linelen = 0;
10152             blank = 0;
10153         }
10154         if (blank) {
10155             fprintf(f, " ");
10156             linelen++;
10157         }
10158         fprintf(f, "%s", numtext);
10159         linelen += numlen;
10160
10161         /* Get move */
10162         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10163         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10164
10165         /* Print move */
10166         blank = linelen > 0 && movelen > 0;
10167         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10168             fprintf(f, "\n");
10169             linelen = 0;
10170             blank = 0;
10171         }
10172         if (blank) {
10173             fprintf(f, " ");
10174             linelen++;
10175         }
10176         fprintf(f, "%s", move_buffer);
10177         linelen += movelen;
10178
10179         /* [AS] Add PV info if present */
10180         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10181             /* [HGM] add time */
10182             char buf[MSG_SIZ]; int seconds;
10183
10184             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10185
10186             if( seconds <= 0) buf[0] = 0; else
10187             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10188                 seconds = (seconds + 4)/10; // round to full seconds
10189                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10190                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10191             }
10192
10193             sprintf( move_buffer, "{%s%.2f/%d%s}",
10194                 pvInfoList[i].score >= 0 ? "+" : "",
10195                 pvInfoList[i].score / 100.0,
10196                 pvInfoList[i].depth,
10197                 buf );
10198
10199             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10200
10201             /* Print score/depth */
10202             blank = linelen > 0 && movelen > 0;
10203             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10204                 fprintf(f, "\n");
10205                 linelen = 0;
10206                 blank = 0;
10207             }
10208             if (blank) {
10209                 fprintf(f, " ");
10210                 linelen++;
10211             }
10212             fprintf(f, "%s", move_buffer);
10213             linelen += movelen;
10214         }
10215
10216         i++;
10217     }
10218
10219     /* Start a new line */
10220     if (linelen > 0) fprintf(f, "\n");
10221
10222     /* Print comments after last move */
10223     if (commentList[i] != NULL) {
10224         fprintf(f, "%s\n", commentList[i]);
10225     }
10226
10227     /* Print result */
10228     if (gameInfo.resultDetails != NULL &&
10229         gameInfo.resultDetails[0] != NULLCHAR) {
10230         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10231                 PGNResult(gameInfo.result));
10232     } else {
10233         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10234     }
10235
10236     fclose(f);
10237     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10238     return TRUE;
10239 }
10240
10241 /* Save game in old style and close the file */
10242 int
10243 SaveGameOldStyle(f)
10244      FILE *f;
10245 {
10246     int i, offset;
10247     time_t tm;
10248
10249     tm = time((time_t *) NULL);
10250
10251     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10252     PrintOpponents(f);
10253
10254     if (backwardMostMove > 0 || startedFromSetupPosition) {
10255         fprintf(f, "\n[--------------\n");
10256         PrintPosition(f, backwardMostMove);
10257         fprintf(f, "--------------]\n");
10258     } else {
10259         fprintf(f, "\n");
10260     }
10261
10262     i = backwardMostMove;
10263     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10264
10265     while (i < forwardMostMove) {
10266         if (commentList[i] != NULL) {
10267             fprintf(f, "[%s]\n", commentList[i]);
10268         }
10269
10270         if ((i % 2) == 1) {
10271             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10272             i++;
10273         } else {
10274             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10275             i++;
10276             if (commentList[i] != NULL) {
10277                 fprintf(f, "\n");
10278                 continue;
10279             }
10280             if (i >= forwardMostMove) {
10281                 fprintf(f, "\n");
10282                 break;
10283             }
10284             fprintf(f, "%s\n", parseList[i]);
10285             i++;
10286         }
10287     }
10288
10289     if (commentList[i] != NULL) {
10290         fprintf(f, "[%s]\n", commentList[i]);
10291     }
10292
10293     /* This isn't really the old style, but it's close enough */
10294     if (gameInfo.resultDetails != NULL &&
10295         gameInfo.resultDetails[0] != NULLCHAR) {
10296         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10297                 gameInfo.resultDetails);
10298     } else {
10299         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10300     }
10301
10302     fclose(f);
10303     return TRUE;
10304 }
10305
10306 /* Save the current game to open file f and close the file */
10307 int
10308 SaveGame(f, dummy, dummy2)
10309      FILE *f;
10310      int dummy;
10311      char *dummy2;
10312 {
10313     if (gameMode == EditPosition) EditPositionDone(TRUE);
10314     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10315     if (appData.oldSaveStyle)
10316       return SaveGameOldStyle(f);
10317     else
10318       return SaveGamePGN(f);
10319 }
10320
10321 /* Save the current position to the given file */
10322 int
10323 SavePositionToFile(filename)
10324      char *filename;
10325 {
10326     FILE *f;
10327     char buf[MSG_SIZ];
10328
10329     if (strcmp(filename, "-") == 0) {
10330         return SavePosition(stdout, 0, NULL);
10331     } else {
10332         f = fopen(filename, "a");
10333         if (f == NULL) {
10334             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10335             DisplayError(buf, errno);
10336             return FALSE;
10337         } else {
10338             SavePosition(f, 0, NULL);
10339             return TRUE;
10340         }
10341     }
10342 }
10343
10344 /* Save the current position to the given open file and close the file */
10345 int
10346 SavePosition(f, dummy, dummy2)
10347      FILE *f;
10348      int dummy;
10349      char *dummy2;
10350 {
10351     time_t tm;
10352     char *fen;
10353     if (gameMode == EditPosition) EditPositionDone(TRUE);
10354     if (appData.oldSaveStyle) {
10355         tm = time((time_t *) NULL);
10356
10357         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10358         PrintOpponents(f);
10359         fprintf(f, "[--------------\n");
10360         PrintPosition(f, currentMove);
10361         fprintf(f, "--------------]\n");
10362     } else {
10363         fen = PositionToFEN(currentMove, NULL);
10364         fprintf(f, "%s\n", fen);
10365         free(fen);
10366     }
10367     fclose(f);
10368     return TRUE;
10369 }
10370
10371 void
10372 ReloadCmailMsgEvent(unregister)
10373      int unregister;
10374 {
10375 #if !WIN32
10376     static char *inFilename = NULL;
10377     static char *outFilename;
10378     int i;
10379     struct stat inbuf, outbuf;
10380     int status;
10381
10382     /* Any registered moves are unregistered if unregister is set, */
10383     /* i.e. invoked by the signal handler */
10384     if (unregister) {
10385         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10386             cmailMoveRegistered[i] = FALSE;
10387             if (cmailCommentList[i] != NULL) {
10388                 free(cmailCommentList[i]);
10389                 cmailCommentList[i] = NULL;
10390             }
10391         }
10392         nCmailMovesRegistered = 0;
10393     }
10394
10395     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10396         cmailResult[i] = CMAIL_NOT_RESULT;
10397     }
10398     nCmailResults = 0;
10399
10400     if (inFilename == NULL) {
10401         /* Because the filenames are static they only get malloced once  */
10402         /* and they never get freed                                      */
10403         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10404         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10405
10406         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10407         sprintf(outFilename, "%s.out", appData.cmailGameName);
10408     }
10409
10410     status = stat(outFilename, &outbuf);
10411     if (status < 0) {
10412         cmailMailedMove = FALSE;
10413     } else {
10414         status = stat(inFilename, &inbuf);
10415         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10416     }
10417
10418     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10419        counts the games, notes how each one terminated, etc.
10420
10421        It would be nice to remove this kludge and instead gather all
10422        the information while building the game list.  (And to keep it
10423        in the game list nodes instead of having a bunch of fixed-size
10424        parallel arrays.)  Note this will require getting each game's
10425        termination from the PGN tags, as the game list builder does
10426        not process the game moves.  --mann
10427        */
10428     cmailMsgLoaded = TRUE;
10429     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10430
10431     /* Load first game in the file or popup game menu */
10432     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10433
10434 #endif /* !WIN32 */
10435     return;
10436 }
10437
10438 int
10439 RegisterMove()
10440 {
10441     FILE *f;
10442     char string[MSG_SIZ];
10443
10444     if (   cmailMailedMove
10445         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10446         return TRUE;            /* Allow free viewing  */
10447     }
10448
10449     /* Unregister move to ensure that we don't leave RegisterMove        */
10450     /* with the move registered when the conditions for registering no   */
10451     /* longer hold                                                       */
10452     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10453         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10454         nCmailMovesRegistered --;
10455
10456         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10457           {
10458               free(cmailCommentList[lastLoadGameNumber - 1]);
10459               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10460           }
10461     }
10462
10463     if (cmailOldMove == -1) {
10464         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10465         return FALSE;
10466     }
10467
10468     if (currentMove > cmailOldMove + 1) {
10469         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10470         return FALSE;
10471     }
10472
10473     if (currentMove < cmailOldMove) {
10474         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10475         return FALSE;
10476     }
10477
10478     if (forwardMostMove > currentMove) {
10479         /* Silently truncate extra moves */
10480         TruncateGame();
10481     }
10482
10483     if (   (currentMove == cmailOldMove + 1)
10484         || (   (currentMove == cmailOldMove)
10485             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10486                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10487         if (gameInfo.result != GameUnfinished) {
10488             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10489         }
10490
10491         if (commentList[currentMove] != NULL) {
10492             cmailCommentList[lastLoadGameNumber - 1]
10493               = StrSave(commentList[currentMove]);
10494         }
10495         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10496
10497         if (appData.debugMode)
10498           fprintf(debugFP, "Saving %s for game %d\n",
10499                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10500
10501         sprintf(string,
10502                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10503
10504         f = fopen(string, "w");
10505         if (appData.oldSaveStyle) {
10506             SaveGameOldStyle(f); /* also closes the file */
10507
10508             sprintf(string, "%s.pos.out", appData.cmailGameName);
10509             f = fopen(string, "w");
10510             SavePosition(f, 0, NULL); /* also closes the file */
10511         } else {
10512             fprintf(f, "{--------------\n");
10513             PrintPosition(f, currentMove);
10514             fprintf(f, "--------------}\n\n");
10515
10516             SaveGame(f, 0, NULL); /* also closes the file*/
10517         }
10518
10519         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10520         nCmailMovesRegistered ++;
10521     } else if (nCmailGames == 1) {
10522         DisplayError(_("You have not made a move yet"), 0);
10523         return FALSE;
10524     }
10525
10526     return TRUE;
10527 }
10528
10529 void
10530 MailMoveEvent()
10531 {
10532 #if !WIN32
10533     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10534     FILE *commandOutput;
10535     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10536     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10537     int nBuffers;
10538     int i;
10539     int archived;
10540     char *arcDir;
10541
10542     if (! cmailMsgLoaded) {
10543         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10544         return;
10545     }
10546
10547     if (nCmailGames == nCmailResults) {
10548         DisplayError(_("No unfinished games"), 0);
10549         return;
10550     }
10551
10552 #if CMAIL_PROHIBIT_REMAIL
10553     if (cmailMailedMove) {
10554         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);
10555         DisplayError(msg, 0);
10556         return;
10557     }
10558 #endif
10559
10560     if (! (cmailMailedMove || RegisterMove())) return;
10561
10562     if (   cmailMailedMove
10563         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10564         sprintf(string, partCommandString,
10565                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10566         commandOutput = popen(string, "r");
10567
10568         if (commandOutput == NULL) {
10569             DisplayError(_("Failed to invoke cmail"), 0);
10570         } else {
10571             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10572                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10573             }
10574             if (nBuffers > 1) {
10575                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10576                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10577                 nBytes = MSG_SIZ - 1;
10578             } else {
10579                 (void) memcpy(msg, buffer, nBytes);
10580             }
10581             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10582
10583             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10584                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10585
10586                 archived = TRUE;
10587                 for (i = 0; i < nCmailGames; i ++) {
10588                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10589                         archived = FALSE;
10590                     }
10591                 }
10592                 if (   archived
10593                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10594                         != NULL)) {
10595                     sprintf(buffer, "%s/%s.%s.archive",
10596                             arcDir,
10597                             appData.cmailGameName,
10598                             gameInfo.date);
10599                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10600                     cmailMsgLoaded = FALSE;
10601                 }
10602             }
10603
10604             DisplayInformation(msg);
10605             pclose(commandOutput);
10606         }
10607     } else {
10608         if ((*cmailMsg) != '\0') {
10609             DisplayInformation(cmailMsg);
10610         }
10611     }
10612
10613     return;
10614 #endif /* !WIN32 */
10615 }
10616
10617 char *
10618 CmailMsg()
10619 {
10620 #if WIN32
10621     return NULL;
10622 #else
10623     int  prependComma = 0;
10624     char number[5];
10625     char string[MSG_SIZ];       /* Space for game-list */
10626     int  i;
10627
10628     if (!cmailMsgLoaded) return "";
10629
10630     if (cmailMailedMove) {
10631         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10632     } else {
10633         /* Create a list of games left */
10634         sprintf(string, "[");
10635         for (i = 0; i < nCmailGames; i ++) {
10636             if (! (   cmailMoveRegistered[i]
10637                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10638                 if (prependComma) {
10639                     sprintf(number, ",%d", i + 1);
10640                 } else {
10641                     sprintf(number, "%d", i + 1);
10642                     prependComma = 1;
10643                 }
10644
10645                 strcat(string, number);
10646             }
10647         }
10648         strcat(string, "]");
10649
10650         if (nCmailMovesRegistered + nCmailResults == 0) {
10651             switch (nCmailGames) {
10652               case 1:
10653                 sprintf(cmailMsg,
10654                         _("Still need to make move for game\n"));
10655                 break;
10656
10657               case 2:
10658                 sprintf(cmailMsg,
10659                         _("Still need to make moves for both games\n"));
10660                 break;
10661
10662               default:
10663                 sprintf(cmailMsg,
10664                         _("Still need to make moves for all %d games\n"),
10665                         nCmailGames);
10666                 break;
10667             }
10668         } else {
10669             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10670               case 1:
10671                 sprintf(cmailMsg,
10672                         _("Still need to make a move for game %s\n"),
10673                         string);
10674                 break;
10675
10676               case 0:
10677                 if (nCmailResults == nCmailGames) {
10678                     sprintf(cmailMsg, _("No unfinished games\n"));
10679                 } else {
10680                     sprintf(cmailMsg, _("Ready to send mail\n"));
10681                 }
10682                 break;
10683
10684               default:
10685                 sprintf(cmailMsg,
10686                         _("Still need to make moves for games %s\n"),
10687                         string);
10688             }
10689         }
10690     }
10691     return cmailMsg;
10692 #endif /* WIN32 */
10693 }
10694
10695 void
10696 ResetGameEvent()
10697 {
10698     if (gameMode == Training)
10699       SetTrainingModeOff();
10700
10701     Reset(TRUE, TRUE);
10702     cmailMsgLoaded = FALSE;
10703     if (appData.icsActive) {
10704       SendToICS(ics_prefix);
10705       SendToICS("refresh\n");
10706     }
10707 }
10708
10709 void
10710 ExitEvent(status)
10711      int status;
10712 {
10713     exiting++;
10714     if (exiting > 2) {
10715       /* Give up on clean exit */
10716       exit(status);
10717     }
10718     if (exiting > 1) {
10719       /* Keep trying for clean exit */
10720       return;
10721     }
10722
10723     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10724
10725     if (telnetISR != NULL) {
10726       RemoveInputSource(telnetISR);
10727     }
10728     if (icsPR != NoProc) {
10729       DestroyChildProcess(icsPR, TRUE);
10730     }
10731
10732     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10733     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10734
10735     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10736     /* make sure this other one finishes before killing it!                  */
10737     if(endingGame) { int count = 0;
10738         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10739         while(endingGame && count++ < 10) DoSleep(1);
10740         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10741     }
10742
10743     /* Kill off chess programs */
10744     if (first.pr != NoProc) {
10745         ExitAnalyzeMode();
10746
10747         DoSleep( appData.delayBeforeQuit );
10748         SendToProgram("quit\n", &first);
10749         DoSleep( appData.delayAfterQuit );
10750         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10751     }
10752     if (second.pr != NoProc) {
10753         DoSleep( appData.delayBeforeQuit );
10754         SendToProgram("quit\n", &second);
10755         DoSleep( appData.delayAfterQuit );
10756         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10757     }
10758     if (first.isr != NULL) {
10759         RemoveInputSource(first.isr);
10760     }
10761     if (second.isr != NULL) {
10762         RemoveInputSource(second.isr);
10763     }
10764
10765     ShutDownFrontEnd();
10766     exit(status);
10767 }
10768
10769 void
10770 PauseEvent()
10771 {
10772     if (appData.debugMode)
10773         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10774     if (pausing) {
10775         pausing = FALSE;
10776         ModeHighlight();
10777         if (gameMode == MachinePlaysWhite ||
10778             gameMode == MachinePlaysBlack) {
10779             StartClocks();
10780         } else {
10781             DisplayBothClocks();
10782         }
10783         if (gameMode == PlayFromGameFile) {
10784             if (appData.timeDelay >= 0)
10785                 AutoPlayGameLoop();
10786         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10787             Reset(FALSE, TRUE);
10788             SendToICS(ics_prefix);
10789             SendToICS("refresh\n");
10790         } else if (currentMove < forwardMostMove) {
10791             ForwardInner(forwardMostMove);
10792         }
10793         pauseExamInvalid = FALSE;
10794     } else {
10795         switch (gameMode) {
10796           default:
10797             return;
10798           case IcsExamining:
10799             pauseExamForwardMostMove = forwardMostMove;
10800             pauseExamInvalid = FALSE;
10801             /* fall through */
10802           case IcsObserving:
10803           case IcsPlayingWhite:
10804           case IcsPlayingBlack:
10805             pausing = TRUE;
10806             ModeHighlight();
10807             return;
10808           case PlayFromGameFile:
10809             (void) StopLoadGameTimer();
10810             pausing = TRUE;
10811             ModeHighlight();
10812             break;
10813           case BeginningOfGame:
10814             if (appData.icsActive) return;
10815             /* else fall through */
10816           case MachinePlaysWhite:
10817           case MachinePlaysBlack:
10818           case TwoMachinesPlay:
10819             if (forwardMostMove == 0)
10820               return;           /* don't pause if no one has moved */
10821             if ((gameMode == MachinePlaysWhite &&
10822                  !WhiteOnMove(forwardMostMove)) ||
10823                 (gameMode == MachinePlaysBlack &&
10824                  WhiteOnMove(forwardMostMove))) {
10825                 StopClocks();
10826             }
10827             pausing = TRUE;
10828             ModeHighlight();
10829             break;
10830         }
10831     }
10832 }
10833
10834 void
10835 EditCommentEvent()
10836 {
10837     char title[MSG_SIZ];
10838
10839     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10840         strcpy(title, _("Edit comment"));
10841     } else {
10842         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10843                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10844                 parseList[currentMove - 1]);
10845     }
10846
10847     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10848 }
10849
10850
10851 void
10852 EditTagsEvent()
10853 {
10854     char *tags = PGNTags(&gameInfo);
10855     EditTagsPopUp(tags);
10856     free(tags);
10857 }
10858
10859 void
10860 AnalyzeModeEvent()
10861 {
10862     if (appData.noChessProgram || gameMode == AnalyzeMode)
10863       return;
10864
10865     if (gameMode != AnalyzeFile) {
10866         if (!appData.icsEngineAnalyze) {
10867                EditGameEvent();
10868                if (gameMode != EditGame) return;
10869         }
10870         ResurrectChessProgram();
10871         SendToProgram("analyze\n", &first);
10872         first.analyzing = TRUE;
10873         /*first.maybeThinking = TRUE;*/
10874         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10875         EngineOutputPopUp();
10876     }
10877     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10878     pausing = FALSE;
10879     ModeHighlight();
10880     SetGameInfo();
10881
10882     StartAnalysisClock();
10883     GetTimeMark(&lastNodeCountTime);
10884     lastNodeCount = 0;
10885 }
10886
10887 void
10888 AnalyzeFileEvent()
10889 {
10890     if (appData.noChessProgram || gameMode == AnalyzeFile)
10891       return;
10892
10893     if (gameMode != AnalyzeMode) {
10894         EditGameEvent();
10895         if (gameMode != EditGame) return;
10896         ResurrectChessProgram();
10897         SendToProgram("analyze\n", &first);
10898         first.analyzing = TRUE;
10899         /*first.maybeThinking = TRUE;*/
10900         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10901         EngineOutputPopUp();
10902     }
10903     gameMode = AnalyzeFile;
10904     pausing = FALSE;
10905     ModeHighlight();
10906     SetGameInfo();
10907
10908     StartAnalysisClock();
10909     GetTimeMark(&lastNodeCountTime);
10910     lastNodeCount = 0;
10911 }
10912
10913 void
10914 MachineWhiteEvent()
10915 {
10916     char buf[MSG_SIZ];
10917     char *bookHit = NULL;
10918
10919     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10920       return;
10921
10922
10923     if (gameMode == PlayFromGameFile ||
10924         gameMode == TwoMachinesPlay  ||
10925         gameMode == Training         ||
10926         gameMode == AnalyzeMode      ||
10927         gameMode == EndOfGame)
10928         EditGameEvent();
10929
10930     if (gameMode == EditPosition) 
10931         EditPositionDone(TRUE);
10932
10933     if (!WhiteOnMove(currentMove)) {
10934         DisplayError(_("It is not White's turn"), 0);
10935         return;
10936     }
10937
10938     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10939       ExitAnalyzeMode();
10940
10941     if (gameMode == EditGame || gameMode == AnalyzeMode ||
10942         gameMode == AnalyzeFile)
10943         TruncateGame();
10944
10945     ResurrectChessProgram();    /* in case it isn't running */
10946     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10947         gameMode = MachinePlaysWhite;
10948         ResetClocks();
10949     } else
10950     gameMode = MachinePlaysWhite;
10951     pausing = FALSE;
10952     ModeHighlight();
10953     SetGameInfo();
10954     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10955     DisplayTitle(buf);
10956     if (first.sendName) {
10957       sprintf(buf, "name %s\n", gameInfo.black);
10958       SendToProgram(buf, &first);
10959     }
10960     if (first.sendTime) {
10961       if (first.useColors) {
10962         SendToProgram("black\n", &first); /*gnu kludge*/
10963       }
10964       SendTimeRemaining(&first, TRUE);
10965     }
10966     if (first.useColors) {
10967       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10968     }
10969     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10970     SetMachineThinkingEnables();
10971     first.maybeThinking = TRUE;
10972     StartClocks();
10973     firstMove = FALSE;
10974
10975     if (appData.autoFlipView && !flipView) {
10976       flipView = !flipView;
10977       DrawPosition(FALSE, NULL);
10978       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10979     }
10980
10981     if(bookHit) { // [HGM] book: simulate book reply
10982         static char bookMove[MSG_SIZ]; // a bit generous?
10983
10984         programStats.nodes = programStats.depth = programStats.time =
10985         programStats.score = programStats.got_only_move = 0;
10986         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10987
10988         strcpy(bookMove, "move ");
10989         strcat(bookMove, bookHit);
10990         HandleMachineMove(bookMove, &first);
10991     }
10992 }
10993
10994 void
10995 MachineBlackEvent()
10996 {
10997     char buf[MSG_SIZ];
10998    char *bookHit = NULL;
10999
11000     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11001         return;
11002
11003
11004     if (gameMode == PlayFromGameFile ||
11005         gameMode == TwoMachinesPlay  ||
11006         gameMode == Training         ||
11007         gameMode == AnalyzeMode      ||
11008         gameMode == EndOfGame)
11009         EditGameEvent();
11010
11011     if (gameMode == EditPosition) 
11012         EditPositionDone(TRUE);
11013
11014     if (WhiteOnMove(currentMove)) {
11015         DisplayError(_("It is not Black's turn"), 0);
11016         return;
11017     }
11018
11019     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11020       ExitAnalyzeMode();
11021
11022     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11023         gameMode == AnalyzeFile)
11024         TruncateGame();
11025
11026     ResurrectChessProgram();    /* in case it isn't running */
11027     gameMode = MachinePlaysBlack;
11028     pausing = FALSE;
11029     ModeHighlight();
11030     SetGameInfo();
11031     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11032     DisplayTitle(buf);
11033     if (first.sendName) {
11034       sprintf(buf, "name %s\n", gameInfo.white);
11035       SendToProgram(buf, &first);
11036     }
11037     if (first.sendTime) {
11038       if (first.useColors) {
11039         SendToProgram("white\n", &first); /*gnu kludge*/
11040       }
11041       SendTimeRemaining(&first, FALSE);
11042     }
11043     if (first.useColors) {
11044       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11045     }
11046     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11047     SetMachineThinkingEnables();
11048     first.maybeThinking = TRUE;
11049     StartClocks();
11050
11051     if (appData.autoFlipView && flipView) {
11052       flipView = !flipView;
11053       DrawPosition(FALSE, NULL);
11054       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11055     }
11056     if(bookHit) { // [HGM] book: simulate book reply
11057         static char bookMove[MSG_SIZ]; // a bit generous?
11058
11059         programStats.nodes = programStats.depth = programStats.time =
11060         programStats.score = programStats.got_only_move = 0;
11061         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11062
11063         strcpy(bookMove, "move ");
11064         strcat(bookMove, bookHit);
11065         HandleMachineMove(bookMove, &first);
11066     }
11067 }
11068
11069
11070 void
11071 DisplayTwoMachinesTitle()
11072 {
11073     char buf[MSG_SIZ];
11074     if (appData.matchGames > 0) {
11075         if (first.twoMachinesColor[0] == 'w') {
11076             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11077                     gameInfo.white, gameInfo.black,
11078                     first.matchWins, second.matchWins,
11079                     matchGame - 1 - (first.matchWins + second.matchWins));
11080         } else {
11081             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11082                     gameInfo.white, gameInfo.black,
11083                     second.matchWins, first.matchWins,
11084                     matchGame - 1 - (first.matchWins + second.matchWins));
11085         }
11086     } else {
11087         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11088     }
11089     DisplayTitle(buf);
11090 }
11091
11092 void
11093 TwoMachinesEvent P((void))
11094 {
11095     int i;
11096     char buf[MSG_SIZ];
11097     ChessProgramState *onmove;
11098     char *bookHit = NULL;
11099
11100     if (appData.noChessProgram) return;
11101
11102     switch (gameMode) {
11103       case TwoMachinesPlay:
11104         return;
11105       case MachinePlaysWhite:
11106       case MachinePlaysBlack:
11107         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11108             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11109             return;
11110         }
11111         /* fall through */
11112       case BeginningOfGame:
11113       case PlayFromGameFile:
11114       case EndOfGame:
11115         EditGameEvent();
11116         if (gameMode != EditGame) return;
11117         break;
11118       case EditPosition:
11119         EditPositionDone(TRUE);
11120         break;
11121       case AnalyzeMode:
11122       case AnalyzeFile:
11123         ExitAnalyzeMode();
11124         break;
11125       case EditGame:
11126       default:
11127         break;
11128     }
11129
11130 //    forwardMostMove = currentMove;
11131     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11132     ResurrectChessProgram();    /* in case first program isn't running */
11133
11134     if (second.pr == NULL) {
11135         StartChessProgram(&second);
11136         if (second.protocolVersion == 1) {
11137           TwoMachinesEventIfReady();
11138         } else {
11139           /* kludge: allow timeout for initial "feature" command */
11140           FreezeUI();
11141           DisplayMessage("", _("Starting second chess program"));
11142           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11143         }
11144         return;
11145     }
11146     DisplayMessage("", "");
11147     InitChessProgram(&second, FALSE);
11148     SendToProgram("force\n", &second);
11149     if (startedFromSetupPosition) {
11150         SendBoard(&second, backwardMostMove);
11151     if (appData.debugMode) {
11152         fprintf(debugFP, "Two Machines\n");
11153     }
11154     }
11155     for (i = backwardMostMove; i < forwardMostMove; i++) {
11156         SendMoveToProgram(i, &second);
11157     }
11158
11159     gameMode = TwoMachinesPlay;
11160     pausing = FALSE;
11161     ModeHighlight();
11162     SetGameInfo();
11163     DisplayTwoMachinesTitle();
11164     firstMove = TRUE;
11165     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11166         onmove = &first;
11167     } else {
11168         onmove = &second;
11169     }
11170
11171     SendToProgram(first.computerString, &first);
11172     if (first.sendName) {
11173       sprintf(buf, "name %s\n", second.tidy);
11174       SendToProgram(buf, &first);
11175     }
11176     SendToProgram(second.computerString, &second);
11177     if (second.sendName) {
11178       sprintf(buf, "name %s\n", first.tidy);
11179       SendToProgram(buf, &second);
11180     }
11181
11182     ResetClocks();
11183     if (!first.sendTime || !second.sendTime) {
11184         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11185         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11186     }
11187     if (onmove->sendTime) {
11188       if (onmove->useColors) {
11189         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11190       }
11191       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11192     }
11193     if (onmove->useColors) {
11194       SendToProgram(onmove->twoMachinesColor, onmove);
11195     }
11196     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11197 //    SendToProgram("go\n", onmove);
11198     onmove->maybeThinking = TRUE;
11199     SetMachineThinkingEnables();
11200
11201     StartClocks();
11202
11203     if(bookHit) { // [HGM] book: simulate book reply
11204         static char bookMove[MSG_SIZ]; // a bit generous?
11205
11206         programStats.nodes = programStats.depth = programStats.time =
11207         programStats.score = programStats.got_only_move = 0;
11208         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11209
11210         strcpy(bookMove, "move ");
11211         strcat(bookMove, bookHit);
11212         savedMessage = bookMove; // args for deferred call
11213         savedState = onmove;
11214         ScheduleDelayedEvent(DeferredBookMove, 1);
11215     }
11216 }
11217
11218 void
11219 TrainingEvent()
11220 {
11221     if (gameMode == Training) {
11222       SetTrainingModeOff();
11223       gameMode = PlayFromGameFile;
11224       DisplayMessage("", _("Training mode off"));
11225     } else {
11226       gameMode = Training;
11227       animateTraining = appData.animate;
11228
11229       /* make sure we are not already at the end of the game */
11230       if (currentMove < forwardMostMove) {
11231         SetTrainingModeOn();
11232         DisplayMessage("", _("Training mode on"));
11233       } else {
11234         gameMode = PlayFromGameFile;
11235         DisplayError(_("Already at end of game"), 0);
11236       }
11237     }
11238     ModeHighlight();
11239 }
11240
11241 void
11242 IcsClientEvent()
11243 {
11244     if (!appData.icsActive) return;
11245     switch (gameMode) {
11246       case IcsPlayingWhite:
11247       case IcsPlayingBlack:
11248       case IcsObserving:
11249       case IcsIdle:
11250       case BeginningOfGame:
11251       case IcsExamining:
11252         return;
11253
11254       case EditGame:
11255         break;
11256
11257       case EditPosition:
11258         EditPositionDone(TRUE);
11259         break;
11260
11261       case AnalyzeMode:
11262       case AnalyzeFile:
11263         ExitAnalyzeMode();
11264         break;
11265
11266       default:
11267         EditGameEvent();
11268         break;
11269     }
11270
11271     gameMode = IcsIdle;
11272     ModeHighlight();
11273     return;
11274 }
11275
11276
11277 void
11278 EditGameEvent()
11279 {
11280     int i;
11281
11282     switch (gameMode) {
11283       case Training:
11284         SetTrainingModeOff();
11285         break;
11286       case MachinePlaysWhite:
11287       case MachinePlaysBlack:
11288       case BeginningOfGame:
11289         SendToProgram("force\n", &first);
11290         SetUserThinkingEnables();
11291         break;
11292       case PlayFromGameFile:
11293         (void) StopLoadGameTimer();
11294         if (gameFileFP != NULL) {
11295             gameFileFP = NULL;
11296         }
11297         break;
11298       case EditPosition:
11299         EditPositionDone(TRUE);
11300         break;
11301       case AnalyzeMode:
11302       case AnalyzeFile:
11303         ExitAnalyzeMode();
11304         SendToProgram("force\n", &first);
11305         break;
11306       case TwoMachinesPlay:
11307         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11308         ResurrectChessProgram();
11309         SetUserThinkingEnables();
11310         break;
11311       case EndOfGame:
11312         ResurrectChessProgram();
11313         break;
11314       case IcsPlayingBlack:
11315       case IcsPlayingWhite:
11316         DisplayError(_("Warning: You are still playing a game"), 0);
11317         break;
11318       case IcsObserving:
11319         DisplayError(_("Warning: You are still observing a game"), 0);
11320         break;
11321       case IcsExamining:
11322         DisplayError(_("Warning: You are still examining a game"), 0);
11323         break;
11324       case IcsIdle:
11325         break;
11326       case EditGame:
11327       default:
11328         return;
11329     }
11330
11331     pausing = FALSE;
11332     StopClocks();
11333     first.offeredDraw = second.offeredDraw = 0;
11334
11335     if (gameMode == PlayFromGameFile) {
11336         whiteTimeRemaining = timeRemaining[0][currentMove];
11337         blackTimeRemaining = timeRemaining[1][currentMove];
11338         DisplayTitle("");
11339     }
11340
11341     if (gameMode == MachinePlaysWhite ||
11342         gameMode == MachinePlaysBlack ||
11343         gameMode == TwoMachinesPlay ||
11344         gameMode == EndOfGame) {
11345         i = forwardMostMove;
11346         while (i > currentMove) {
11347             SendToProgram("undo\n", &first);
11348             i--;
11349         }
11350         whiteTimeRemaining = timeRemaining[0][currentMove];
11351         blackTimeRemaining = timeRemaining[1][currentMove];
11352         DisplayBothClocks();
11353         if (whiteFlag || blackFlag) {
11354             whiteFlag = blackFlag = 0;
11355         }
11356         DisplayTitle("");
11357     }
11358
11359     gameMode = EditGame;
11360     ModeHighlight();
11361     SetGameInfo();
11362 }
11363
11364
11365 void
11366 EditPositionEvent()
11367 {
11368     if (gameMode == EditPosition) {
11369         EditGameEvent();
11370         return;
11371     }
11372
11373     EditGameEvent();
11374     if (gameMode != EditGame) return;
11375
11376     gameMode = EditPosition;
11377     ModeHighlight();
11378     SetGameInfo();
11379     if (currentMove > 0)
11380       CopyBoard(boards[0], boards[currentMove]);
11381
11382     blackPlaysFirst = !WhiteOnMove(currentMove);
11383     ResetClocks();
11384     currentMove = forwardMostMove = backwardMostMove = 0;
11385     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11386     DisplayMove(-1);
11387 }
11388
11389 void
11390 ExitAnalyzeMode()
11391 {
11392     /* [DM] icsEngineAnalyze - possible call from other functions */
11393     if (appData.icsEngineAnalyze) {
11394         appData.icsEngineAnalyze = FALSE;
11395
11396         DisplayMessage("",_("Close ICS engine analyze..."));
11397     }
11398     if (first.analysisSupport && first.analyzing) {
11399       SendToProgram("exit\n", &first);
11400       first.analyzing = FALSE;
11401     }
11402     thinkOutput[0] = NULLCHAR;
11403 }
11404
11405 void
11406 EditPositionDone(Boolean fakeRights)
11407 {
11408     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11409
11410     startedFromSetupPosition = TRUE;
11411     InitChessProgram(&first, FALSE);
11412     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11413       boards[0][EP_STATUS] = EP_NONE;
11414       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11415     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11416         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11417         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11418       } else boards[0][CASTLING][2] = NoRights;
11419     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11420         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11421         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11422       } else boards[0][CASTLING][5] = NoRights;
11423     }
11424     SendToProgram("force\n", &first);
11425     if (blackPlaysFirst) {
11426         strcpy(moveList[0], "");
11427         strcpy(parseList[0], "");
11428         currentMove = forwardMostMove = backwardMostMove = 1;
11429         CopyBoard(boards[1], boards[0]);
11430     } else {
11431         currentMove = forwardMostMove = backwardMostMove = 0;
11432     }
11433     SendBoard(&first, forwardMostMove);
11434     if (appData.debugMode) {
11435         fprintf(debugFP, "EditPosDone\n");
11436     }
11437     DisplayTitle("");
11438     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11439     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11440     gameMode = EditGame;
11441     ModeHighlight();
11442     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11443     ClearHighlights(); /* [AS] */
11444 }
11445
11446 /* Pause for `ms' milliseconds */
11447 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11448 void
11449 TimeDelay(ms)
11450      long ms;
11451 {
11452     TimeMark m1, m2;
11453
11454     GetTimeMark(&m1);
11455     do {
11456         GetTimeMark(&m2);
11457     } while (SubtractTimeMarks(&m2, &m1) < ms);
11458 }
11459
11460 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11461 void
11462 SendMultiLineToICS(buf)
11463      char *buf;
11464 {
11465     char temp[MSG_SIZ+1], *p;
11466     int len;
11467
11468     len = strlen(buf);
11469     if (len > MSG_SIZ)
11470       len = MSG_SIZ;
11471
11472     strncpy(temp, buf, len);
11473     temp[len] = 0;
11474
11475     p = temp;
11476     while (*p) {
11477         if (*p == '\n' || *p == '\r')
11478           *p = ' ';
11479         ++p;
11480     }
11481
11482     strcat(temp, "\n");
11483     SendToICS(temp);
11484     SendToPlayer(temp, strlen(temp));
11485 }
11486
11487 void
11488 SetWhiteToPlayEvent()
11489 {
11490     if (gameMode == EditPosition) {
11491         blackPlaysFirst = FALSE;
11492         DisplayBothClocks();    /* works because currentMove is 0 */
11493     } else if (gameMode == IcsExamining) {
11494         SendToICS(ics_prefix);
11495         SendToICS("tomove white\n");
11496     }
11497 }
11498
11499 void
11500 SetBlackToPlayEvent()
11501 {
11502     if (gameMode == EditPosition) {
11503         blackPlaysFirst = TRUE;
11504         currentMove = 1;        /* kludge */
11505         DisplayBothClocks();
11506         currentMove = 0;
11507     } else if (gameMode == IcsExamining) {
11508         SendToICS(ics_prefix);
11509         SendToICS("tomove black\n");
11510     }
11511 }
11512
11513 void
11514 EditPositionMenuEvent(selection, x, y)
11515      ChessSquare selection;
11516      int x, y;
11517 {
11518     char buf[MSG_SIZ];
11519     ChessSquare piece = boards[0][y][x];
11520
11521     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11522
11523     switch (selection) {
11524       case ClearBoard:
11525         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11526             SendToICS(ics_prefix);
11527             SendToICS("bsetup clear\n");
11528         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11529             SendToICS(ics_prefix);
11530             SendToICS("clearboard\n");
11531         } else {
11532             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11533                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11534                 for (y = 0; y < BOARD_HEIGHT; y++) {
11535                     if (gameMode == IcsExamining) {
11536                         if (boards[currentMove][y][x] != EmptySquare) {
11537                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11538                                     AAA + x, ONE + y);
11539                             SendToICS(buf);
11540                         }
11541                     } else {
11542                         boards[0][y][x] = p;
11543                     }
11544                 }
11545             }
11546         }
11547         if (gameMode == EditPosition) {
11548             DrawPosition(FALSE, boards[0]);
11549         }
11550         break;
11551
11552       case WhitePlay:
11553         SetWhiteToPlayEvent();
11554         break;
11555
11556       case BlackPlay:
11557         SetBlackToPlayEvent();
11558         break;
11559
11560       case EmptySquare:
11561         if (gameMode == IcsExamining) {
11562             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11563             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11564             SendToICS(buf);
11565         } else {
11566             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11567                 if(x == BOARD_LEFT-2) {
11568                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11569                     boards[0][y][1] = 0;
11570                 } else
11571                 if(x == BOARD_RGHT+1) {
11572                     if(y >= gameInfo.holdingsSize) break;
11573                     boards[0][y][BOARD_WIDTH-2] = 0;
11574                 } else break;
11575             }
11576             boards[0][y][x] = EmptySquare;
11577             DrawPosition(FALSE, boards[0]);
11578         }
11579         break;
11580
11581       case PromotePiece:
11582         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11583            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11584             selection = (ChessSquare) (PROMOTED piece);
11585         } else if(piece == EmptySquare) selection = WhiteSilver;
11586         else selection = (ChessSquare)((int)piece - 1);
11587         goto defaultlabel;
11588
11589       case DemotePiece:
11590         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11591            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11592             selection = (ChessSquare) (DEMOTED piece);
11593         } else if(piece == EmptySquare) selection = BlackSilver;
11594         else selection = (ChessSquare)((int)piece + 1);
11595         goto defaultlabel;
11596
11597       case WhiteQueen:
11598       case BlackQueen:
11599         if(gameInfo.variant == VariantShatranj ||
11600            gameInfo.variant == VariantXiangqi  ||
11601            gameInfo.variant == VariantCourier    )
11602             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11603         goto defaultlabel;
11604
11605       case WhiteKing:
11606       case BlackKing:
11607         if(gameInfo.variant == VariantXiangqi)
11608             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11609         if(gameInfo.variant == VariantKnightmate)
11610             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11611       default:
11612         defaultlabel:
11613         if (gameMode == IcsExamining) {
11614             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11615             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11616                     PieceToChar(selection), AAA + x, ONE + y);
11617             SendToICS(buf);
11618         } else {
11619             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11620                 int n;
11621                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11622                     n = PieceToNumber(selection - BlackPawn);
11623                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11624                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11625                     boards[0][BOARD_HEIGHT-1-n][1]++;
11626                 } else
11627                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11628                     n = PieceToNumber(selection);
11629                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11630                     boards[0][n][BOARD_WIDTH-1] = selection;
11631                     boards[0][n][BOARD_WIDTH-2]++;
11632                 }
11633             } else
11634             boards[0][y][x] = selection;
11635             DrawPosition(TRUE, boards[0]);
11636         }
11637         break;
11638     }
11639 }
11640
11641
11642 void
11643 DropMenuEvent(selection, x, y)
11644      ChessSquare selection;
11645      int x, y;
11646 {
11647     ChessMove moveType;
11648
11649     switch (gameMode) {
11650       case IcsPlayingWhite:
11651       case MachinePlaysBlack:
11652         if (!WhiteOnMove(currentMove)) {
11653             DisplayMoveError(_("It is Black's turn"));
11654             return;
11655         }
11656         moveType = WhiteDrop;
11657         break;
11658       case IcsPlayingBlack:
11659       case MachinePlaysWhite:
11660         if (WhiteOnMove(currentMove)) {
11661             DisplayMoveError(_("It is White's turn"));
11662             return;
11663         }
11664         moveType = BlackDrop;
11665         break;
11666       case EditGame:
11667         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11668         break;
11669       default:
11670         return;
11671     }
11672
11673     if (moveType == BlackDrop && selection < BlackPawn) {
11674       selection = (ChessSquare) ((int) selection
11675                                  + (int) BlackPawn - (int) WhitePawn);
11676     }
11677     if (boards[currentMove][y][x] != EmptySquare) {
11678         DisplayMoveError(_("That square is occupied"));
11679         return;
11680     }
11681
11682     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11683 }
11684
11685 void
11686 AcceptEvent()
11687 {
11688     /* Accept a pending offer of any kind from opponent */
11689
11690     if (appData.icsActive) {
11691         SendToICS(ics_prefix);
11692         SendToICS("accept\n");
11693     } else if (cmailMsgLoaded) {
11694         if (currentMove == cmailOldMove &&
11695             commentList[cmailOldMove] != NULL &&
11696             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11697                    "Black offers a draw" : "White offers a draw")) {
11698             TruncateGame();
11699             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11700             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11701         } else {
11702             DisplayError(_("There is no pending offer on this move"), 0);
11703             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11704         }
11705     } else {
11706         /* Not used for offers from chess program */
11707     }
11708 }
11709
11710 void
11711 DeclineEvent()
11712 {
11713     /* Decline a pending offer of any kind from opponent */
11714
11715     if (appData.icsActive) {
11716         SendToICS(ics_prefix);
11717         SendToICS("decline\n");
11718     } else if (cmailMsgLoaded) {
11719         if (currentMove == cmailOldMove &&
11720             commentList[cmailOldMove] != NULL &&
11721             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11722                    "Black offers a draw" : "White offers a draw")) {
11723 #ifdef NOTDEF
11724             AppendComment(cmailOldMove, "Draw declined", TRUE);
11725             DisplayComment(cmailOldMove - 1, "Draw declined");
11726 #endif /*NOTDEF*/
11727         } else {
11728             DisplayError(_("There is no pending offer on this move"), 0);
11729         }
11730     } else {
11731         /* Not used for offers from chess program */
11732     }
11733 }
11734
11735 void
11736 RematchEvent()
11737 {
11738     /* Issue ICS rematch command */
11739     if (appData.icsActive) {
11740         SendToICS(ics_prefix);
11741         SendToICS("rematch\n");
11742     }
11743 }
11744
11745 void
11746 CallFlagEvent()
11747 {
11748     /* Call your opponent's flag (claim a win on time) */
11749     if (appData.icsActive) {
11750         SendToICS(ics_prefix);
11751         SendToICS("flag\n");
11752     } else {
11753         switch (gameMode) {
11754           default:
11755             return;
11756           case MachinePlaysWhite:
11757             if (whiteFlag) {
11758                 if (blackFlag)
11759                   GameEnds(GameIsDrawn, "Both players ran out of time",
11760                            GE_PLAYER);
11761                 else
11762                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11763             } else {
11764                 DisplayError(_("Your opponent is not out of time"), 0);
11765             }
11766             break;
11767           case MachinePlaysBlack:
11768             if (blackFlag) {
11769                 if (whiteFlag)
11770                   GameEnds(GameIsDrawn, "Both players ran out of time",
11771                            GE_PLAYER);
11772                 else
11773                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11774             } else {
11775                 DisplayError(_("Your opponent is not out of time"), 0);
11776             }
11777             break;
11778         }
11779     }
11780 }
11781
11782 void
11783 DrawEvent()
11784 {
11785     /* Offer draw or accept pending draw offer from opponent */
11786
11787     if (appData.icsActive) {
11788         /* Note: tournament rules require draw offers to be
11789            made after you make your move but before you punch
11790            your clock.  Currently ICS doesn't let you do that;
11791            instead, you immediately punch your clock after making
11792            a move, but you can offer a draw at any time. */
11793
11794         SendToICS(ics_prefix);
11795         SendToICS("draw\n");
11796     } else if (cmailMsgLoaded) {
11797         if (currentMove == cmailOldMove &&
11798             commentList[cmailOldMove] != NULL &&
11799             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11800                    "Black offers a draw" : "White offers a draw")) {
11801             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11802             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11803         } else if (currentMove == cmailOldMove + 1) {
11804             char *offer = WhiteOnMove(cmailOldMove) ?
11805               "White offers a draw" : "Black offers a draw";
11806             AppendComment(currentMove, offer, TRUE);
11807             DisplayComment(currentMove - 1, offer);
11808             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11809         } else {
11810             DisplayError(_("You must make your move before offering a draw"), 0);
11811             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11812         }
11813     } else if (first.offeredDraw) {
11814         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11815     } else {
11816         if (first.sendDrawOffers) {
11817             SendToProgram("draw\n", &first);
11818             userOfferedDraw = TRUE;
11819         }
11820     }
11821 }
11822
11823 void
11824 AdjournEvent()
11825 {
11826     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11827
11828     if (appData.icsActive) {
11829         SendToICS(ics_prefix);
11830         SendToICS("adjourn\n");
11831     } else {
11832         /* Currently GNU Chess doesn't offer or accept Adjourns */
11833     }
11834 }
11835
11836
11837 void
11838 AbortEvent()
11839 {
11840     /* Offer Abort or accept pending Abort offer from opponent */
11841
11842     if (appData.icsActive) {
11843         SendToICS(ics_prefix);
11844         SendToICS("abort\n");
11845     } else {
11846         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11847     }
11848 }
11849
11850 void
11851 ResignEvent()
11852 {
11853     /* Resign.  You can do this even if it's not your turn. */
11854
11855     if (appData.icsActive) {
11856         SendToICS(ics_prefix);
11857         SendToICS("resign\n");
11858     } else {
11859         switch (gameMode) {
11860           case MachinePlaysWhite:
11861             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11862             break;
11863           case MachinePlaysBlack:
11864             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11865             break;
11866           case EditGame:
11867             if (cmailMsgLoaded) {
11868                 TruncateGame();
11869                 if (WhiteOnMove(cmailOldMove)) {
11870                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11871                 } else {
11872                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11873                 }
11874                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11875             }
11876             break;
11877           default:
11878             break;
11879         }
11880     }
11881 }
11882
11883
11884 void
11885 StopObservingEvent()
11886 {
11887     /* Stop observing current games */
11888     SendToICS(ics_prefix);
11889     SendToICS("unobserve\n");
11890 }
11891
11892 void
11893 StopExaminingEvent()
11894 {
11895     /* Stop observing current game */
11896     SendToICS(ics_prefix);
11897     SendToICS("unexamine\n");
11898 }
11899
11900 void
11901 ForwardInner(target)
11902      int target;
11903 {
11904     int limit;
11905
11906     if (appData.debugMode)
11907         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11908                 target, currentMove, forwardMostMove);
11909
11910     if (gameMode == EditPosition)
11911       return;
11912
11913     if (gameMode == PlayFromGameFile && !pausing)
11914       PauseEvent();
11915
11916     if (gameMode == IcsExamining && pausing)
11917       limit = pauseExamForwardMostMove;
11918     else
11919       limit = forwardMostMove;
11920
11921     if (target > limit) target = limit;
11922
11923     if (target > 0 && moveList[target - 1][0]) {
11924         int fromX, fromY, toX, toY;
11925         toX = moveList[target - 1][2] - AAA;
11926         toY = moveList[target - 1][3] - ONE;
11927         if (moveList[target - 1][1] == '@') {
11928             if (appData.highlightLastMove) {
11929                 SetHighlights(-1, -1, toX, toY);
11930             }
11931         } else {
11932             fromX = moveList[target - 1][0] - AAA;
11933             fromY = moveList[target - 1][1] - ONE;
11934             if (target == currentMove + 1) {
11935                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11936             }
11937             if (appData.highlightLastMove) {
11938                 SetHighlights(fromX, fromY, toX, toY);
11939             }
11940         }
11941     }
11942     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11943         gameMode == Training || gameMode == PlayFromGameFile ||
11944         gameMode == AnalyzeFile) {
11945         while (currentMove < target) {
11946             SendMoveToProgram(currentMove++, &first);
11947         }
11948     } else {
11949         currentMove = target;
11950     }
11951
11952     if (gameMode == EditGame || gameMode == EndOfGame) {
11953         whiteTimeRemaining = timeRemaining[0][currentMove];
11954         blackTimeRemaining = timeRemaining[1][currentMove];
11955     }
11956     DisplayBothClocks();
11957     DisplayMove(currentMove - 1);
11958     DrawPosition(FALSE, boards[currentMove]);
11959     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11960     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11961         DisplayComment(currentMove - 1, commentList[currentMove]);
11962     }
11963 }
11964
11965
11966 void
11967 ForwardEvent()
11968 {
11969     if (gameMode == IcsExamining && !pausing) {
11970         SendToICS(ics_prefix);
11971         SendToICS("forward\n");
11972     } else {
11973         ForwardInner(currentMove + 1);
11974     }
11975 }
11976
11977 void
11978 ToEndEvent()
11979 {
11980     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11981         /* to optimze, we temporarily turn off analysis mode while we feed
11982          * the remaining moves to the engine. Otherwise we get analysis output
11983          * after each move.
11984          */
11985         if (first.analysisSupport) {
11986           SendToProgram("exit\nforce\n", &first);
11987           first.analyzing = FALSE;
11988         }
11989     }
11990
11991     if (gameMode == IcsExamining && !pausing) {
11992         SendToICS(ics_prefix);
11993         SendToICS("forward 999999\n");
11994     } else {
11995         ForwardInner(forwardMostMove);
11996     }
11997
11998     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11999         /* we have fed all the moves, so reactivate analysis mode */
12000         SendToProgram("analyze\n", &first);
12001         first.analyzing = TRUE;
12002         /*first.maybeThinking = TRUE;*/
12003         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12004     }
12005 }
12006
12007 void
12008 BackwardInner(target)
12009      int target;
12010 {
12011     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12012
12013     if (appData.debugMode)
12014         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12015                 target, currentMove, forwardMostMove);
12016
12017     if (gameMode == EditPosition) return;
12018     if (currentMove <= backwardMostMove) {
12019         ClearHighlights();
12020         DrawPosition(full_redraw, boards[currentMove]);
12021         return;
12022     }
12023     if (gameMode == PlayFromGameFile && !pausing)
12024       PauseEvent();
12025
12026     if (moveList[target][0]) {
12027         int fromX, fromY, toX, toY;
12028         toX = moveList[target][2] - AAA;
12029         toY = moveList[target][3] - ONE;
12030         if (moveList[target][1] == '@') {
12031             if (appData.highlightLastMove) {
12032                 SetHighlights(-1, -1, toX, toY);
12033             }
12034         } else {
12035             fromX = moveList[target][0] - AAA;
12036             fromY = moveList[target][1] - ONE;
12037             if (target == currentMove - 1) {
12038                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12039             }
12040             if (appData.highlightLastMove) {
12041                 SetHighlights(fromX, fromY, toX, toY);
12042             }
12043         }
12044     }
12045     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12046         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12047         while (currentMove > target) {
12048             SendToProgram("undo\n", &first);
12049             currentMove--;
12050         }
12051     } else {
12052         currentMove = target;
12053     }
12054
12055     if (gameMode == EditGame || gameMode == EndOfGame) {
12056         whiteTimeRemaining = timeRemaining[0][currentMove];
12057         blackTimeRemaining = timeRemaining[1][currentMove];
12058     }
12059     DisplayBothClocks();
12060     DisplayMove(currentMove - 1);
12061     DrawPosition(full_redraw, boards[currentMove]);
12062     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12063     // [HGM] PV info: routine tests if comment empty
12064     DisplayComment(currentMove - 1, commentList[currentMove]);
12065 }
12066
12067 void
12068 BackwardEvent()
12069 {
12070     if (gameMode == IcsExamining && !pausing) {
12071         SendToICS(ics_prefix);
12072         SendToICS("backward\n");
12073     } else {
12074         BackwardInner(currentMove - 1);
12075     }
12076 }
12077
12078 void
12079 ToStartEvent()
12080 {
12081     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12082         /* to optimize, we temporarily turn off analysis mode while we undo
12083          * all the moves. Otherwise we get analysis output after each undo.
12084          */
12085         if (first.analysisSupport) {
12086           SendToProgram("exit\nforce\n", &first);
12087           first.analyzing = FALSE;
12088         }
12089     }
12090
12091     if (gameMode == IcsExamining && !pausing) {
12092         SendToICS(ics_prefix);
12093         SendToICS("backward 999999\n");
12094     } else {
12095         BackwardInner(backwardMostMove);
12096     }
12097
12098     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12099         /* we have fed all the moves, so reactivate analysis mode */
12100         SendToProgram("analyze\n", &first);
12101         first.analyzing = TRUE;
12102         /*first.maybeThinking = TRUE;*/
12103         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12104     }
12105 }
12106
12107 void
12108 ToNrEvent(int to)
12109 {
12110   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12111   if (to >= forwardMostMove) to = forwardMostMove;
12112   if (to <= backwardMostMove) to = backwardMostMove;
12113   if (to < currentMove) {
12114     BackwardInner(to);
12115   } else {
12116     ForwardInner(to);
12117   }
12118 }
12119
12120 void
12121 RevertEvent()
12122 {
12123     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12124         return;
12125     }
12126     if (gameMode != IcsExamining) {
12127         DisplayError(_("You are not examining a game"), 0);
12128         return;
12129     }
12130     if (pausing) {
12131         DisplayError(_("You can't revert while pausing"), 0);
12132         return;
12133     }
12134     SendToICS(ics_prefix);
12135     SendToICS("revert\n");
12136 }
12137
12138 void
12139 RetractMoveEvent()
12140 {
12141     switch (gameMode) {
12142       case MachinePlaysWhite:
12143       case MachinePlaysBlack:
12144         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12145             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12146             return;
12147         }
12148         if (forwardMostMove < 2) return;
12149         currentMove = forwardMostMove = forwardMostMove - 2;
12150         whiteTimeRemaining = timeRemaining[0][currentMove];
12151         blackTimeRemaining = timeRemaining[1][currentMove];
12152         DisplayBothClocks();
12153         DisplayMove(currentMove - 1);
12154         ClearHighlights();/*!! could figure this out*/
12155         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12156         SendToProgram("remove\n", &first);
12157         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12158         break;
12159
12160       case BeginningOfGame:
12161       default:
12162         break;
12163
12164       case IcsPlayingWhite:
12165       case IcsPlayingBlack:
12166         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12167             SendToICS(ics_prefix);
12168             SendToICS("takeback 2\n");
12169         } else {
12170             SendToICS(ics_prefix);
12171             SendToICS("takeback 1\n");
12172         }
12173         break;
12174     }
12175 }
12176
12177 void
12178 MoveNowEvent()
12179 {
12180     ChessProgramState *cps;
12181
12182     switch (gameMode) {
12183       case MachinePlaysWhite:
12184         if (!WhiteOnMove(forwardMostMove)) {
12185             DisplayError(_("It is your turn"), 0);
12186             return;
12187         }
12188         cps = &first;
12189         break;
12190       case MachinePlaysBlack:
12191         if (WhiteOnMove(forwardMostMove)) {
12192             DisplayError(_("It is your turn"), 0);
12193             return;
12194         }
12195         cps = &first;
12196         break;
12197       case TwoMachinesPlay:
12198         if (WhiteOnMove(forwardMostMove) ==
12199             (first.twoMachinesColor[0] == 'w')) {
12200             cps = &first;
12201         } else {
12202             cps = &second;
12203         }
12204         break;
12205       case BeginningOfGame:
12206       default:
12207         return;
12208     }
12209     SendToProgram("?\n", cps);
12210 }
12211
12212 void
12213 TruncateGameEvent()
12214 {
12215     EditGameEvent();
12216     if (gameMode != EditGame) return;
12217     TruncateGame();
12218 }
12219
12220 void
12221 TruncateGame()
12222 {
12223     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12224     if (forwardMostMove > currentMove) {
12225         if (gameInfo.resultDetails != NULL) {
12226             free(gameInfo.resultDetails);
12227             gameInfo.resultDetails = NULL;
12228             gameInfo.result = GameUnfinished;
12229         }
12230         forwardMostMove = currentMove;
12231         HistorySet(parseList, backwardMostMove, forwardMostMove,
12232                    currentMove-1);
12233     }
12234 }
12235
12236 void
12237 HintEvent()
12238 {
12239     if (appData.noChessProgram) return;
12240     switch (gameMode) {
12241       case MachinePlaysWhite:
12242         if (WhiteOnMove(forwardMostMove)) {
12243             DisplayError(_("Wait until your turn"), 0);
12244             return;
12245         }
12246         break;
12247       case BeginningOfGame:
12248       case MachinePlaysBlack:
12249         if (!WhiteOnMove(forwardMostMove)) {
12250             DisplayError(_("Wait until your turn"), 0);
12251             return;
12252         }
12253         break;
12254       default:
12255         DisplayError(_("No hint available"), 0);
12256         return;
12257     }
12258     SendToProgram("hint\n", &first);
12259     hintRequested = TRUE;
12260 }
12261
12262 void
12263 BookEvent()
12264 {
12265     if (appData.noChessProgram) return;
12266     switch (gameMode) {
12267       case MachinePlaysWhite:
12268         if (WhiteOnMove(forwardMostMove)) {
12269             DisplayError(_("Wait until your turn"), 0);
12270             return;
12271         }
12272         break;
12273       case BeginningOfGame:
12274       case MachinePlaysBlack:
12275         if (!WhiteOnMove(forwardMostMove)) {
12276             DisplayError(_("Wait until your turn"), 0);
12277             return;
12278         }
12279         break;
12280       case EditPosition:
12281         EditPositionDone(TRUE);
12282         break;
12283       case TwoMachinesPlay:
12284         return;
12285       default:
12286         break;
12287     }
12288     SendToProgram("bk\n", &first);
12289     bookOutput[0] = NULLCHAR;
12290     bookRequested = TRUE;
12291 }
12292
12293 void
12294 AboutGameEvent()
12295 {
12296     char *tags = PGNTags(&gameInfo);
12297     TagsPopUp(tags, CmailMsg());
12298     free(tags);
12299 }
12300
12301 /* end button procedures */
12302
12303 void
12304 PrintPosition(fp, move)
12305      FILE *fp;
12306      int move;
12307 {
12308     int i, j;
12309
12310     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12311         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12312             char c = PieceToChar(boards[move][i][j]);
12313             fputc(c == 'x' ? '.' : c, fp);
12314             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12315         }
12316     }
12317     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12318       fprintf(fp, "white to play\n");
12319     else
12320       fprintf(fp, "black to play\n");
12321 }
12322
12323 void
12324 PrintOpponents(fp)
12325      FILE *fp;
12326 {
12327     if (gameInfo.white != NULL) {
12328         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12329     } else {
12330         fprintf(fp, "\n");
12331     }
12332 }
12333
12334 /* Find last component of program's own name, using some heuristics */
12335 void
12336 TidyProgramName(prog, host, buf)
12337      char *prog, *host, buf[MSG_SIZ];
12338 {
12339     char *p, *q;
12340     int local = (strcmp(host, "localhost") == 0);
12341     while (!local && (p = strchr(prog, ';')) != NULL) {
12342         p++;
12343         while (*p == ' ') p++;
12344         prog = p;
12345     }
12346     if (*prog == '"' || *prog == '\'') {
12347         q = strchr(prog + 1, *prog);
12348     } else {
12349         q = strchr(prog, ' ');
12350     }
12351     if (q == NULL) q = prog + strlen(prog);
12352     p = q;
12353     while (p >= prog && *p != '/' && *p != '\\') p--;
12354     p++;
12355     if(p == prog && *p == '"') p++;
12356     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12357     memcpy(buf, p, q - p);
12358     buf[q - p] = NULLCHAR;
12359     if (!local) {
12360         strcat(buf, "@");
12361         strcat(buf, host);
12362     }
12363 }
12364
12365 char *
12366 TimeControlTagValue()
12367 {
12368     char buf[MSG_SIZ];
12369     if (!appData.clockMode) {
12370         strcpy(buf, "-");
12371     } else if (movesPerSession > 0) {
12372         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12373     } else if (timeIncrement == 0) {
12374         sprintf(buf, "%ld", timeControl/1000);
12375     } else {
12376         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12377     }
12378     return StrSave(buf);
12379 }
12380
12381 void
12382 SetGameInfo()
12383 {
12384     /* This routine is used only for certain modes */
12385     VariantClass v = gameInfo.variant;
12386     ChessMove r = GameUnfinished;
12387     char *p = NULL;
12388
12389     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12390         r = gameInfo.result; 
12391         p = gameInfo.resultDetails; 
12392         gameInfo.resultDetails = NULL;
12393     }
12394     ClearGameInfo(&gameInfo);
12395     gameInfo.variant = v;
12396
12397     switch (gameMode) {
12398       case MachinePlaysWhite:
12399         gameInfo.event = StrSave( appData.pgnEventHeader );
12400         gameInfo.site = StrSave(HostName());
12401         gameInfo.date = PGNDate();
12402         gameInfo.round = StrSave("-");
12403         gameInfo.white = StrSave(first.tidy);
12404         gameInfo.black = StrSave(UserName());
12405         gameInfo.timeControl = TimeControlTagValue();
12406         break;
12407
12408       case MachinePlaysBlack:
12409         gameInfo.event = StrSave( appData.pgnEventHeader );
12410         gameInfo.site = StrSave(HostName());
12411         gameInfo.date = PGNDate();
12412         gameInfo.round = StrSave("-");
12413         gameInfo.white = StrSave(UserName());
12414         gameInfo.black = StrSave(first.tidy);
12415         gameInfo.timeControl = TimeControlTagValue();
12416         break;
12417
12418       case TwoMachinesPlay:
12419         gameInfo.event = StrSave( appData.pgnEventHeader );
12420         gameInfo.site = StrSave(HostName());
12421         gameInfo.date = PGNDate();
12422         if (matchGame > 0) {
12423             char buf[MSG_SIZ];
12424             sprintf(buf, "%d", matchGame);
12425             gameInfo.round = StrSave(buf);
12426         } else {
12427             gameInfo.round = StrSave("-");
12428         }
12429         if (first.twoMachinesColor[0] == 'w') {
12430             gameInfo.white = StrSave(first.tidy);
12431             gameInfo.black = StrSave(second.tidy);
12432         } else {
12433             gameInfo.white = StrSave(second.tidy);
12434             gameInfo.black = StrSave(first.tidy);
12435         }
12436         gameInfo.timeControl = TimeControlTagValue();
12437         break;
12438
12439       case EditGame:
12440         gameInfo.event = StrSave("Edited game");
12441         gameInfo.site = StrSave(HostName());
12442         gameInfo.date = PGNDate();
12443         gameInfo.round = StrSave("-");
12444         gameInfo.white = StrSave("-");
12445         gameInfo.black = StrSave("-");
12446         gameInfo.result = r;
12447         gameInfo.resultDetails = p;
12448         break;
12449
12450       case EditPosition:
12451         gameInfo.event = StrSave("Edited position");
12452         gameInfo.site = StrSave(HostName());
12453         gameInfo.date = PGNDate();
12454         gameInfo.round = StrSave("-");
12455         gameInfo.white = StrSave("-");
12456         gameInfo.black = StrSave("-");
12457         break;
12458
12459       case IcsPlayingWhite:
12460       case IcsPlayingBlack:
12461       case IcsObserving:
12462       case IcsExamining:
12463         break;
12464
12465       case PlayFromGameFile:
12466         gameInfo.event = StrSave("Game from non-PGN file");
12467         gameInfo.site = StrSave(HostName());
12468         gameInfo.date = PGNDate();
12469         gameInfo.round = StrSave("-");
12470         gameInfo.white = StrSave("?");
12471         gameInfo.black = StrSave("?");
12472         break;
12473
12474       default:
12475         break;
12476     }
12477 }
12478
12479 void
12480 ReplaceComment(index, text)
12481      int index;
12482      char *text;
12483 {
12484     int len;
12485
12486     while (*text == '\n') text++;
12487     len = strlen(text);
12488     while (len > 0 && text[len - 1] == '\n') len--;
12489
12490     if (commentList[index] != NULL)
12491       free(commentList[index]);
12492
12493     if (len == 0) {
12494         commentList[index] = NULL;
12495         return;
12496     }
12497   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12498       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12499       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12500     commentList[index] = (char *) malloc(len + 2);
12501     strncpy(commentList[index], text, len);
12502     commentList[index][len] = '\n';
12503     commentList[index][len + 1] = NULLCHAR;
12504   } else { 
12505     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12506     char *p;
12507     commentList[index] = (char *) malloc(len + 6);
12508     strcpy(commentList[index], "{\n");
12509     strncpy(commentList[index]+2, text, len);
12510     commentList[index][len+2] = NULLCHAR;
12511     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12512     strcat(commentList[index], "\n}\n");
12513   }
12514 }
12515
12516 void
12517 CrushCRs(text)
12518      char *text;
12519 {
12520   char *p = text;
12521   char *q = text;
12522   char ch;
12523
12524   do {
12525     ch = *p++;
12526     if (ch == '\r') continue;
12527     *q++ = ch;
12528   } while (ch != '\0');
12529 }
12530
12531 void
12532 AppendComment(index, text, addBraces)
12533      int index;
12534      char *text;
12535      Boolean addBraces; // [HGM] braces: tells if we should add {}
12536 {
12537     int oldlen, len;
12538     char *old;
12539
12540 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12541     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12542
12543     CrushCRs(text);
12544     while (*text == '\n') text++;
12545     len = strlen(text);
12546     while (len > 0 && text[len - 1] == '\n') len--;
12547
12548     if (len == 0) return;
12549
12550     if (commentList[index] != NULL) {
12551         old = commentList[index];
12552         oldlen = strlen(old);
12553         while(commentList[index][oldlen-1] ==  '\n')
12554           commentList[index][--oldlen] = NULLCHAR;
12555         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12556         strcpy(commentList[index], old);
12557         free(old);
12558         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12559         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12560           if(addBraces) addBraces = FALSE; else { text++; len--; }
12561           while (*text == '\n') { text++; len--; }
12562           commentList[index][--oldlen] = NULLCHAR;
12563       }
12564         if(addBraces) strcat(commentList[index], "\n{\n");
12565         else          strcat(commentList[index], "\n");
12566         strcat(commentList[index], text);
12567         if(addBraces) strcat(commentList[index], "\n}\n");
12568         else          strcat(commentList[index], "\n");
12569     } else {
12570         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12571         if(addBraces)
12572              strcpy(commentList[index], "{\n");
12573         else commentList[index][0] = NULLCHAR;
12574         strcat(commentList[index], text);
12575         strcat(commentList[index], "\n");
12576         if(addBraces) strcat(commentList[index], "}\n");
12577     }
12578 }
12579
12580 static char * FindStr( char * text, char * sub_text )
12581 {
12582     char * result = strstr( text, sub_text );
12583
12584     if( result != NULL ) {
12585         result += strlen( sub_text );
12586     }
12587
12588     return result;
12589 }
12590
12591 /* [AS] Try to extract PV info from PGN comment */
12592 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12593 char *GetInfoFromComment( int index, char * text )
12594 {
12595     char * sep = text;
12596
12597     if( text != NULL && index > 0 ) {
12598         int score = 0;
12599         int depth = 0;
12600         int time = -1, sec = 0, deci;
12601         char * s_eval = FindStr( text, "[%eval " );
12602         char * s_emt = FindStr( text, "[%emt " );
12603
12604         if( s_eval != NULL || s_emt != NULL ) {
12605             /* New style */
12606             char delim;
12607
12608             if( s_eval != NULL ) {
12609                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12610                     return text;
12611                 }
12612
12613                 if( delim != ']' ) {
12614                     return text;
12615                 }
12616             }
12617
12618             if( s_emt != NULL ) {
12619             }
12620                 return text;
12621         }
12622         else {
12623             /* We expect something like: [+|-]nnn.nn/dd */
12624             int score_lo = 0;
12625
12626             if(*text != '{') return text; // [HGM] braces: must be normal comment
12627
12628             sep = strchr( text, '/' );
12629             if( sep == NULL || sep < (text+4) ) {
12630                 return text;
12631             }
12632
12633             time = -1; sec = -1; deci = -1;
12634             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12635                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12636                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12637                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12638                 return text;
12639             }
12640
12641             if( score_lo < 0 || score_lo >= 100 ) {
12642                 return text;
12643             }
12644
12645             if(sec >= 0) time = 600*time + 10*sec; else
12646             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12647
12648             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12649
12650             /* [HGM] PV time: now locate end of PV info */
12651             while( *++sep >= '0' && *sep <= '9'); // strip depth
12652             if(time >= 0)
12653             while( *++sep >= '0' && *sep <= '9'); // strip time
12654             if(sec >= 0)
12655             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12656             if(deci >= 0)
12657             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12658             while(*sep == ' ') sep++;
12659         }
12660
12661         if( depth <= 0 ) {
12662             return text;
12663         }
12664
12665         if( time < 0 ) {
12666             time = -1;
12667         }
12668
12669         pvInfoList[index-1].depth = depth;
12670         pvInfoList[index-1].score = score;
12671         pvInfoList[index-1].time  = 10*time; // centi-sec
12672         if(*sep == '}') *sep = 0; else *--sep = '{';
12673     }
12674     return sep;
12675 }
12676
12677 void
12678 SendToProgram(message, cps)
12679      char *message;
12680      ChessProgramState *cps;
12681 {
12682     int count, outCount, error;
12683     char buf[MSG_SIZ];
12684
12685     if (cps->pr == NULL) return;
12686     Attention(cps);
12687
12688     if (appData.debugMode) {
12689         TimeMark now;
12690         GetTimeMark(&now);
12691         fprintf(debugFP, "%ld >%-6s: %s",
12692                 SubtractTimeMarks(&now, &programStartTime),
12693                 cps->which, message);
12694     }
12695
12696     count = strlen(message);
12697     outCount = OutputToProcess(cps->pr, message, count, &error);
12698     if (outCount < count && !exiting
12699                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12700         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12701         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12702             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12703                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12704                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12705             } else {
12706                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12707             }
12708             gameInfo.resultDetails = StrSave(buf);
12709         }
12710         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12711     }
12712 }
12713
12714 void
12715 ReceiveFromProgram(isr, closure, message, count, error)
12716      InputSourceRef isr;
12717      VOIDSTAR closure;
12718      char *message;
12719      int count;
12720      int error;
12721 {
12722     char *end_str;
12723     char buf[MSG_SIZ];
12724     ChessProgramState *cps = (ChessProgramState *)closure;
12725
12726     if (isr != cps->isr) return; /* Killed intentionally */
12727     if (count <= 0) {
12728         if (count == 0) {
12729             sprintf(buf,
12730                     _("Error: %s chess program (%s) exited unexpectedly"),
12731                     cps->which, cps->program);
12732         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12733                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12734                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12735                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12736                 } else {
12737                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12738                 }
12739                 gameInfo.resultDetails = StrSave(buf);
12740             }
12741             RemoveInputSource(cps->isr);
12742             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12743         } else {
12744             sprintf(buf,
12745                     _("Error reading from %s chess program (%s)"),
12746                     cps->which, cps->program);
12747             RemoveInputSource(cps->isr);
12748
12749             /* [AS] Program is misbehaving badly... kill it */
12750             if( count == -2 ) {
12751                 DestroyChildProcess( cps->pr, 9 );
12752                 cps->pr = NoProc;
12753             }
12754
12755             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12756         }
12757         return;
12758     }
12759
12760     if ((end_str = strchr(message, '\r')) != NULL)
12761       *end_str = NULLCHAR;
12762     if ((end_str = strchr(message, '\n')) != NULL)
12763       *end_str = NULLCHAR;
12764
12765     if (appData.debugMode) {
12766         TimeMark now; int print = 1;
12767         char *quote = ""; char c; int i;
12768
12769         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12770                 char start = message[0];
12771                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12772                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12773                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12774                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12775                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12776                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12777                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12778                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12779                         { quote = "# "; print = (appData.engineComments == 2); }
12780                 message[0] = start; // restore original message
12781         }
12782         if(print) {
12783                 GetTimeMark(&now);
12784                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12785                         SubtractTimeMarks(&now, &programStartTime), cps->which,
12786                         quote,
12787                         message);
12788         }
12789     }
12790
12791     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12792     if (appData.icsEngineAnalyze) {
12793         if (strstr(message, "whisper") != NULL ||
12794              strstr(message, "kibitz") != NULL ||
12795             strstr(message, "tellics") != NULL) return;
12796     }
12797
12798     HandleMachineMove(message, cps);
12799 }
12800
12801
12802 void
12803 SendTimeControl(cps, mps, tc, inc, sd, st)
12804      ChessProgramState *cps;
12805      int mps, inc, sd, st;
12806      long tc;
12807 {
12808     char buf[MSG_SIZ];
12809     int seconds;
12810
12811     if( timeControl_2 > 0 ) {
12812         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12813             tc = timeControl_2;
12814         }
12815     }
12816     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12817     inc /= cps->timeOdds;
12818     st  /= cps->timeOdds;
12819
12820     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12821
12822     if (st > 0) {
12823       /* Set exact time per move, normally using st command */
12824       if (cps->stKludge) {
12825         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12826         seconds = st % 60;
12827         if (seconds == 0) {
12828           sprintf(buf, "level 1 %d\n", st/60);
12829         } else {
12830           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12831         }
12832       } else {
12833         sprintf(buf, "st %d\n", st);
12834       }
12835     } else {
12836       /* Set conventional or incremental time control, using level command */
12837       if (seconds == 0) {
12838         /* Note old gnuchess bug -- minutes:seconds used to not work.
12839            Fixed in later versions, but still avoid :seconds
12840            when seconds is 0. */
12841         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12842       } else {
12843         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12844                 seconds, inc/1000);
12845       }
12846     }
12847     SendToProgram(buf, cps);
12848
12849     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12850     /* Orthogonally, limit search to given depth */
12851     if (sd > 0) {
12852       if (cps->sdKludge) {
12853         sprintf(buf, "depth\n%d\n", sd);
12854       } else {
12855         sprintf(buf, "sd %d\n", sd);
12856       }
12857       SendToProgram(buf, cps);
12858     }
12859
12860     if(cps->nps > 0) { /* [HGM] nps */
12861         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12862         else {
12863                 sprintf(buf, "nps %d\n", cps->nps);
12864               SendToProgram(buf, cps);
12865         }
12866     }
12867 }
12868
12869 ChessProgramState *WhitePlayer()
12870 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12871 {
12872     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12873        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12874         return &second;
12875     return &first;
12876 }
12877
12878 void
12879 SendTimeRemaining(cps, machineWhite)
12880      ChessProgramState *cps;
12881      int /*boolean*/ machineWhite;
12882 {
12883     char message[MSG_SIZ];
12884     long time, otime;
12885
12886     /* Note: this routine must be called when the clocks are stopped
12887        or when they have *just* been set or switched; otherwise
12888        it will be off by the time since the current tick started.
12889     */
12890     if (machineWhite) {
12891         time = whiteTimeRemaining / 10;
12892         otime = blackTimeRemaining / 10;
12893     } else {
12894         time = blackTimeRemaining / 10;
12895         otime = whiteTimeRemaining / 10;
12896     }
12897     /* [HGM] translate opponent's time by time-odds factor */
12898     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12899     if (appData.debugMode) {
12900         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12901     }
12902
12903     if (time <= 0) time = 1;
12904     if (otime <= 0) otime = 1;
12905
12906     sprintf(message, "time %ld\n", time);
12907     SendToProgram(message, cps);
12908
12909     sprintf(message, "otim %ld\n", otime);
12910     SendToProgram(message, cps);
12911 }
12912
12913 int
12914 BoolFeature(p, name, loc, cps)
12915      char **p;
12916      char *name;
12917      int *loc;
12918      ChessProgramState *cps;
12919 {
12920   char buf[MSG_SIZ];
12921   int len = strlen(name);
12922   int val;
12923   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12924     (*p) += len + 1;
12925     sscanf(*p, "%d", &val);
12926     *loc = (val != 0);
12927     while (**p && **p != ' ') (*p)++;
12928     sprintf(buf, "accepted %s\n", name);
12929     SendToProgram(buf, cps);
12930     return TRUE;
12931   }
12932   return FALSE;
12933 }
12934
12935 int
12936 IntFeature(p, name, loc, cps)
12937      char **p;
12938      char *name;
12939      int *loc;
12940      ChessProgramState *cps;
12941 {
12942   char buf[MSG_SIZ];
12943   int len = strlen(name);
12944   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12945     (*p) += len + 1;
12946     sscanf(*p, "%d", loc);
12947     while (**p && **p != ' ') (*p)++;
12948     sprintf(buf, "accepted %s\n", name);
12949     SendToProgram(buf, cps);
12950     return TRUE;
12951   }
12952   return FALSE;
12953 }
12954
12955 int
12956 StringFeature(p, name, loc, cps)
12957      char **p;
12958      char *name;
12959      char loc[];
12960      ChessProgramState *cps;
12961 {
12962   char buf[MSG_SIZ];
12963   int len = strlen(name);
12964   if (strncmp((*p), name, len) == 0
12965       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12966     (*p) += len + 2;
12967     sscanf(*p, "%[^\"]", loc);
12968     while (**p && **p != '\"') (*p)++;
12969     if (**p == '\"') (*p)++;
12970     sprintf(buf, "accepted %s\n", name);
12971     SendToProgram(buf, cps);
12972     return TRUE;
12973   }
12974   return FALSE;
12975 }
12976
12977 int
12978 ParseOption(Option *opt, ChessProgramState *cps)
12979 // [HGM] options: process the string that defines an engine option, and determine
12980 // name, type, default value, and allowed value range
12981 {
12982         char *p, *q, buf[MSG_SIZ];
12983         int n, min = (-1)<<31, max = 1<<31, def;
12984
12985         if(p = strstr(opt->name, " -spin ")) {
12986             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12987             if(max < min) max = min; // enforce consistency
12988             if(def < min) def = min;
12989             if(def > max) def = max;
12990             opt->value = def;
12991             opt->min = min;
12992             opt->max = max;
12993             opt->type = Spin;
12994         } else if((p = strstr(opt->name, " -slider "))) {
12995             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12996             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12997             if(max < min) max = min; // enforce consistency
12998             if(def < min) def = min;
12999             if(def > max) def = max;
13000             opt->value = def;
13001             opt->min = min;
13002             opt->max = max;
13003             opt->type = Spin; // Slider;
13004         } else if((p = strstr(opt->name, " -string "))) {
13005             opt->textValue = p+9;
13006             opt->type = TextBox;
13007         } else if((p = strstr(opt->name, " -file "))) {
13008             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13009             opt->textValue = p+7;
13010             opt->type = TextBox; // FileName;
13011         } else if((p = strstr(opt->name, " -path "))) {
13012             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13013             opt->textValue = p+7;
13014             opt->type = TextBox; // PathName;
13015         } else if(p = strstr(opt->name, " -check ")) {
13016             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13017             opt->value = (def != 0);
13018             opt->type = CheckBox;
13019         } else if(p = strstr(opt->name, " -combo ")) {
13020             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13021             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13022             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13023             opt->value = n = 0;
13024             while(q = StrStr(q, " /// ")) {
13025                 n++; *q = 0;    // count choices, and null-terminate each of them
13026                 q += 5;
13027                 if(*q == '*') { // remember default, which is marked with * prefix
13028                     q++;
13029                     opt->value = n;
13030                 }
13031                 cps->comboList[cps->comboCnt++] = q;
13032             }
13033             cps->comboList[cps->comboCnt++] = NULL;
13034             opt->max = n + 1;
13035             opt->type = ComboBox;
13036         } else if(p = strstr(opt->name, " -button")) {
13037             opt->type = Button;
13038         } else if(p = strstr(opt->name, " -save")) {
13039             opt->type = SaveButton;
13040         } else return FALSE;
13041         *p = 0; // terminate option name
13042         // now look if the command-line options define a setting for this engine option.
13043         if(cps->optionSettings && cps->optionSettings[0])
13044             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13045         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13046                 sprintf(buf, "option %s", p);
13047                 if(p = strstr(buf, ",")) *p = 0;
13048                 strcat(buf, "\n");
13049                 SendToProgram(buf, cps);
13050         }
13051         return TRUE;
13052 }
13053
13054 void
13055 FeatureDone(cps, val)
13056      ChessProgramState* cps;
13057      int val;
13058 {
13059   DelayedEventCallback cb = GetDelayedEvent();
13060   if ((cb == InitBackEnd3 && cps == &first) ||
13061       (cb == TwoMachinesEventIfReady && cps == &second)) {
13062     CancelDelayedEvent();
13063     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13064   }
13065   cps->initDone = val;
13066 }
13067
13068 /* Parse feature command from engine */
13069 void
13070 ParseFeatures(args, cps)
13071      char* args;
13072      ChessProgramState *cps;
13073 {
13074   char *p = args;
13075   char *q;
13076   int val;
13077   char buf[MSG_SIZ];
13078
13079   for (;;) {
13080     while (*p == ' ') p++;
13081     if (*p == NULLCHAR) return;
13082
13083     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13084     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13085     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13086     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13087     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13088     if (BoolFeature(&p, "reuse", &val, cps)) {
13089       /* Engine can disable reuse, but can't enable it if user said no */
13090       if (!val) cps->reuse = FALSE;
13091       continue;
13092     }
13093     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13094     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13095       if (gameMode == TwoMachinesPlay) {
13096         DisplayTwoMachinesTitle();
13097       } else {
13098         DisplayTitle("");
13099       }
13100       continue;
13101     }
13102     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13103     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13104     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13105     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13106     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13107     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13108     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13109     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13110     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13111     if (IntFeature(&p, "done", &val, cps)) {
13112       FeatureDone(cps, val);
13113       continue;
13114     }
13115     /* Added by Tord: */
13116     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13117     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13118     /* End of additions by Tord */
13119
13120     /* [HGM] added features: */
13121     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13122     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13123     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13124     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13125     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13126     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13127     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13128         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13129             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13130             SendToProgram(buf, cps);
13131             continue;
13132         }
13133         if(cps->nrOptions >= MAX_OPTIONS) {
13134             cps->nrOptions--;
13135             sprintf(buf, "%s engine has too many options\n", cps->which);
13136             DisplayError(buf, 0);
13137         }
13138         continue;
13139     }
13140     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13141     /* End of additions by HGM */
13142
13143     /* unknown feature: complain and skip */
13144     q = p;
13145     while (*q && *q != '=') q++;
13146     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13147     SendToProgram(buf, cps);
13148     p = q;
13149     if (*p == '=') {
13150       p++;
13151       if (*p == '\"') {
13152         p++;
13153         while (*p && *p != '\"') p++;
13154         if (*p == '\"') p++;
13155       } else {
13156         while (*p && *p != ' ') p++;
13157       }
13158     }
13159   }
13160
13161 }
13162
13163 void
13164 PeriodicUpdatesEvent(newState)
13165      int newState;
13166 {
13167     if (newState == appData.periodicUpdates)
13168       return;
13169
13170     appData.periodicUpdates=newState;
13171
13172     /* Display type changes, so update it now */
13173 //    DisplayAnalysis();
13174
13175     /* Get the ball rolling again... */
13176     if (newState) {
13177         AnalysisPeriodicEvent(1);
13178         StartAnalysisClock();
13179     }
13180 }
13181
13182 void
13183 PonderNextMoveEvent(newState)
13184      int newState;
13185 {
13186     if (newState == appData.ponderNextMove) return;
13187     if (gameMode == EditPosition) EditPositionDone(TRUE);
13188     if (newState) {
13189         SendToProgram("hard\n", &first);
13190         if (gameMode == TwoMachinesPlay) {
13191             SendToProgram("hard\n", &second);
13192         }
13193     } else {
13194         SendToProgram("easy\n", &first);
13195         thinkOutput[0] = NULLCHAR;
13196         if (gameMode == TwoMachinesPlay) {
13197             SendToProgram("easy\n", &second);
13198         }
13199     }
13200     appData.ponderNextMove = newState;
13201 }
13202
13203 void
13204 NewSettingEvent(option, command, value)
13205      char *command;
13206      int option, value;
13207 {
13208     char buf[MSG_SIZ];
13209
13210     if (gameMode == EditPosition) EditPositionDone(TRUE);
13211     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13212     SendToProgram(buf, &first);
13213     if (gameMode == TwoMachinesPlay) {
13214         SendToProgram(buf, &second);
13215     }
13216 }
13217
13218 void
13219 ShowThinkingEvent()
13220 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13221 {
13222     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13223     int newState = appData.showThinking
13224         // [HGM] thinking: other features now need thinking output as well
13225         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13226
13227     if (oldState == newState) return;
13228     oldState = newState;
13229     if (gameMode == EditPosition) EditPositionDone(TRUE);
13230     if (oldState) {
13231         SendToProgram("post\n", &first);
13232         if (gameMode == TwoMachinesPlay) {
13233             SendToProgram("post\n", &second);
13234         }
13235     } else {
13236         SendToProgram("nopost\n", &first);
13237         thinkOutput[0] = NULLCHAR;
13238         if (gameMode == TwoMachinesPlay) {
13239             SendToProgram("nopost\n", &second);
13240         }
13241     }
13242 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13243 }
13244
13245 void
13246 AskQuestionEvent(title, question, replyPrefix, which)
13247      char *title; char *question; char *replyPrefix; char *which;
13248 {
13249   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13250   if (pr == NoProc) return;
13251   AskQuestion(title, question, replyPrefix, pr);
13252 }
13253
13254 void
13255 DisplayMove(moveNumber)
13256      int moveNumber;
13257 {
13258     char message[MSG_SIZ];
13259     char res[MSG_SIZ];
13260     char cpThinkOutput[MSG_SIZ];
13261
13262     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13263
13264     if (moveNumber == forwardMostMove - 1 ||
13265         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13266
13267         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13268
13269         if (strchr(cpThinkOutput, '\n')) {
13270             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13271         }
13272     } else {
13273         *cpThinkOutput = NULLCHAR;
13274     }
13275
13276     /* [AS] Hide thinking from human user */
13277     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13278         *cpThinkOutput = NULLCHAR;
13279         if( thinkOutput[0] != NULLCHAR ) {
13280             int i;
13281
13282             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13283                 cpThinkOutput[i] = '.';
13284             }
13285             cpThinkOutput[i] = NULLCHAR;
13286             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13287         }
13288     }
13289
13290     if (moveNumber == forwardMostMove - 1 &&
13291         gameInfo.resultDetails != NULL) {
13292         if (gameInfo.resultDetails[0] == NULLCHAR) {
13293             sprintf(res, " %s", PGNResult(gameInfo.result));
13294         } else {
13295             sprintf(res, " {%s} %s",
13296                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13297         }
13298     } else {
13299         res[0] = NULLCHAR;
13300     }
13301
13302     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13303         DisplayMessage(res, cpThinkOutput);
13304     } else {
13305         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13306                 WhiteOnMove(moveNumber) ? " " : ".. ",
13307                 parseList[moveNumber], res);
13308         DisplayMessage(message, cpThinkOutput);
13309     }
13310 }
13311
13312 void
13313 DisplayComment(moveNumber, text)
13314      int moveNumber;
13315      char *text;
13316 {
13317     char title[MSG_SIZ];
13318     char buf[8000]; // comment can be long!
13319     int score, depth;
13320     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13321       strcpy(title, "Comment");
13322     } else {
13323       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13324               WhiteOnMove(moveNumber) ? " " : ".. ",
13325               parseList[moveNumber]);
13326     }
13327     // [HGM] PV info: display PV info together with (or as) comment
13328     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13329       if(text == NULL) text = "";                                           
13330       score = pvInfoList[moveNumber].score;
13331       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13332               depth, (pvInfoList[moveNumber].time+50)/100, text);
13333       text = buf;
13334     }
13335     if (text != NULL && (appData.autoDisplayComment || commentUp))
13336       CommentPopUp(title, text);
13337 }
13338
13339 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13340  * might be busy thinking or pondering.  It can be omitted if your
13341  * gnuchess is configured to stop thinking immediately on any user
13342  * input.  However, that gnuchess feature depends on the FIONREAD
13343  * ioctl, which does not work properly on some flavors of Unix.
13344  */
13345 void
13346 Attention(cps)
13347      ChessProgramState *cps;
13348 {
13349 #if ATTENTION
13350     if (!cps->useSigint) return;
13351     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13352     switch (gameMode) {
13353       case MachinePlaysWhite:
13354       case MachinePlaysBlack:
13355       case TwoMachinesPlay:
13356       case IcsPlayingWhite:
13357       case IcsPlayingBlack:
13358       case AnalyzeMode:
13359       case AnalyzeFile:
13360         /* Skip if we know it isn't thinking */
13361         if (!cps->maybeThinking) return;
13362         if (appData.debugMode)
13363           fprintf(debugFP, "Interrupting %s\n", cps->which);
13364         InterruptChildProcess(cps->pr);
13365         cps->maybeThinking = FALSE;
13366         break;
13367       default:
13368         break;
13369     }
13370 #endif /*ATTENTION*/
13371 }
13372
13373 int
13374 CheckFlags()
13375 {
13376     if (whiteTimeRemaining <= 0) {
13377         if (!whiteFlag) {
13378             whiteFlag = TRUE;
13379             if (appData.icsActive) {
13380                 if (appData.autoCallFlag &&
13381                     gameMode == IcsPlayingBlack && !blackFlag) {
13382                   SendToICS(ics_prefix);
13383                   SendToICS("flag\n");
13384                 }
13385             } else {
13386                 if (blackFlag) {
13387                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13388                 } else {
13389                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13390                     if (appData.autoCallFlag) {
13391                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13392                         return TRUE;
13393                     }
13394                 }
13395             }
13396         }
13397     }
13398     if (blackTimeRemaining <= 0) {
13399         if (!blackFlag) {
13400             blackFlag = TRUE;
13401             if (appData.icsActive) {
13402                 if (appData.autoCallFlag &&
13403                     gameMode == IcsPlayingWhite && !whiteFlag) {
13404                   SendToICS(ics_prefix);
13405                   SendToICS("flag\n");
13406                 }
13407             } else {
13408                 if (whiteFlag) {
13409                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13410                 } else {
13411                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13412                     if (appData.autoCallFlag) {
13413                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13414                         return TRUE;
13415                     }
13416                 }
13417             }
13418         }
13419     }
13420     return FALSE;
13421 }
13422
13423 void
13424 CheckTimeControl()
13425 {
13426     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13427         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13428
13429     /*
13430      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13431      */
13432     if ( !WhiteOnMove(forwardMostMove) )
13433         /* White made time control */
13434         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13435         /* [HGM] time odds: correct new time quota for time odds! */
13436                                             / WhitePlayer()->timeOdds;
13437       else
13438         /* Black made time control */
13439         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13440                                             / WhitePlayer()->other->timeOdds;
13441 }
13442
13443 void
13444 DisplayBothClocks()
13445 {
13446     int wom = gameMode == EditPosition ?
13447       !blackPlaysFirst : WhiteOnMove(currentMove);
13448     DisplayWhiteClock(whiteTimeRemaining, wom);
13449     DisplayBlackClock(blackTimeRemaining, !wom);
13450 }
13451
13452
13453 /* Timekeeping seems to be a portability nightmare.  I think everyone
13454    has ftime(), but I'm really not sure, so I'm including some ifdefs
13455    to use other calls if you don't.  Clocks will be less accurate if
13456    you have neither ftime nor gettimeofday.
13457 */
13458
13459 /* VS 2008 requires the #include outside of the function */
13460 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13461 #include <sys/timeb.h>
13462 #endif
13463
13464 /* Get the current time as a TimeMark */
13465 void
13466 GetTimeMark(tm)
13467      TimeMark *tm;
13468 {
13469 #if HAVE_GETTIMEOFDAY
13470
13471     struct timeval timeVal;
13472     struct timezone timeZone;
13473
13474     gettimeofday(&timeVal, &timeZone);
13475     tm->sec = (long) timeVal.tv_sec;
13476     tm->ms = (int) (timeVal.tv_usec / 1000L);
13477
13478 #else /*!HAVE_GETTIMEOFDAY*/
13479 #if HAVE_FTIME
13480
13481 // include <sys/timeb.h> / moved to just above start of function
13482     struct timeb timeB;
13483
13484     ftime(&timeB);
13485     tm->sec = (long) timeB.time;
13486     tm->ms = (int) timeB.millitm;
13487
13488 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13489     tm->sec = (long) time(NULL);
13490     tm->ms = 0;
13491 #endif
13492 #endif
13493 }
13494
13495 /* Return the difference in milliseconds between two
13496    time marks.  We assume the difference will fit in a long!
13497 */
13498 long
13499 SubtractTimeMarks(tm2, tm1)
13500      TimeMark *tm2, *tm1;
13501 {
13502     return 1000L*(tm2->sec - tm1->sec) +
13503            (long) (tm2->ms - tm1->ms);
13504 }
13505
13506
13507 /*
13508  * Code to manage the game clocks.
13509  *
13510  * In tournament play, black starts the clock and then white makes a move.
13511  * We give the human user a slight advantage if he is playing white---the
13512  * clocks don't run until he makes his first move, so it takes zero time.
13513  * Also, we don't account for network lag, so we could get out of sync
13514  * with GNU Chess's clock -- but then, referees are always right.
13515  */
13516
13517 static TimeMark tickStartTM;
13518 static long intendedTickLength;
13519
13520 long
13521 NextTickLength(timeRemaining)
13522      long timeRemaining;
13523 {
13524     long nominalTickLength, nextTickLength;
13525
13526     if (timeRemaining > 0L && timeRemaining <= 10000L)
13527       nominalTickLength = 100L;
13528     else
13529       nominalTickLength = 1000L;
13530     nextTickLength = timeRemaining % nominalTickLength;
13531     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13532
13533     return nextTickLength;
13534 }
13535
13536 /* Adjust clock one minute up or down */
13537 void
13538 AdjustClock(Boolean which, int dir)
13539 {
13540     if(which) blackTimeRemaining += 60000*dir;
13541     else      whiteTimeRemaining += 60000*dir;
13542     DisplayBothClocks();
13543 }
13544
13545 /* Stop clocks and reset to a fresh time control */
13546 void
13547 ResetClocks()
13548 {
13549     (void) StopClockTimer();
13550     if (appData.icsActive) {
13551         whiteTimeRemaining = blackTimeRemaining = 0;
13552     } else if (searchTime) {
13553         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13554         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13555     } else { /* [HGM] correct new time quote for time odds */
13556         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13557         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13558     }
13559     if (whiteFlag || blackFlag) {
13560         DisplayTitle("");
13561         whiteFlag = blackFlag = FALSE;
13562     }
13563     DisplayBothClocks();
13564 }
13565
13566 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13567
13568 /* Decrement running clock by amount of time that has passed */
13569 void
13570 DecrementClocks()
13571 {
13572     long timeRemaining;
13573     long lastTickLength, fudge;
13574     TimeMark now;
13575
13576     if (!appData.clockMode) return;
13577     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13578
13579     GetTimeMark(&now);
13580
13581     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13582
13583     /* Fudge if we woke up a little too soon */
13584     fudge = intendedTickLength - lastTickLength;
13585     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13586
13587     if (WhiteOnMove(forwardMostMove)) {
13588         if(whiteNPS >= 0) lastTickLength = 0;
13589         timeRemaining = whiteTimeRemaining -= lastTickLength;
13590         DisplayWhiteClock(whiteTimeRemaining - fudge,
13591                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13592     } else {
13593         if(blackNPS >= 0) lastTickLength = 0;
13594         timeRemaining = blackTimeRemaining -= lastTickLength;
13595         DisplayBlackClock(blackTimeRemaining - fudge,
13596                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13597     }
13598
13599     if (CheckFlags()) return;
13600
13601     tickStartTM = now;
13602     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13603     StartClockTimer(intendedTickLength);
13604
13605     /* if the time remaining has fallen below the alarm threshold, sound the
13606      * alarm. if the alarm has sounded and (due to a takeback or time control
13607      * with increment) the time remaining has increased to a level above the
13608      * threshold, reset the alarm so it can sound again.
13609      */
13610
13611     if (appData.icsActive && appData.icsAlarm) {
13612
13613         /* make sure we are dealing with the user's clock */
13614         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13615                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13616            )) return;
13617
13618         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13619             alarmSounded = FALSE;
13620         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13621             PlayAlarmSound();
13622             alarmSounded = TRUE;
13623         }
13624     }
13625 }
13626
13627
13628 /* A player has just moved, so stop the previously running
13629    clock and (if in clock mode) start the other one.
13630    We redisplay both clocks in case we're in ICS mode, because
13631    ICS gives us an update to both clocks after every move.
13632    Note that this routine is called *after* forwardMostMove
13633    is updated, so the last fractional tick must be subtracted
13634    from the color that is *not* on move now.
13635 */
13636 void
13637 SwitchClocks()
13638 {
13639     long lastTickLength;
13640     TimeMark now;
13641     int flagged = FALSE;
13642
13643     GetTimeMark(&now);
13644
13645     if (StopClockTimer() && appData.clockMode) {
13646         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13647         if (WhiteOnMove(forwardMostMove)) {
13648             if(blackNPS >= 0) lastTickLength = 0;
13649             blackTimeRemaining -= lastTickLength;
13650            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13651 //         if(pvInfoList[forwardMostMove-1].time == -1)
13652                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13653                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13654         } else {
13655            if(whiteNPS >= 0) lastTickLength = 0;
13656            whiteTimeRemaining -= lastTickLength;
13657            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13658 //         if(pvInfoList[forwardMostMove-1].time == -1)
13659                  pvInfoList[forwardMostMove-1].time =
13660                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13661         }
13662         flagged = CheckFlags();
13663     }
13664     CheckTimeControl();
13665
13666     if (flagged || !appData.clockMode) return;
13667
13668     switch (gameMode) {
13669       case MachinePlaysBlack:
13670       case MachinePlaysWhite:
13671       case BeginningOfGame:
13672         if (pausing) return;
13673         break;
13674
13675       case EditGame:
13676       case PlayFromGameFile:
13677       case IcsExamining:
13678         return;
13679
13680       default:
13681         break;
13682     }
13683
13684     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13685         if(WhiteOnMove(forwardMostMove))
13686              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13687         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13688     }
13689
13690     tickStartTM = now;
13691     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13692       whiteTimeRemaining : blackTimeRemaining);
13693     StartClockTimer(intendedTickLength);
13694 }
13695
13696
13697 /* Stop both clocks */
13698 void
13699 StopClocks()
13700 {
13701     long lastTickLength;
13702     TimeMark now;
13703
13704     if (!StopClockTimer()) return;
13705     if (!appData.clockMode) return;
13706
13707     GetTimeMark(&now);
13708
13709     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13710     if (WhiteOnMove(forwardMostMove)) {
13711         if(whiteNPS >= 0) lastTickLength = 0;
13712         whiteTimeRemaining -= lastTickLength;
13713         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13714     } else {
13715         if(blackNPS >= 0) lastTickLength = 0;
13716         blackTimeRemaining -= lastTickLength;
13717         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13718     }
13719     CheckFlags();
13720 }
13721
13722 /* Start clock of player on move.  Time may have been reset, so
13723    if clock is already running, stop and restart it. */
13724 void
13725 StartClocks()
13726 {
13727     (void) StopClockTimer(); /* in case it was running already */
13728     DisplayBothClocks();
13729     if (CheckFlags()) return;
13730
13731     if (!appData.clockMode) return;
13732     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13733
13734     GetTimeMark(&tickStartTM);
13735     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13736       whiteTimeRemaining : blackTimeRemaining);
13737
13738    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13739     whiteNPS = blackNPS = -1;
13740     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13741        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13742         whiteNPS = first.nps;
13743     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13744        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13745         blackNPS = first.nps;
13746     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13747         whiteNPS = second.nps;
13748     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13749         blackNPS = second.nps;
13750     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13751
13752     StartClockTimer(intendedTickLength);
13753 }
13754
13755 char *
13756 TimeString(ms)
13757      long ms;
13758 {
13759     long second, minute, hour, day;
13760     char *sign = "";
13761     static char buf[32];
13762
13763     if (ms > 0 && ms <= 9900) {
13764       /* convert milliseconds to tenths, rounding up */
13765       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13766
13767       sprintf(buf, " %03.1f ", tenths/10.0);
13768       return buf;
13769     }
13770
13771     /* convert milliseconds to seconds, rounding up */
13772     /* use floating point to avoid strangeness of integer division
13773        with negative dividends on many machines */
13774     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13775
13776     if (second < 0) {
13777         sign = "-";
13778         second = -second;
13779     }
13780
13781     day = second / (60 * 60 * 24);
13782     second = second % (60 * 60 * 24);
13783     hour = second / (60 * 60);
13784     second = second % (60 * 60);
13785     minute = second / 60;
13786     second = second % 60;
13787
13788     if (day > 0)
13789       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13790               sign, day, hour, minute, second);
13791     else if (hour > 0)
13792       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13793     else
13794       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13795
13796     return buf;
13797 }
13798
13799
13800 /*
13801  * This is necessary because some C libraries aren't ANSI C compliant yet.
13802  */
13803 char *
13804 StrStr(string, match)
13805      char *string, *match;
13806 {
13807     int i, length;
13808
13809     length = strlen(match);
13810
13811     for (i = strlen(string) - length; i >= 0; i--, string++)
13812       if (!strncmp(match, string, length))
13813         return string;
13814
13815     return NULL;
13816 }
13817
13818 char *
13819 StrCaseStr(string, match)
13820      char *string, *match;
13821 {
13822     int i, j, length;
13823
13824     length = strlen(match);
13825
13826     for (i = strlen(string) - length; i >= 0; i--, string++) {
13827         for (j = 0; j < length; j++) {
13828             if (ToLower(match[j]) != ToLower(string[j]))
13829               break;
13830         }
13831         if (j == length) return string;
13832     }
13833
13834     return NULL;
13835 }
13836
13837 #ifndef _amigados
13838 int
13839 StrCaseCmp(s1, s2)
13840      char *s1, *s2;
13841 {
13842     char c1, c2;
13843
13844     for (;;) {
13845         c1 = ToLower(*s1++);
13846         c2 = ToLower(*s2++);
13847         if (c1 > c2) return 1;
13848         if (c1 < c2) return -1;
13849         if (c1 == NULLCHAR) return 0;
13850     }
13851 }
13852
13853
13854 int
13855 ToLower(c)
13856      int c;
13857 {
13858     return isupper(c) ? tolower(c) : c;
13859 }
13860
13861
13862 int
13863 ToUpper(c)
13864      int c;
13865 {
13866     return islower(c) ? toupper(c) : c;
13867 }
13868 #endif /* !_amigados    */
13869
13870 char *
13871 StrSave(s)
13872      char *s;
13873 {
13874     char *ret;
13875
13876     if ((ret = (char *) malloc(strlen(s) + 1))) {
13877         strcpy(ret, s);
13878     }
13879     return ret;
13880 }
13881
13882 char *
13883 StrSavePtr(s, savePtr)
13884      char *s, **savePtr;
13885 {
13886     if (*savePtr) {
13887         free(*savePtr);
13888     }
13889     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13890         strcpy(*savePtr, s);
13891     }
13892     return(*savePtr);
13893 }
13894
13895 char *
13896 PGNDate()
13897 {
13898     time_t clock;
13899     struct tm *tm;
13900     char buf[MSG_SIZ];
13901
13902     clock = time((time_t *)NULL);
13903     tm = localtime(&clock);
13904     sprintf(buf, "%04d.%02d.%02d",
13905             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13906     return StrSave(buf);
13907 }
13908
13909
13910 char *
13911 PositionToFEN(move, overrideCastling)
13912      int move;
13913      char *overrideCastling;
13914 {
13915     int i, j, fromX, fromY, toX, toY;
13916     int whiteToPlay;
13917     char buf[128];
13918     char *p, *q;
13919     int emptycount;
13920     ChessSquare piece;
13921
13922     whiteToPlay = (gameMode == EditPosition) ?
13923       !blackPlaysFirst : (move % 2 == 0);
13924     p = buf;
13925
13926     /* Piece placement data */
13927     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13928         emptycount = 0;
13929         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13930             if (boards[move][i][j] == EmptySquare) {
13931                 emptycount++;
13932             } else { ChessSquare piece = boards[move][i][j];
13933                 if (emptycount > 0) {
13934                     if(emptycount<10) /* [HGM] can be >= 10 */
13935                         *p++ = '0' + emptycount;
13936                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13937                     emptycount = 0;
13938                 }
13939                 if(PieceToChar(piece) == '+') {
13940                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13941                     *p++ = '+';
13942                     piece = (ChessSquare)(DEMOTED piece);
13943                 }
13944                 *p++ = PieceToChar(piece);
13945                 if(p[-1] == '~') {
13946                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13947                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13948                     *p++ = '~';
13949                 }
13950             }
13951         }
13952         if (emptycount > 0) {
13953             if(emptycount<10) /* [HGM] can be >= 10 */
13954                 *p++ = '0' + emptycount;
13955             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13956             emptycount = 0;
13957         }
13958         *p++ = '/';
13959     }
13960     *(p - 1) = ' ';
13961
13962     /* [HGM] print Crazyhouse or Shogi holdings */
13963     if( gameInfo.holdingsWidth ) {
13964         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13965         q = p;
13966         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13967             piece = boards[move][i][BOARD_WIDTH-1];
13968             if( piece != EmptySquare )
13969               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13970                   *p++ = PieceToChar(piece);
13971         }
13972         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13973             piece = boards[move][BOARD_HEIGHT-i-1][0];
13974             if( piece != EmptySquare )
13975               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13976                   *p++ = PieceToChar(piece);
13977         }
13978
13979         if( q == p ) *p++ = '-';
13980         *p++ = ']';
13981         *p++ = ' ';
13982     }
13983
13984     /* Active color */
13985     *p++ = whiteToPlay ? 'w' : 'b';
13986     *p++ = ' ';
13987
13988   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13989     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13990   } else {
13991   if(nrCastlingRights) {
13992      q = p;
13993      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13994        /* [HGM] write directly from rights */
13995            if(boards[move][CASTLING][2] != NoRights &&
13996               boards[move][CASTLING][0] != NoRights   )
13997                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13998            if(boards[move][CASTLING][2] != NoRights &&
13999               boards[move][CASTLING][1] != NoRights   )
14000                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14001            if(boards[move][CASTLING][5] != NoRights &&
14002               boards[move][CASTLING][3] != NoRights   )
14003                 *p++ = boards[move][CASTLING][3] + AAA;
14004            if(boards[move][CASTLING][5] != NoRights &&
14005               boards[move][CASTLING][4] != NoRights   )
14006                 *p++ = boards[move][CASTLING][4] + AAA;
14007      } else {
14008
14009         /* [HGM] write true castling rights */
14010         if( nrCastlingRights == 6 ) {
14011             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14012                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14013             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14014                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14015             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14016                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14017             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14018                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14019         }
14020      }
14021      if (q == p) *p++ = '-'; /* No castling rights */
14022      *p++ = ' ';
14023   }
14024
14025   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14026      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14027     /* En passant target square */
14028     if (move > backwardMostMove) {
14029         fromX = moveList[move - 1][0] - AAA;
14030         fromY = moveList[move - 1][1] - ONE;
14031         toX = moveList[move - 1][2] - AAA;
14032         toY = moveList[move - 1][3] - ONE;
14033         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14034             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14035             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14036             fromX == toX) {
14037             /* 2-square pawn move just happened */
14038             *p++ = toX + AAA;
14039             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14040         } else {
14041             *p++ = '-';
14042         }
14043     } else if(move == backwardMostMove) {
14044         // [HGM] perhaps we should always do it like this, and forget the above?
14045         if((signed char)boards[move][EP_STATUS] >= 0) {
14046             *p++ = boards[move][EP_STATUS] + AAA;
14047             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14048         } else {
14049             *p++ = '-';
14050         }
14051     } else {
14052         *p++ = '-';
14053     }
14054     *p++ = ' ';
14055   }
14056   }
14057
14058     /* [HGM] find reversible plies */
14059     {   int i = 0, j=move;
14060
14061         if (appData.debugMode) { int k;
14062             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14063             for(k=backwardMostMove; k<=forwardMostMove; k++)
14064                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14065
14066         }
14067
14068         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14069         if( j == backwardMostMove ) i += initialRulePlies;
14070         sprintf(p, "%d ", i);
14071         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14072     }
14073     /* Fullmove number */
14074     sprintf(p, "%d", (move / 2) + 1);
14075
14076     return StrSave(buf);
14077 }
14078
14079 Boolean
14080 ParseFEN(board, blackPlaysFirst, fen)
14081     Board board;
14082      int *blackPlaysFirst;
14083      char *fen;
14084 {
14085     int i, j;
14086     char *p;
14087     int emptycount;
14088     ChessSquare piece;
14089
14090     p = fen;
14091
14092     /* [HGM] by default clear Crazyhouse holdings, if present */
14093     if(gameInfo.holdingsWidth) {
14094        for(i=0; i<BOARD_HEIGHT; i++) {
14095            board[i][0]             = EmptySquare; /* black holdings */
14096            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14097            board[i][1]             = (ChessSquare) 0; /* black counts */
14098            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14099        }
14100     }
14101
14102     /* Piece placement data */
14103     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14104         j = 0;
14105         for (;;) {
14106             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14107                 if (*p == '/') p++;
14108                 emptycount = gameInfo.boardWidth - j;
14109                 while (emptycount--)
14110                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14111                 break;
14112 #if(BOARD_FILES >= 10)
14113             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14114                 p++; emptycount=10;
14115                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14116                 while (emptycount--)
14117                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14118 #endif
14119             } else if (isdigit(*p)) {
14120                 emptycount = *p++ - '0';
14121                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14122                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14123                 while (emptycount--)
14124                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14125             } else if (*p == '+' || isalpha(*p)) {
14126                 if (j >= gameInfo.boardWidth) return FALSE;
14127                 if(*p=='+') {
14128                     piece = CharToPiece(*++p);
14129                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14130                     piece = (ChessSquare) (PROMOTED piece ); p++;
14131                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14132                 } else piece = CharToPiece(*p++);
14133
14134                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14135                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14136                     piece = (ChessSquare) (PROMOTED piece);
14137                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14138                     p++;
14139                 }
14140                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14141             } else {
14142                 return FALSE;
14143             }
14144         }
14145     }
14146     while (*p == '/' || *p == ' ') p++;
14147
14148     /* [HGM] look for Crazyhouse holdings here */
14149     while(*p==' ') p++;
14150     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14151         if(*p == '[') p++;
14152         if(*p == '-' ) *p++; /* empty holdings */ else {
14153             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14154             /* if we would allow FEN reading to set board size, we would   */
14155             /* have to add holdings and shift the board read so far here   */
14156             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14157                 *p++;
14158                 if((int) piece >= (int) BlackPawn ) {
14159                     i = (int)piece - (int)BlackPawn;
14160                     i = PieceToNumber((ChessSquare)i);
14161                     if( i >= gameInfo.holdingsSize ) return FALSE;
14162                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14163                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14164                 } else {
14165                     i = (int)piece - (int)WhitePawn;
14166                     i = PieceToNumber((ChessSquare)i);
14167                     if( i >= gameInfo.holdingsSize ) return FALSE;
14168                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14169                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14170                 }
14171             }
14172         }
14173         if(*p == ']') *p++;
14174     }
14175
14176     while(*p == ' ') p++;
14177
14178     /* Active color */
14179     switch (*p++) {
14180       case 'w':
14181         *blackPlaysFirst = FALSE;
14182         break;
14183       case 'b':
14184         *blackPlaysFirst = TRUE;
14185         break;
14186       default:
14187         return FALSE;
14188     }
14189
14190     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14191     /* return the extra info in global variiables             */
14192
14193     /* set defaults in case FEN is incomplete */
14194     board[EP_STATUS] = EP_UNKNOWN;
14195     for(i=0; i<nrCastlingRights; i++ ) {
14196         board[CASTLING][i] =
14197             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14198     }   /* assume possible unless obviously impossible */
14199     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14200     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14201     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14202     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14203     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14204     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14205     FENrulePlies = 0;
14206
14207     while(*p==' ') p++;
14208     if(nrCastlingRights) {
14209       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14210           /* castling indicator present, so default becomes no castlings */
14211           for(i=0; i<nrCastlingRights; i++ ) {
14212                  board[CASTLING][i] = NoRights;
14213           }
14214       }
14215       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14216              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14217              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14218              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14219         char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14220
14221         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14222             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14223             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14224         }
14225         switch(c) {
14226           case'K':
14227               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14228               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14229               board[CASTLING][2] = whiteKingFile;
14230               break;
14231           case'Q':
14232               for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14233               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14234               board[CASTLING][2] = whiteKingFile;
14235               break;
14236           case'k':
14237               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14238               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14239               board[CASTLING][5] = blackKingFile;
14240               break;
14241           case'q':
14242               for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14243               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14244               board[CASTLING][5] = blackKingFile;
14245           case '-':
14246               break;
14247           default: /* FRC castlings */
14248               if(c >= 'a') { /* black rights */
14249                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14250                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14251                   if(i == BOARD_RGHT) break;
14252                   board[CASTLING][5] = i;
14253                   c -= AAA;
14254                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14255                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14256                   if(c > i)
14257                       board[CASTLING][3] = c;
14258                   else
14259                       board[CASTLING][4] = c;
14260               } else { /* white rights */
14261                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14262                     if(board[0][i] == WhiteKing) break;
14263                   if(i == BOARD_RGHT) break;
14264                   board[CASTLING][2] = i;
14265                   c -= AAA - 'a' + 'A';
14266                   if(board[0][c] >= WhiteKing) break;
14267                   if(c > i)
14268                       board[CASTLING][0] = c;
14269                   else
14270                       board[CASTLING][1] = c;
14271               }
14272         }
14273       }
14274     if (appData.debugMode) {
14275         fprintf(debugFP, "FEN castling rights:");
14276         for(i=0; i<nrCastlingRights; i++)
14277         fprintf(debugFP, " %d", board[CASTLING][i]);
14278         fprintf(debugFP, "\n");
14279     }
14280
14281       while(*p==' ') p++;
14282     }
14283
14284     /* read e.p. field in games that know e.p. capture */
14285     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14286        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14287       if(*p=='-') {
14288         p++; board[EP_STATUS] = EP_NONE;
14289       } else {
14290          char c = *p++ - AAA;
14291
14292          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14293          if(*p >= '0' && *p <='9') *p++;
14294          board[EP_STATUS] = c;
14295       }
14296     }
14297
14298
14299     if(sscanf(p, "%d", &i) == 1) {
14300         FENrulePlies = i; /* 50-move ply counter */
14301         /* (The move number is still ignored)    */
14302     }
14303
14304     return TRUE;
14305 }
14306
14307 void
14308 EditPositionPasteFEN(char *fen)
14309 {
14310   if (fen != NULL) {
14311     Board initial_position;
14312
14313     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14314       DisplayError(_("Bad FEN position in clipboard"), 0);
14315       return ;
14316     } else {
14317       int savedBlackPlaysFirst = blackPlaysFirst;
14318       EditPositionEvent();
14319       blackPlaysFirst = savedBlackPlaysFirst;
14320       CopyBoard(boards[0], initial_position);
14321       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14322       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14323       DisplayBothClocks();
14324       DrawPosition(FALSE, boards[currentMove]);
14325     }
14326   }
14327 }
14328
14329 static char cseq[12] = "\\   ";
14330
14331 Boolean set_cont_sequence(char *new_seq)
14332 {
14333     int len;
14334     Boolean ret;
14335
14336     // handle bad attempts to set the sequence
14337         if (!new_seq)
14338                 return 0; // acceptable error - no debug
14339
14340     len = strlen(new_seq);
14341     ret = (len > 0) && (len < sizeof(cseq));
14342     if (ret)
14343         strcpy(cseq, new_seq);
14344     else if (appData.debugMode)
14345         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14346     return ret;
14347 }
14348
14349 /*
14350     reformat a source message so words don't cross the width boundary.  internal
14351     newlines are not removed.  returns the wrapped size (no null character unless
14352     included in source message).  If dest is NULL, only calculate the size required
14353     for the dest buffer.  lp argument indicats line position upon entry, and it's
14354     passed back upon exit.
14355 */
14356 int wrap(char *dest, char *src, int count, int width, int *lp)
14357 {
14358     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14359
14360     cseq_len = strlen(cseq);
14361     old_line = line = *lp;
14362     ansi = len = clen = 0;
14363
14364     for (i=0; i < count; i++)
14365     {
14366         if (src[i] == '\033')
14367             ansi = 1;
14368
14369         // if we hit the width, back up
14370         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14371         {
14372             // store i & len in case the word is too long
14373             old_i = i, old_len = len;
14374
14375             // find the end of the last word
14376             while (i && src[i] != ' ' && src[i] != '\n')
14377             {
14378                 i--;
14379                 len--;
14380             }
14381
14382             // word too long?  restore i & len before splitting it
14383             if ((old_i-i+clen) >= width)
14384             {
14385                 i = old_i;
14386                 len = old_len;
14387             }
14388
14389             // extra space?
14390             if (i && src[i-1] == ' ')
14391                 len--;
14392
14393             if (src[i] != ' ' && src[i] != '\n')
14394             {
14395                 i--;
14396                 if (len)
14397                     len--;
14398             }
14399
14400             // now append the newline and continuation sequence
14401             if (dest)
14402                 dest[len] = '\n';
14403             len++;
14404             if (dest)
14405                 strncpy(dest+len, cseq, cseq_len);
14406             len += cseq_len;
14407             line = cseq_len;
14408             clen = cseq_len;
14409             continue;
14410         }
14411
14412         if (dest)
14413             dest[len] = src[i];
14414         len++;
14415         if (!ansi)
14416             line++;
14417         if (src[i] == '\n')
14418             line = 0;
14419         if (src[i] == 'm')
14420             ansi = 0;
14421     }
14422     if (dest && appData.debugMode)
14423     {
14424         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14425             count, width, line, len, *lp);
14426         show_bytes(debugFP, src, count);
14427         fprintf(debugFP, "\ndest: ");
14428         show_bytes(debugFP, dest, len);
14429         fprintf(debugFP, "\n");
14430     }
14431     *lp = dest ? line : old_line;
14432
14433     return len;
14434 }
14435
14436 // [HGM] vari: routines for shelving variations
14437
14438 void 
14439 PushTail(int firstMove, int lastMove)
14440 {
14441         int i, j, nrMoves = lastMove - firstMove;
14442
14443         if(appData.icsActive) { // only in local mode
14444                 forwardMostMove = currentMove; // mimic old ICS behavior
14445                 return;
14446         }
14447         if(storedGames >= MAX_VARIATIONS-1) return;
14448
14449         // push current tail of game on stack
14450         savedResult[storedGames] = gameInfo.result;
14451         savedDetails[storedGames] = gameInfo.resultDetails;
14452         gameInfo.resultDetails = NULL;
14453         savedFirst[storedGames] = firstMove;
14454         savedLast [storedGames] = lastMove;
14455         savedFramePtr[storedGames] = framePtr;
14456         framePtr -= nrMoves; // reserve space for the boards
14457         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14458             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14459             for(j=0; j<MOVE_LEN; j++)
14460                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14461             for(j=0; j<2*MOVE_LEN; j++)
14462                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14463             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14464             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14465             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14466             pvInfoList[firstMove+i-1].depth = 0;
14467             commentList[framePtr+i] = commentList[firstMove+i];
14468             commentList[firstMove+i] = NULL;
14469         }
14470
14471         storedGames++;
14472         forwardMostMove = currentMove; // truncte game so we can start variation
14473         if(storedGames == 1) GreyRevert(FALSE);
14474 }
14475
14476 Boolean
14477 PopTail(Boolean annotate)
14478 {
14479         int i, j, nrMoves;
14480         char buf[8000], moveBuf[20];
14481
14482         if(appData.icsActive) return FALSE; // only in local mode
14483         if(!storedGames) return FALSE; // sanity
14484
14485         storedGames--;
14486         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14487         nrMoves = savedLast[storedGames] - currentMove;
14488         if(annotate) {
14489                 int cnt = 10;
14490                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14491                 else strcpy(buf, "(");
14492                 for(i=currentMove; i<forwardMostMove; i++) {
14493                         if(WhiteOnMove(i))
14494                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14495                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14496                         strcat(buf, moveBuf);
14497                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14498                 }
14499                 strcat(buf, ")");
14500         }
14501         for(i=1; i<nrMoves; i++) { // copy last variation back
14502             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14503             for(j=0; j<MOVE_LEN; j++)
14504                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14505             for(j=0; j<2*MOVE_LEN; j++)
14506                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14507             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14508             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14509             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14510             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14511             commentList[currentMove+i] = commentList[framePtr+i];
14512             commentList[framePtr+i] = NULL;
14513         }
14514         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14515         framePtr = savedFramePtr[storedGames];
14516         gameInfo.result = savedResult[storedGames];
14517         if(gameInfo.resultDetails != NULL) {
14518             free(gameInfo.resultDetails);
14519       }
14520         gameInfo.resultDetails = savedDetails[storedGames];
14521         forwardMostMove = currentMove + nrMoves;
14522         if(storedGames == 0) GreyRevert(TRUE);
14523         return TRUE;
14524 }
14525
14526 void 
14527 CleanupTail()
14528 {       // remove all shelved variations
14529         int i;
14530         for(i=0; i<storedGames; i++) {
14531             if(savedDetails[i])
14532                 free(savedDetails[i]);
14533             savedDetails[i] = NULL;
14534         }
14535         for(i=framePtr; i<MAX_MOVES; i++) {
14536                 if(commentList[i]) free(commentList[i]);
14537                 commentList[i] = NULL;
14538         }
14539         framePtr = MAX_MOVES-1;
14540         storedGames = 0;
14541 }