Insert autoKibitz continuation lines at end of line
[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, 2010 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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "* shouts:") ||
2744                                            looking_at(buf, &i, "* c-shouts:") ||
2745                                            looking_at(buf, &i, "--> * ") ||
2746                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2747                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2748                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2749                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2750                 int p;
2751                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2752                 chattingPartner = -1;
2753
2754                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2755                 for(p=0; p<MAX_CHAT; p++) {
2756                     if(channel == atoi(chatPartner[p])) {
2757                     talker[0] = '['; strcat(talker, "] ");
2758                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2759                     chattingPartner = p; break;
2760                     }
2761                 } else
2762                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2763                 for(p=0; p<MAX_CHAT; p++) {
2764                     if(!strcmp("whispers", chatPartner[p])) {
2765                         talker[0] = '['; strcat(talker, "] ");
2766                         chattingPartner = p; break;
2767                     }
2768                 } else
2769                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2770                 for(p=0; p<MAX_CHAT; p++) {
2771                     if(!strcmp("shouts", chatPartner[p])) {
2772                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2773                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2774                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2775                         chattingPartner = p; break;
2776                     }
2777                 }
2778                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2779                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2780                     talker[0] = 0; Colorize(ColorTell, FALSE);
2781                     chattingPartner = p; break;
2782                 }
2783                 if(chattingPartner<0) i = oldi; else {
2784                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2785                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2786                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2787                     started = STARTED_COMMENT;
2788                     parse_pos = 0; parse[0] = NULLCHAR;
2789                     savingComment = 3 + chattingPartner; // counts as TRUE
2790                     suppressKibitz = TRUE;
2791                     continue;
2792                 }
2793             } // [HGM] chat: end of patch
2794
2795             if (appData.zippyTalk || appData.zippyPlay) {
2796                 /* [DM] Backup address for color zippy lines */
2797                 backup = i;
2798 #if ZIPPY
2799        #ifdef WIN32
2800                if (loggedOn == TRUE)
2801                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2802                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2803        #else
2804                 if (ZippyControl(buf, &i) ||
2805                     ZippyConverse(buf, &i) ||
2806                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2807                       loggedOn = TRUE;
2808                       if (!appData.colorize) continue;
2809                 }
2810        #endif
2811 #endif
2812             } // [DM] 'else { ' deleted
2813                 if (
2814                     /* Regular tells and says */
2815                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2816                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2817                     looking_at(buf, &i, "* says: ") ||
2818                     /* Don't color "message" or "messages" output */
2819                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2820                     looking_at(buf, &i, "*. * at *:*: ") ||
2821                     looking_at(buf, &i, "--* (*:*): ") ||
2822                     /* Message notifications (same color as tells) */
2823                     looking_at(buf, &i, "* has left a message ") ||
2824                     looking_at(buf, &i, "* just sent you a message:\n") ||
2825                     /* Whispers and kibitzes */
2826                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2827                     looking_at(buf, &i, "* kibitzes: ") ||
2828                     /* Channel tells */
2829                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2830
2831                   if (tkind == 1 && strchr(star_match[0], ':')) {
2832                       /* Avoid "tells you:" spoofs in channels */
2833                      tkind = 3;
2834                   }
2835                   if (star_match[0][0] == NULLCHAR ||
2836                       strchr(star_match[0], ' ') ||
2837                       (tkind == 3 && strchr(star_match[1], ' '))) {
2838                     /* Reject bogus matches */
2839                     i = oldi;
2840                   } else {
2841                     if (appData.colorize) {
2842                       if (oldi > next_out) {
2843                         SendToPlayer(&buf[next_out], oldi - next_out);
2844                         next_out = oldi;
2845                       }
2846                       switch (tkind) {
2847                       case 1:
2848                         Colorize(ColorTell, FALSE);
2849                         curColor = ColorTell;
2850                         break;
2851                       case 2:
2852                         Colorize(ColorKibitz, FALSE);
2853                         curColor = ColorKibitz;
2854                         break;
2855                       case 3:
2856                         p = strrchr(star_match[1], '(');
2857                         if (p == NULL) {
2858                           p = star_match[1];
2859                         } else {
2860                           p++;
2861                         }
2862                         if (atoi(p) == 1) {
2863                           Colorize(ColorChannel1, FALSE);
2864                           curColor = ColorChannel1;
2865                         } else {
2866                           Colorize(ColorChannel, FALSE);
2867                           curColor = ColorChannel;
2868                         }
2869                         break;
2870                       case 5:
2871                         curColor = ColorNormal;
2872                         break;
2873                       }
2874                     }
2875                     if (started == STARTED_NONE && appData.autoComment &&
2876                         (gameMode == IcsObserving ||
2877                          gameMode == IcsPlayingWhite ||
2878                          gameMode == IcsPlayingBlack)) {
2879                       parse_pos = i - oldi;
2880                       memcpy(parse, &buf[oldi], parse_pos);
2881                       parse[parse_pos] = NULLCHAR;
2882                       started = STARTED_COMMENT;
2883                       savingComment = TRUE;
2884                     } else {
2885                       started = STARTED_CHATTER;
2886                       savingComment = FALSE;
2887                     }
2888                     loggedOn = TRUE;
2889                     continue;
2890                   }
2891                 }
2892
2893                 if (looking_at(buf, &i, "* s-shouts: ") ||
2894                     looking_at(buf, &i, "* c-shouts: ")) {
2895                     if (appData.colorize) {
2896                         if (oldi > next_out) {
2897                             SendToPlayer(&buf[next_out], oldi - next_out);
2898                             next_out = oldi;
2899                         }
2900                         Colorize(ColorSShout, FALSE);
2901                         curColor = ColorSShout;
2902                     }
2903                     loggedOn = TRUE;
2904                     started = STARTED_CHATTER;
2905                     continue;
2906                 }
2907
2908                 if (looking_at(buf, &i, "--->")) {
2909                     loggedOn = TRUE;
2910                     continue;
2911                 }
2912
2913                 if (looking_at(buf, &i, "* shouts: ") ||
2914                     looking_at(buf, &i, "--> ")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorShout, FALSE);
2921                         curColor = ColorShout;
2922                     }
2923                     loggedOn = TRUE;
2924                     started = STARTED_CHATTER;
2925                     continue;
2926                 }
2927
2928                 if (looking_at( buf, &i, "Challenge:")) {
2929                     if (appData.colorize) {
2930                         if (oldi > next_out) {
2931                             SendToPlayer(&buf[next_out], oldi - next_out);
2932                             next_out = oldi;
2933                         }
2934                         Colorize(ColorChallenge, FALSE);
2935                         curColor = ColorChallenge;
2936                     }
2937                     loggedOn = TRUE;
2938                     continue;
2939                 }
2940
2941                 if (looking_at(buf, &i, "* offers you") ||
2942                     looking_at(buf, &i, "* offers to be") ||
2943                     looking_at(buf, &i, "* would like to") ||
2944                     looking_at(buf, &i, "* requests to") ||
2945                     looking_at(buf, &i, "Your opponent offers") ||
2946                     looking_at(buf, &i, "Your opponent requests")) {
2947
2948                     if (appData.colorize) {
2949                         if (oldi > next_out) {
2950                             SendToPlayer(&buf[next_out], oldi - next_out);
2951                             next_out = oldi;
2952                         }
2953                         Colorize(ColorRequest, FALSE);
2954                         curColor = ColorRequest;
2955                     }
2956                     continue;
2957                 }
2958
2959                 if (looking_at(buf, &i, "* (*) seeking")) {
2960                     if (appData.colorize) {
2961                         if (oldi > next_out) {
2962                             SendToPlayer(&buf[next_out], oldi - next_out);
2963                             next_out = oldi;
2964                         }
2965                         Colorize(ColorSeek, FALSE);
2966                         curColor = ColorSeek;
2967                     }
2968                     continue;
2969             }
2970
2971             if (looking_at(buf, &i, "\\   ")) {
2972                 if (prevColor != ColorNormal) {
2973                     if (oldi > next_out) {
2974                         SendToPlayer(&buf[next_out], oldi - next_out);
2975                         next_out = oldi;
2976                     }
2977                     Colorize(prevColor, TRUE);
2978                     curColor = prevColor;
2979                 }
2980                 if (savingComment) {
2981                     parse_pos = i - oldi;
2982                     memcpy(parse, &buf[oldi], parse_pos);
2983                     parse[parse_pos] = NULLCHAR;
2984                     started = STARTED_COMMENT;
2985                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2986                         chattingPartner = savingComment - 3; // kludge to remember the box
2987                 } else {
2988                     started = STARTED_CHATTER;
2989                 }
2990                 continue;
2991             }
2992
2993             if (looking_at(buf, &i, "Black Strength :") ||
2994                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2995                 looking_at(buf, &i, "<10>") ||
2996                 looking_at(buf, &i, "#@#")) {
2997                 /* Wrong board style */
2998                 loggedOn = TRUE;
2999                 SendToICS(ics_prefix);
3000                 SendToICS("set style 12\n");
3001                 SendToICS(ics_prefix);
3002                 SendToICS("refresh\n");
3003                 continue;
3004             }
3005             
3006             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3007                 ICSInitScript();
3008                 have_sent_ICS_logon = 1;
3009                 continue;
3010             }
3011               
3012             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3013                 (looking_at(buf, &i, "\n<12> ") ||
3014                  looking_at(buf, &i, "<12> "))) {
3015                 loggedOn = TRUE;
3016                 if (oldi > next_out) {
3017                     SendToPlayer(&buf[next_out], oldi - next_out);
3018                 }
3019                 next_out = i;
3020                 started = STARTED_BOARD;
3021                 parse_pos = 0;
3022                 continue;
3023             }
3024
3025             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3026                 looking_at(buf, &i, "<b1> ")) {
3027                 if (oldi > next_out) {
3028                     SendToPlayer(&buf[next_out], oldi - next_out);
3029                 }
3030                 next_out = i;
3031                 started = STARTED_HOLDINGS;
3032                 parse_pos = 0;
3033                 continue;
3034             }
3035
3036             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3037                 loggedOn = TRUE;
3038                 /* Header for a move list -- first line */
3039
3040                 switch (ics_getting_history) {
3041                   case H_FALSE:
3042                     switch (gameMode) {
3043                       case IcsIdle:
3044                       case BeginningOfGame:
3045                         /* User typed "moves" or "oldmoves" while we
3046                            were idle.  Pretend we asked for these
3047                            moves and soak them up so user can step
3048                            through them and/or save them.
3049                            */
3050                         Reset(FALSE, TRUE);
3051                         gameMode = IcsObserving;
3052                         ModeHighlight();
3053                         ics_gamenum = -1;
3054                         ics_getting_history = H_GOT_UNREQ_HEADER;
3055                         break;
3056                       case EditGame: /*?*/
3057                       case EditPosition: /*?*/
3058                         /* Should above feature work in these modes too? */
3059                         /* For now it doesn't */
3060                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3061                         break;
3062                       default:
3063                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3064                         break;
3065                     }
3066                     break;
3067                   case H_REQUESTED:
3068                     /* Is this the right one? */
3069                     if (gameInfo.white && gameInfo.black &&
3070                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3071                         strcmp(gameInfo.black, star_match[2]) == 0) {
3072                         /* All is well */
3073                         ics_getting_history = H_GOT_REQ_HEADER;
3074                     }
3075                     break;
3076                   case H_GOT_REQ_HEADER:
3077                   case H_GOT_UNREQ_HEADER:
3078                   case H_GOT_UNWANTED_HEADER:
3079                   case H_GETTING_MOVES:
3080                     /* Should not happen */
3081                     DisplayError(_("Error gathering move list: two headers"), 0);
3082                     ics_getting_history = H_FALSE;
3083                     break;
3084                 }
3085
3086                 /* Save player ratings into gameInfo if needed */
3087                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3088                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3089                     (gameInfo.whiteRating == -1 ||
3090                      gameInfo.blackRating == -1)) {
3091
3092                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3093                     gameInfo.blackRating = string_to_rating(star_match[3]);
3094                     if (appData.debugMode)
3095                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3096                               gameInfo.whiteRating, gameInfo.blackRating);
3097                 }
3098                 continue;
3099             }
3100
3101             if (looking_at(buf, &i,
3102               "* * match, initial time: * minute*, increment: * second")) {
3103                 /* Header for a move list -- second line */
3104                 /* Initial board will follow if this is a wild game */
3105                 if (gameInfo.event != NULL) free(gameInfo.event);
3106                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3107                 gameInfo.event = StrSave(str);
3108                 /* [HGM] we switched variant. Translate boards if needed. */
3109                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3110                 continue;
3111             }
3112
3113             if (looking_at(buf, &i, "Move  ")) {
3114                 /* Beginning of a move list */
3115                 switch (ics_getting_history) {
3116                   case H_FALSE:
3117                     /* Normally should not happen */
3118                     /* Maybe user hit reset while we were parsing */
3119                     break;
3120                   case H_REQUESTED:
3121                     /* Happens if we are ignoring a move list that is not
3122                      * the one we just requested.  Common if the user
3123                      * tries to observe two games without turning off
3124                      * getMoveList */
3125                     break;
3126                   case H_GETTING_MOVES:
3127                     /* Should not happen */
3128                     DisplayError(_("Error gathering move list: nested"), 0);
3129                     ics_getting_history = H_FALSE;
3130                     break;
3131                   case H_GOT_REQ_HEADER:
3132                     ics_getting_history = H_GETTING_MOVES;
3133                     started = STARTED_MOVES;
3134                     parse_pos = 0;
3135                     if (oldi > next_out) {
3136                         SendToPlayer(&buf[next_out], oldi - next_out);
3137                     }
3138                     break;
3139                   case H_GOT_UNREQ_HEADER:
3140                     ics_getting_history = H_GETTING_MOVES;
3141                     started = STARTED_MOVES_NOHIDE;
3142                     parse_pos = 0;
3143                     break;
3144                   case H_GOT_UNWANTED_HEADER:
3145                     ics_getting_history = H_FALSE;
3146                     break;
3147                 }
3148                 continue;
3149             }                           
3150             
3151             if (looking_at(buf, &i, "% ") ||
3152                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3153                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3154                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3155                     soughtPending = FALSE;
3156                     seekGraphUp = TRUE;
3157                     DrawSeekGraph();
3158                 }
3159                 if(suppressKibitz) next_out = i;
3160                 savingComment = FALSE;
3161                 suppressKibitz = 0;
3162                 switch (started) {
3163                   case STARTED_MOVES:
3164                   case STARTED_MOVES_NOHIDE:
3165                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3166                     parse[parse_pos + i - oldi] = NULLCHAR;
3167                     ParseGameHistory(parse);
3168 #if ZIPPY
3169                     if (appData.zippyPlay && first.initDone) {
3170                         FeedMovesToProgram(&first, forwardMostMove);
3171                         if (gameMode == IcsPlayingWhite) {
3172                             if (WhiteOnMove(forwardMostMove)) {
3173                                 if (first.sendTime) {
3174                                   if (first.useColors) {
3175                                     SendToProgram("black\n", &first); 
3176                                   }
3177                                   SendTimeRemaining(&first, TRUE);
3178                                 }
3179                                 if (first.useColors) {
3180                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3181                                 }
3182                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3183                                 first.maybeThinking = TRUE;
3184                             } else {
3185                                 if (first.usePlayother) {
3186                                   if (first.sendTime) {
3187                                     SendTimeRemaining(&first, TRUE);
3188                                   }
3189                                   SendToProgram("playother\n", &first);
3190                                   firstMove = FALSE;
3191                                 } else {
3192                                   firstMove = TRUE;
3193                                 }
3194                             }
3195                         } else if (gameMode == IcsPlayingBlack) {
3196                             if (!WhiteOnMove(forwardMostMove)) {
3197                                 if (first.sendTime) {
3198                                   if (first.useColors) {
3199                                     SendToProgram("white\n", &first);
3200                                   }
3201                                   SendTimeRemaining(&first, FALSE);
3202                                 }
3203                                 if (first.useColors) {
3204                                   SendToProgram("black\n", &first);
3205                                 }
3206                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3207                                 first.maybeThinking = TRUE;
3208                             } else {
3209                                 if (first.usePlayother) {
3210                                   if (first.sendTime) {
3211                                     SendTimeRemaining(&first, FALSE);
3212                                   }
3213                                   SendToProgram("playother\n", &first);
3214                                   firstMove = FALSE;
3215                                 } else {
3216                                   firstMove = TRUE;
3217                                 }
3218                             }
3219                         }                       
3220                     }
3221 #endif
3222                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3223                         /* Moves came from oldmoves or moves command
3224                            while we weren't doing anything else.
3225                            */
3226                         currentMove = forwardMostMove;
3227                         ClearHighlights();/*!!could figure this out*/
3228                         flipView = appData.flipView;
3229                         DrawPosition(TRUE, boards[currentMove]);
3230                         DisplayBothClocks();
3231                         sprintf(str, "%s vs. %s",
3232                                 gameInfo.white, gameInfo.black);
3233                         DisplayTitle(str);
3234                         gameMode = IcsIdle;
3235                     } else {
3236                         /* Moves were history of an active game */
3237                         if (gameInfo.resultDetails != NULL) {
3238                             free(gameInfo.resultDetails);
3239                             gameInfo.resultDetails = NULL;
3240                         }
3241                     }
3242                     HistorySet(parseList, backwardMostMove,
3243                                forwardMostMove, currentMove-1);
3244                     DisplayMove(currentMove - 1);
3245                     if (started == STARTED_MOVES) next_out = i;
3246                     started = STARTED_NONE;
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249
3250                   case STARTED_OBSERVE:
3251                     started = STARTED_NONE;
3252                     SendToICS(ics_prefix);
3253                     SendToICS("refresh\n");
3254                     break;
3255
3256                   default:
3257                     break;
3258                 }
3259                 if(bookHit) { // [HGM] book: simulate book reply
3260                     static char bookMove[MSG_SIZ]; // a bit generous?
3261
3262                     programStats.nodes = programStats.depth = programStats.time = 
3263                     programStats.score = programStats.got_only_move = 0;
3264                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3265
3266                     strcpy(bookMove, "move ");
3267                     strcat(bookMove, bookHit);
3268                     HandleMachineMove(bookMove, &first);
3269                 }
3270                 continue;
3271             }
3272             
3273             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3274                  started == STARTED_HOLDINGS ||
3275                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3276                 /* Accumulate characters in move list or board */
3277                 parse[parse_pos++] = buf[i];
3278             }
3279             
3280             /* Start of game messages.  Mostly we detect start of game
3281                when the first board image arrives.  On some versions
3282                of the ICS, though, we need to do a "refresh" after starting
3283                to observe in order to get the current board right away. */
3284             if (looking_at(buf, &i, "Adding game * to observation list")) {
3285                 started = STARTED_OBSERVE;
3286                 continue;
3287             }
3288
3289             /* Handle auto-observe */
3290             if (appData.autoObserve &&
3291                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3292                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3293                 char *player;
3294                 /* Choose the player that was highlighted, if any. */
3295                 if (star_match[0][0] == '\033' ||
3296                     star_match[1][0] != '\033') {
3297                     player = star_match[0];
3298                 } else {
3299                     player = star_match[2];
3300                 }
3301                 sprintf(str, "%sobserve %s\n",
3302                         ics_prefix, StripHighlightAndTitle(player));
3303                 SendToICS(str);
3304
3305                 /* Save ratings from notify string */
3306                 strcpy(player1Name, star_match[0]);
3307                 player1Rating = string_to_rating(star_match[1]);
3308                 strcpy(player2Name, star_match[2]);
3309                 player2Rating = string_to_rating(star_match[3]);
3310
3311                 if (appData.debugMode)
3312                   fprintf(debugFP, 
3313                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3314                           player1Name, player1Rating,
3315                           player2Name, player2Rating);
3316
3317                 continue;
3318             }
3319
3320             /* Deal with automatic examine mode after a game,
3321                and with IcsObserving -> IcsExamining transition */
3322             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3323                 looking_at(buf, &i, "has made you an examiner of game *")) {
3324
3325                 int gamenum = atoi(star_match[0]);
3326                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3327                     gamenum == ics_gamenum) {
3328                     /* We were already playing or observing this game;
3329                        no need to refetch history */
3330                     gameMode = IcsExamining;
3331                     if (pausing) {
3332                         pauseExamForwardMostMove = forwardMostMove;
3333                     } else if (currentMove < forwardMostMove) {
3334                         ForwardInner(forwardMostMove);
3335                     }
3336                 } else {
3337                     /* I don't think this case really can happen */
3338                     SendToICS(ics_prefix);
3339                     SendToICS("refresh\n");
3340                 }
3341                 continue;
3342             }    
3343             
3344             /* Error messages */
3345 //          if (ics_user_moved) {
3346             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3347                 if (looking_at(buf, &i, "Illegal move") ||
3348                     looking_at(buf, &i, "Not a legal move") ||
3349                     looking_at(buf, &i, "Your king is in check") ||
3350                     looking_at(buf, &i, "It isn't your turn") ||
3351                     looking_at(buf, &i, "It is not your move")) {
3352                     /* Illegal move */
3353                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3354                         currentMove = forwardMostMove-1;
3355                         DisplayMove(currentMove - 1); /* before DMError */
3356                         DrawPosition(FALSE, boards[currentMove]);
3357                         SwitchClocks(forwardMostMove-1); // [HGM] race
3358                         DisplayBothClocks();
3359                     }
3360                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3361                     ics_user_moved = 0;
3362                     continue;
3363                 }
3364             }
3365
3366             if (looking_at(buf, &i, "still have time") ||
3367                 looking_at(buf, &i, "not out of time") ||
3368                 looking_at(buf, &i, "either player is out of time") ||
3369                 looking_at(buf, &i, "has timeseal; checking")) {
3370                 /* We must have called his flag a little too soon */
3371                 whiteFlag = blackFlag = FALSE;
3372                 continue;
3373             }
3374
3375             if (looking_at(buf, &i, "added * seconds to") ||
3376                 looking_at(buf, &i, "seconds were added to")) {
3377                 /* Update the clocks */
3378                 SendToICS(ics_prefix);
3379                 SendToICS("refresh\n");
3380                 continue;
3381             }
3382
3383             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3384                 ics_clock_paused = TRUE;
3385                 StopClocks();
3386                 continue;
3387             }
3388
3389             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3390                 ics_clock_paused = FALSE;
3391                 StartClocks();
3392                 continue;
3393             }
3394
3395             /* Grab player ratings from the Creating: message.
3396                Note we have to check for the special case when
3397                the ICS inserts things like [white] or [black]. */
3398             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3399                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3400                 /* star_matches:
3401                    0    player 1 name (not necessarily white)
3402                    1    player 1 rating
3403                    2    empty, white, or black (IGNORED)
3404                    3    player 2 name (not necessarily black)
3405                    4    player 2 rating
3406                    
3407                    The names/ratings are sorted out when the game
3408                    actually starts (below).
3409                 */
3410                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3411                 player1Rating = string_to_rating(star_match[1]);
3412                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3413                 player2Rating = string_to_rating(star_match[4]);
3414
3415                 if (appData.debugMode)
3416                   fprintf(debugFP, 
3417                           "Ratings from 'Creating:' %s %d, %s %d\n",
3418                           player1Name, player1Rating,
3419                           player2Name, player2Rating);
3420
3421                 continue;
3422             }
3423             
3424             /* Improved generic start/end-of-game messages */
3425             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3426                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3427                 /* If tkind == 0: */
3428                 /* star_match[0] is the game number */
3429                 /*           [1] is the white player's name */
3430                 /*           [2] is the black player's name */
3431                 /* For end-of-game: */
3432                 /*           [3] is the reason for the game end */
3433                 /*           [4] is a PGN end game-token, preceded by " " */
3434                 /* For start-of-game: */
3435                 /*           [3] begins with "Creating" or "Continuing" */
3436                 /*           [4] is " *" or empty (don't care). */
3437                 int gamenum = atoi(star_match[0]);
3438                 char *whitename, *blackname, *why, *endtoken;
3439                 ChessMove endtype = (ChessMove) 0;
3440
3441                 if (tkind == 0) {
3442                   whitename = star_match[1];
3443                   blackname = star_match[2];
3444                   why = star_match[3];
3445                   endtoken = star_match[4];
3446                 } else {
3447                   whitename = star_match[1];
3448                   blackname = star_match[3];
3449                   why = star_match[5];
3450                   endtoken = star_match[6];
3451                 }
3452
3453                 /* Game start messages */
3454                 if (strncmp(why, "Creating ", 9) == 0 ||
3455                     strncmp(why, "Continuing ", 11) == 0) {
3456                     gs_gamenum = gamenum;
3457                     strcpy(gs_kind, strchr(why, ' ') + 1);
3458                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3459 #if ZIPPY
3460                     if (appData.zippyPlay) {
3461                         ZippyGameStart(whitename, blackname);
3462                     }
3463 #endif /*ZIPPY*/
3464                     continue;
3465                 }
3466
3467                 /* Game end messages */
3468                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3469                     ics_gamenum != gamenum) {
3470                     continue;
3471                 }
3472                 while (endtoken[0] == ' ') endtoken++;
3473                 switch (endtoken[0]) {
3474                   case '*':
3475                   default:
3476                     endtype = GameUnfinished;
3477                     break;
3478                   case '0':
3479                     endtype = BlackWins;
3480                     break;
3481                   case '1':
3482                     if (endtoken[1] == '/')
3483                       endtype = GameIsDrawn;
3484                     else
3485                       endtype = WhiteWins;
3486                     break;
3487                 }
3488                 GameEnds(endtype, why, GE_ICS);
3489 #if ZIPPY
3490                 if (appData.zippyPlay && first.initDone) {
3491                     ZippyGameEnd(endtype, why);
3492                     if (first.pr == NULL) {
3493                       /* Start the next process early so that we'll
3494                          be ready for the next challenge */
3495                       StartChessProgram(&first);
3496                     }
3497                     /* Send "new" early, in case this command takes
3498                        a long time to finish, so that we'll be ready
3499                        for the next challenge. */
3500                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3501                     Reset(TRUE, TRUE);
3502                 }
3503 #endif /*ZIPPY*/
3504                 continue;
3505             }
3506
3507             if (looking_at(buf, &i, "Removing game * from observation") ||
3508                 looking_at(buf, &i, "no longer observing game *") ||
3509                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3510                 if (gameMode == IcsObserving &&
3511                     atoi(star_match[0]) == ics_gamenum)
3512                   {
3513                       /* icsEngineAnalyze */
3514                       if (appData.icsEngineAnalyze) {
3515                             ExitAnalyzeMode();
3516                             ModeHighlight();
3517                       }
3518                       StopClocks();
3519                       gameMode = IcsIdle;
3520                       ics_gamenum = -1;
3521                       ics_user_moved = FALSE;
3522                   }
3523                 continue;
3524             }
3525
3526             if (looking_at(buf, &i, "no longer examining game *")) {
3527                 if (gameMode == IcsExamining &&
3528                     atoi(star_match[0]) == ics_gamenum)
3529                   {
3530                       gameMode = IcsIdle;
3531                       ics_gamenum = -1;
3532                       ics_user_moved = FALSE;
3533                   }
3534                 continue;
3535             }
3536
3537             /* Advance leftover_start past any newlines we find,
3538                so only partial lines can get reparsed */
3539             if (looking_at(buf, &i, "\n")) {
3540                 prevColor = curColor;
3541                 if (curColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(ColorNormal, FALSE);
3547                     curColor = ColorNormal;
3548                 }
3549                 if (started == STARTED_BOARD) {
3550                     started = STARTED_NONE;
3551                     parse[parse_pos] = NULLCHAR;
3552                     ParseBoard12(parse);
3553                     ics_user_moved = 0;
3554
3555                     /* Send premove here */
3556                     if (appData.premove) {
3557                       char str[MSG_SIZ];
3558                       if (currentMove == 0 &&
3559                           gameMode == IcsPlayingWhite &&
3560                           appData.premoveWhite) {
3561                         sprintf(str, "%s\n", appData.premoveWhiteText);
3562                         if (appData.debugMode)
3563                           fprintf(debugFP, "Sending premove:\n");
3564                         SendToICS(str);
3565                       } else if (currentMove == 1 &&
3566                                  gameMode == IcsPlayingBlack &&
3567                                  appData.premoveBlack) {
3568                         sprintf(str, "%s\n", appData.premoveBlackText);
3569                         if (appData.debugMode)
3570                           fprintf(debugFP, "Sending premove:\n");
3571                         SendToICS(str);
3572                       } else if (gotPremove) {
3573                         gotPremove = 0;
3574                         ClearPremoveHighlights();
3575                         if (appData.debugMode)
3576                           fprintf(debugFP, "Sending premove:\n");
3577                           UserMoveEvent(premoveFromX, premoveFromY, 
3578                                         premoveToX, premoveToY, 
3579                                         premovePromoChar);
3580                       }
3581                     }
3582
3583                     /* Usually suppress following prompt */
3584                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3585                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3586                         if (looking_at(buf, &i, "*% ")) {
3587                             savingComment = FALSE;
3588                             suppressKibitz = 0;
3589                         }
3590                     }
3591                     next_out = i;
3592                 } else if (started == STARTED_HOLDINGS) {
3593                     int gamenum;
3594                     char new_piece[MSG_SIZ];
3595                     started = STARTED_NONE;
3596                     parse[parse_pos] = NULLCHAR;
3597                     if (appData.debugMode)
3598                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3599                                                         parse, currentMove);
3600                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3601                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3602                         if (gameInfo.variant == VariantNormal) {
3603                           /* [HGM] We seem to switch variant during a game!
3604                            * Presumably no holdings were displayed, so we have
3605                            * to move the position two files to the right to
3606                            * create room for them!
3607                            */
3608                           VariantClass newVariant;
3609                           switch(gameInfo.boardWidth) { // base guess on board width
3610                                 case 9:  newVariant = VariantShogi; break;
3611                                 case 10: newVariant = VariantGreat; break;
3612                                 default: newVariant = VariantCrazyhouse; break;
3613                           }
3614                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3615                           /* Get a move list just to see the header, which
3616                              will tell us whether this is really bug or zh */
3617                           if (ics_getting_history == H_FALSE) {
3618                             ics_getting_history = H_REQUESTED;
3619                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3620                             SendToICS(str);
3621                           }
3622                         }
3623                         new_piece[0] = NULLCHAR;
3624                         sscanf(parse, "game %d white [%s black [%s <- %s",
3625                                &gamenum, white_holding, black_holding,
3626                                new_piece);
3627                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3628                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3629                         /* [HGM] copy holdings to board holdings area */
3630                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3631                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3632                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3633 #if ZIPPY
3634                         if (appData.zippyPlay && first.initDone) {
3635                             ZippyHoldings(white_holding, black_holding,
3636                                           new_piece);
3637                         }
3638 #endif /*ZIPPY*/
3639                         if (tinyLayout || smallLayout) {
3640                             char wh[16], bh[16];
3641                             PackHolding(wh, white_holding);
3642                             PackHolding(bh, black_holding);
3643                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3644                                     gameInfo.white, gameInfo.black);
3645                         } else {
3646                             sprintf(str, "%s [%s] vs. %s [%s]",
3647                                     gameInfo.white, white_holding,
3648                                     gameInfo.black, black_holding);
3649                         }
3650                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3651                         DrawPosition(FALSE, boards[currentMove]);
3652                         DisplayTitle(str);
3653                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3654                         sscanf(parse, "game %d white [%s black [%s <- %s",
3655                                &gamenum, white_holding, black_holding,
3656                                new_piece);
3657                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3658                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3659                         /* [HGM] copy holdings to partner-board holdings area */
3660                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3661                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3662                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3663                       }
3664                     }
3665                     /* Suppress following prompt */
3666                     if (looking_at(buf, &i, "*% ")) {
3667                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3668                         savingComment = FALSE;
3669                         suppressKibitz = 0;
3670                     }
3671                     next_out = i;
3672                 }
3673                 continue;
3674             }
3675
3676             i++;                /* skip unparsed character and loop back */
3677         }
3678         
3679         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3680 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3681 //          SendToPlayer(&buf[next_out], i - next_out);
3682             started != STARTED_HOLDINGS && leftover_start > next_out) {
3683             SendToPlayer(&buf[next_out], leftover_start - next_out);
3684             next_out = i;
3685         }
3686         
3687         leftover_len = buf_len - leftover_start;
3688         /* if buffer ends with something we couldn't parse,
3689            reparse it after appending the next read */
3690         
3691     } else if (count == 0) {
3692         RemoveInputSource(isr);
3693         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3694     } else {
3695         DisplayFatalError(_("Error reading from ICS"), error, 1);
3696     }
3697 }
3698
3699
3700 /* Board style 12 looks like this:
3701    
3702    <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
3703    
3704  * The "<12> " is stripped before it gets to this routine.  The two
3705  * trailing 0's (flip state and clock ticking) are later addition, and
3706  * some chess servers may not have them, or may have only the first.
3707  * Additional trailing fields may be added in the future.  
3708  */
3709
3710 #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"
3711
3712 #define RELATION_OBSERVING_PLAYED    0
3713 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3714 #define RELATION_PLAYING_MYMOVE      1
3715 #define RELATION_PLAYING_NOTMYMOVE  -1
3716 #define RELATION_EXAMINING           2
3717 #define RELATION_ISOLATED_BOARD     -3
3718 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3719
3720 void
3721 ParseBoard12(string)
3722      char *string;
3723
3724     GameMode newGameMode;
3725     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3726     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3727     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3728     char to_play, board_chars[200];
3729     char move_str[500], str[500], elapsed_time[500];
3730     char black[32], white[32];
3731     Board board;
3732     int prevMove = currentMove;
3733     int ticking = 2;
3734     ChessMove moveType;
3735     int fromX, fromY, toX, toY;
3736     char promoChar;
3737     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3738     char *bookHit = NULL; // [HGM] book
3739     Boolean weird = FALSE, reqFlag = FALSE;
3740
3741     fromX = fromY = toX = toY = -1;
3742     
3743     newGame = FALSE;
3744
3745     if (appData.debugMode)
3746       fprintf(debugFP, _("Parsing board: %s\n"), string);
3747
3748     move_str[0] = NULLCHAR;
3749     elapsed_time[0] = NULLCHAR;
3750     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3751         int  i = 0, j;
3752         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3753             if(string[i] == ' ') { ranks++; files = 0; }
3754             else files++;
3755             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3756             i++;
3757         }
3758         for(j = 0; j <i; j++) board_chars[j] = string[j];
3759         board_chars[i] = '\0';
3760         string += i + 1;
3761     }
3762     n = sscanf(string, PATTERN, &to_play, &double_push,
3763                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3764                &gamenum, white, black, &relation, &basetime, &increment,
3765                &white_stren, &black_stren, &white_time, &black_time,
3766                &moveNum, str, elapsed_time, move_str, &ics_flip,
3767                &ticking);
3768
3769     if (n < 21) {
3770         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3771         DisplayError(str, 0);
3772         return;
3773     }
3774
3775     /* Convert the move number to internal form */
3776     moveNum = (moveNum - 1) * 2;
3777     if (to_play == 'B') moveNum++;
3778     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3779       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3780                         0, 1);
3781       return;
3782     }
3783     
3784     switch (relation) {
3785       case RELATION_OBSERVING_PLAYED:
3786       case RELATION_OBSERVING_STATIC:
3787         if (gamenum == -1) {
3788             /* Old ICC buglet */
3789             relation = RELATION_OBSERVING_STATIC;
3790         }
3791         newGameMode = IcsObserving;
3792         break;
3793       case RELATION_PLAYING_MYMOVE:
3794       case RELATION_PLAYING_NOTMYMOVE:
3795         newGameMode =
3796           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3797             IcsPlayingWhite : IcsPlayingBlack;
3798         break;
3799       case RELATION_EXAMINING:
3800         newGameMode = IcsExamining;
3801         break;
3802       case RELATION_ISOLATED_BOARD:
3803       default:
3804         /* Just display this board.  If user was doing something else,
3805            we will forget about it until the next board comes. */ 
3806         newGameMode = IcsIdle;
3807         break;
3808       case RELATION_STARTING_POSITION:
3809         newGameMode = gameMode;
3810         break;
3811     }
3812     
3813     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3814          && newGameMode == IcsObserving && appData.bgObserve) {
3815       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3816       char buf[MSG_SIZ];
3817       for (k = 0; k < ranks; k++) {
3818         for (j = 0; j < files; j++)
3819           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3820         if(gameInfo.holdingsWidth > 1) {
3821              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3822              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3823         }
3824       }
3825       CopyBoard(partnerBoard, board);
3826       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3827       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3828                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3829       DisplayMessage(buf, "");
3830       return;
3831     }
3832
3833     /* Modify behavior for initial board display on move listing
3834        of wild games.
3835        */
3836     switch (ics_getting_history) {
3837       case H_FALSE:
3838       case H_REQUESTED:
3839         break;
3840       case H_GOT_REQ_HEADER:
3841       case H_GOT_UNREQ_HEADER:
3842         /* This is the initial position of the current game */
3843         gamenum = ics_gamenum;
3844         moveNum = 0;            /* old ICS bug workaround */
3845         if (to_play == 'B') {
3846           startedFromSetupPosition = TRUE;
3847           blackPlaysFirst = TRUE;
3848           moveNum = 1;
3849           if (forwardMostMove == 0) forwardMostMove = 1;
3850           if (backwardMostMove == 0) backwardMostMove = 1;
3851           if (currentMove == 0) currentMove = 1;
3852         }
3853         newGameMode = gameMode;
3854         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3855         break;
3856       case H_GOT_UNWANTED_HEADER:
3857         /* This is an initial board that we don't want */
3858         return;
3859       case H_GETTING_MOVES:
3860         /* Should not happen */
3861         DisplayError(_("Error gathering move list: extra board"), 0);
3862         ics_getting_history = H_FALSE;
3863         return;
3864     }
3865
3866    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3867                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3868      /* [HGM] We seem to have switched variant unexpectedly
3869       * Try to guess new variant from board size
3870       */
3871           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3872           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3873           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3874           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3875           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3876           if(!weird) newVariant = VariantNormal;
3877           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3878           /* Get a move list just to see the header, which
3879              will tell us whether this is really bug or zh */
3880           if (ics_getting_history == H_FALSE) {
3881             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3882             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3883             SendToICS(str);
3884           }
3885     }
3886     
3887     /* Take action if this is the first board of a new game, or of a
3888        different game than is currently being displayed.  */
3889     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3890         relation == RELATION_ISOLATED_BOARD) {
3891         
3892         /* Forget the old game and get the history (if any) of the new one */
3893         if (gameMode != BeginningOfGame) {
3894           Reset(TRUE, TRUE);
3895         }
3896         newGame = TRUE;
3897         if (appData.autoRaiseBoard) BoardToTop();
3898         prevMove = -3;
3899         if (gamenum == -1) {
3900             newGameMode = IcsIdle;
3901         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3902                    appData.getMoveList && !reqFlag) {
3903             /* Need to get game history */
3904             ics_getting_history = H_REQUESTED;
3905             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3906             SendToICS(str);
3907         }
3908         
3909         /* Initially flip the board to have black on the bottom if playing
3910            black or if the ICS flip flag is set, but let the user change
3911            it with the Flip View button. */
3912         flipView = appData.autoFlipView ? 
3913           (newGameMode == IcsPlayingBlack) || ics_flip :
3914           appData.flipView;
3915         
3916         /* Done with values from previous mode; copy in new ones */
3917         gameMode = newGameMode;
3918         ModeHighlight();
3919         ics_gamenum = gamenum;
3920         if (gamenum == gs_gamenum) {
3921             int klen = strlen(gs_kind);
3922             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3923             sprintf(str, "ICS %s", gs_kind);
3924             gameInfo.event = StrSave(str);
3925         } else {
3926             gameInfo.event = StrSave("ICS game");
3927         }
3928         gameInfo.site = StrSave(appData.icsHost);
3929         gameInfo.date = PGNDate();
3930         gameInfo.round = StrSave("-");
3931         gameInfo.white = StrSave(white);
3932         gameInfo.black = StrSave(black);
3933         timeControl = basetime * 60 * 1000;
3934         timeControl_2 = 0;
3935         timeIncrement = increment * 1000;
3936         movesPerSession = 0;
3937         gameInfo.timeControl = TimeControlTagValue();
3938         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3941     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3942     setbuf(debugFP, NULL);
3943   }
3944
3945         gameInfo.outOfBook = NULL;
3946         
3947         /* Do we have the ratings? */
3948         if (strcmp(player1Name, white) == 0 &&
3949             strcmp(player2Name, black) == 0) {
3950             if (appData.debugMode)
3951               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3952                       player1Rating, player2Rating);
3953             gameInfo.whiteRating = player1Rating;
3954             gameInfo.blackRating = player2Rating;
3955         } else if (strcmp(player2Name, white) == 0 &&
3956                    strcmp(player1Name, black) == 0) {
3957             if (appData.debugMode)
3958               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3959                       player2Rating, player1Rating);
3960             gameInfo.whiteRating = player2Rating;
3961             gameInfo.blackRating = player1Rating;
3962         }
3963         player1Name[0] = player2Name[0] = NULLCHAR;
3964
3965         /* Silence shouts if requested */
3966         if (appData.quietPlay &&
3967             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3968             SendToICS(ics_prefix);
3969             SendToICS("set shout 0\n");
3970         }
3971     }
3972     
3973     /* Deal with midgame name changes */
3974     if (!newGame) {
3975         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3976             if (gameInfo.white) free(gameInfo.white);
3977             gameInfo.white = StrSave(white);
3978         }
3979         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3980             if (gameInfo.black) free(gameInfo.black);
3981             gameInfo.black = StrSave(black);
3982         }
3983     }
3984     
3985     /* Throw away game result if anything actually changes in examine mode */
3986     if (gameMode == IcsExamining && !newGame) {
3987         gameInfo.result = GameUnfinished;
3988         if (gameInfo.resultDetails != NULL) {
3989             free(gameInfo.resultDetails);
3990             gameInfo.resultDetails = NULL;
3991         }
3992     }
3993     
3994     /* In pausing && IcsExamining mode, we ignore boards coming
3995        in if they are in a different variation than we are. */
3996     if (pauseExamInvalid) return;
3997     if (pausing && gameMode == IcsExamining) {
3998         if (moveNum <= pauseExamForwardMostMove) {
3999             pauseExamInvalid = TRUE;
4000             forwardMostMove = pauseExamForwardMostMove;
4001             return;
4002         }
4003     }
4004     
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4007   }
4008     /* Parse the board */
4009     for (k = 0; k < ranks; k++) {
4010       for (j = 0; j < files; j++)
4011         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4012       if(gameInfo.holdingsWidth > 1) {
4013            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4014            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4015       }
4016     }
4017     CopyBoard(boards[moveNum], board);
4018     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4019     if (moveNum == 0) {
4020         startedFromSetupPosition =
4021           !CompareBoards(board, initialPosition);
4022         if(startedFromSetupPosition)
4023             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4024     }
4025
4026     /* [HGM] Set castling rights. Take the outermost Rooks,
4027        to make it also work for FRC opening positions. Note that board12
4028        is really defective for later FRC positions, as it has no way to
4029        indicate which Rook can castle if they are on the same side of King.
4030        For the initial position we grant rights to the outermost Rooks,
4031        and remember thos rights, and we then copy them on positions
4032        later in an FRC game. This means WB might not recognize castlings with
4033        Rooks that have moved back to their original position as illegal,
4034        but in ICS mode that is not its job anyway.
4035     */
4036     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4037     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4038
4039         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4040             if(board[0][i] == WhiteRook) j = i;
4041         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4042         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4043             if(board[0][i] == WhiteRook) j = i;
4044         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4045         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4046             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4047         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4048         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4049             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4050         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4051
4052         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4053         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4054             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4055         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4056             if(board[BOARD_HEIGHT-1][k] == bKing)
4057                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4058         if(gameInfo.variant == VariantTwoKings) {
4059             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4060             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4061             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4062         }
4063     } else { int r;
4064         r = boards[moveNum][CASTLING][0] = initialRights[0];
4065         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4066         r = boards[moveNum][CASTLING][1] = initialRights[1];
4067         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4068         r = boards[moveNum][CASTLING][3] = initialRights[3];
4069         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4070         r = boards[moveNum][CASTLING][4] = initialRights[4];
4071         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4072         /* wildcastle kludge: always assume King has rights */
4073         r = boards[moveNum][CASTLING][2] = initialRights[2];
4074         r = boards[moveNum][CASTLING][5] = initialRights[5];
4075     }
4076     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4077     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4078
4079     
4080     if (ics_getting_history == H_GOT_REQ_HEADER ||
4081         ics_getting_history == H_GOT_UNREQ_HEADER) {
4082         /* This was an initial position from a move list, not
4083            the current position */
4084         return;
4085     }
4086     
4087     /* Update currentMove and known move number limits */
4088     newMove = newGame || moveNum > forwardMostMove;
4089
4090     if (newGame) {
4091         forwardMostMove = backwardMostMove = currentMove = moveNum;
4092         if (gameMode == IcsExamining && moveNum == 0) {
4093           /* Workaround for ICS limitation: we are not told the wild
4094              type when starting to examine a game.  But if we ask for
4095              the move list, the move list header will tell us */
4096             ics_getting_history = H_REQUESTED;
4097             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4098             SendToICS(str);
4099         }
4100     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4101                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4102 #if ZIPPY
4103         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4104         /* [HGM] applied this also to an engine that is silently watching        */
4105         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4106             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4107             gameInfo.variant == currentlyInitializedVariant) {
4108           takeback = forwardMostMove - moveNum;
4109           for (i = 0; i < takeback; i++) {
4110             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4111             SendToProgram("undo\n", &first);
4112           }
4113         }
4114 #endif
4115
4116         forwardMostMove = moveNum;
4117         if (!pausing || currentMove > forwardMostMove)
4118           currentMove = forwardMostMove;
4119     } else {
4120         /* New part of history that is not contiguous with old part */ 
4121         if (pausing && gameMode == IcsExamining) {
4122             pauseExamInvalid = TRUE;
4123             forwardMostMove = pauseExamForwardMostMove;
4124             return;
4125         }
4126         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4127 #if ZIPPY
4128             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4129                 // [HGM] when we will receive the move list we now request, it will be
4130                 // fed to the engine from the first move on. So if the engine is not
4131                 // in the initial position now, bring it there.
4132                 InitChessProgram(&first, 0);
4133             }
4134 #endif
4135             ics_getting_history = H_REQUESTED;
4136             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4137             SendToICS(str);
4138         }
4139         forwardMostMove = backwardMostMove = currentMove = moveNum;
4140     }
4141     
4142     /* Update the clocks */
4143     if (strchr(elapsed_time, '.')) {
4144       /* Time is in ms */
4145       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4146       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4147     } else {
4148       /* Time is in seconds */
4149       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4150       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4151     }
4152       
4153
4154 #if ZIPPY
4155     if (appData.zippyPlay && newGame &&
4156         gameMode != IcsObserving && gameMode != IcsIdle &&
4157         gameMode != IcsExamining)
4158       ZippyFirstBoard(moveNum, basetime, increment);
4159 #endif
4160     
4161     /* Put the move on the move list, first converting
4162        to canonical algebraic form. */
4163     if (moveNum > 0) {
4164   if (appData.debugMode) {
4165     if (appData.debugMode) { int f = forwardMostMove;
4166         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4167                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4168                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4169     }
4170     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4171     fprintf(debugFP, "moveNum = %d\n", moveNum);
4172     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4173     setbuf(debugFP, NULL);
4174   }
4175         if (moveNum <= backwardMostMove) {
4176             /* We don't know what the board looked like before
4177                this move.  Punt. */
4178             strcpy(parseList[moveNum - 1], move_str);
4179             strcat(parseList[moveNum - 1], " ");
4180             strcat(parseList[moveNum - 1], elapsed_time);
4181             moveList[moveNum - 1][0] = NULLCHAR;
4182         } else if (strcmp(move_str, "none") == 0) {
4183             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4184             /* Again, we don't know what the board looked like;
4185                this is really the start of the game. */
4186             parseList[moveNum - 1][0] = NULLCHAR;
4187             moveList[moveNum - 1][0] = NULLCHAR;
4188             backwardMostMove = moveNum;
4189             startedFromSetupPosition = TRUE;
4190             fromX = fromY = toX = toY = -1;
4191         } else {
4192           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4193           //                 So we parse the long-algebraic move string in stead of the SAN move
4194           int valid; char buf[MSG_SIZ], *prom;
4195
4196           // str looks something like "Q/a1-a2"; kill the slash
4197           if(str[1] == '/') 
4198                 sprintf(buf, "%c%s", str[0], str+2);
4199           else  strcpy(buf, str); // might be castling
4200           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4201                 strcat(buf, prom); // long move lacks promo specification!
4202           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4203                 if(appData.debugMode) 
4204                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4205                 strcpy(move_str, buf);
4206           }
4207           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4208                                 &fromX, &fromY, &toX, &toY, &promoChar)
4209                || ParseOneMove(buf, moveNum - 1, &moveType,
4210                                 &fromX, &fromY, &toX, &toY, &promoChar);
4211           // end of long SAN patch
4212           if (valid) {
4213             (void) CoordsToAlgebraic(boards[moveNum - 1],
4214                                      PosFlags(moveNum - 1),
4215                                      fromY, fromX, toY, toX, promoChar,
4216                                      parseList[moveNum-1]);
4217             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4218               case MT_NONE:
4219               case MT_STALEMATE:
4220               default:
4221                 break;
4222               case MT_CHECK:
4223                 if(gameInfo.variant != VariantShogi)
4224                     strcat(parseList[moveNum - 1], "+");
4225                 break;
4226               case MT_CHECKMATE:
4227               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4228                 strcat(parseList[moveNum - 1], "#");
4229                 break;
4230             }
4231             strcat(parseList[moveNum - 1], " ");
4232             strcat(parseList[moveNum - 1], elapsed_time);
4233             /* currentMoveString is set as a side-effect of ParseOneMove */
4234             strcpy(moveList[moveNum - 1], currentMoveString);
4235             strcat(moveList[moveNum - 1], "\n");
4236           } else {
4237             /* Move from ICS was illegal!?  Punt. */
4238   if (appData.debugMode) {
4239     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4241   }
4242             strcpy(parseList[moveNum - 1], move_str);
4243             strcat(parseList[moveNum - 1], " ");
4244             strcat(parseList[moveNum - 1], elapsed_time);
4245             moveList[moveNum - 1][0] = NULLCHAR;
4246             fromX = fromY = toX = toY = -1;
4247           }
4248         }
4249   if (appData.debugMode) {
4250     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4251     setbuf(debugFP, NULL);
4252   }
4253
4254 #if ZIPPY
4255         /* Send move to chess program (BEFORE animating it). */
4256         if (appData.zippyPlay && !newGame && newMove && 
4257            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4258
4259             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4260                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4261                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4262                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4263                             move_str);
4264                     DisplayError(str, 0);
4265                 } else {
4266                     if (first.sendTime) {
4267                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4268                     }
4269                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4270                     if (firstMove && !bookHit) {
4271                         firstMove = FALSE;
4272                         if (first.useColors) {
4273                           SendToProgram(gameMode == IcsPlayingWhite ?
4274                                         "white\ngo\n" :
4275                                         "black\ngo\n", &first);
4276                         } else {
4277                           SendToProgram("go\n", &first);
4278                         }
4279                         first.maybeThinking = TRUE;
4280                     }
4281                 }
4282             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4283               if (moveList[moveNum - 1][0] == NULLCHAR) {
4284                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4285                 DisplayError(str, 0);
4286               } else {
4287                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4288                 SendMoveToProgram(moveNum - 1, &first);
4289               }
4290             }
4291         }
4292 #endif
4293     }
4294
4295     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4296         /* If move comes from a remote source, animate it.  If it
4297            isn't remote, it will have already been animated. */
4298         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4299             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4300         }
4301         if (!pausing && appData.highlightLastMove) {
4302             SetHighlights(fromX, fromY, toX, toY);
4303         }
4304     }
4305     
4306     /* Start the clocks */
4307     whiteFlag = blackFlag = FALSE;
4308     appData.clockMode = !(basetime == 0 && increment == 0);
4309     if (ticking == 0) {
4310       ics_clock_paused = TRUE;
4311       StopClocks();
4312     } else if (ticking == 1) {
4313       ics_clock_paused = FALSE;
4314     }
4315     if (gameMode == IcsIdle ||
4316         relation == RELATION_OBSERVING_STATIC ||
4317         relation == RELATION_EXAMINING ||
4318         ics_clock_paused)
4319       DisplayBothClocks();
4320     else
4321       StartClocks();
4322     
4323     /* Display opponents and material strengths */
4324     if (gameInfo.variant != VariantBughouse &&
4325         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4326         if (tinyLayout || smallLayout) {
4327             if(gameInfo.variant == VariantNormal)
4328                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4329                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4330                     basetime, increment);
4331             else
4332                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4333                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4334                     basetime, increment, (int) gameInfo.variant);
4335         } else {
4336             if(gameInfo.variant == VariantNormal)
4337                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment);
4340             else
4341                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4342                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4343                     basetime, increment, VariantName(gameInfo.variant));
4344         }
4345         DisplayTitle(str);
4346   if (appData.debugMode) {
4347     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4348   }
4349     }
4350
4351
4352     /* Display the board */
4353     if (!pausing && !appData.noGUI) {
4354       
4355       if (appData.premove)
4356           if (!gotPremove || 
4357              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4358              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4359               ClearPremoveHighlights();
4360
4361       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4362       DrawPosition(j, boards[currentMove]);
4363
4364       DisplayMove(moveNum - 1);
4365       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4366             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4367               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4368         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4369       }
4370     }
4371
4372     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4373 #if ZIPPY
4374     if(bookHit) { // [HGM] book: simulate book reply
4375         static char bookMove[MSG_SIZ]; // a bit generous?
4376
4377         programStats.nodes = programStats.depth = programStats.time = 
4378         programStats.score = programStats.got_only_move = 0;
4379         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4380
4381         strcpy(bookMove, "move ");
4382         strcat(bookMove, bookHit);
4383         HandleMachineMove(bookMove, &first);
4384     }
4385 #endif
4386 }
4387
4388 void
4389 GetMoveListEvent()
4390 {
4391     char buf[MSG_SIZ];
4392     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4393         ics_getting_history = H_REQUESTED;
4394         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4395         SendToICS(buf);
4396     }
4397 }
4398
4399 void
4400 AnalysisPeriodicEvent(force)
4401      int force;
4402 {
4403     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4404          && !force) || !appData.periodicUpdates)
4405       return;
4406
4407     /* Send . command to Crafty to collect stats */
4408     SendToProgram(".\n", &first);
4409
4410     /* Don't send another until we get a response (this makes
4411        us stop sending to old Crafty's which don't understand
4412        the "." command (sending illegal cmds resets node count & time,
4413        which looks bad)) */
4414     programStats.ok_to_send = 0;
4415 }
4416
4417 void ics_update_width(new_width)
4418         int new_width;
4419 {
4420         ics_printf("set width %d\n", new_width);
4421 }
4422
4423 void
4424 SendMoveToProgram(moveNum, cps)
4425      int moveNum;
4426      ChessProgramState *cps;
4427 {
4428     char buf[MSG_SIZ];
4429
4430     if (cps->useUsermove) {
4431       SendToProgram("usermove ", cps);
4432     }
4433     if (cps->useSAN) {
4434       char *space;
4435       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4436         int len = space - parseList[moveNum];
4437         memcpy(buf, parseList[moveNum], len);
4438         buf[len++] = '\n';
4439         buf[len] = NULLCHAR;
4440       } else {
4441         sprintf(buf, "%s\n", parseList[moveNum]);
4442       }
4443       SendToProgram(buf, cps);
4444     } else {
4445       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4446         AlphaRank(moveList[moveNum], 4);
4447         SendToProgram(moveList[moveNum], cps);
4448         AlphaRank(moveList[moveNum], 4); // and back
4449       } else
4450       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4451        * the engine. It would be nice to have a better way to identify castle 
4452        * moves here. */
4453       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4454                                                                          && cps->useOOCastle) {
4455         int fromX = moveList[moveNum][0] - AAA; 
4456         int fromY = moveList[moveNum][1] - ONE;
4457         int toX = moveList[moveNum][2] - AAA; 
4458         int toY = moveList[moveNum][3] - ONE;
4459         if((boards[moveNum][fromY][fromX] == WhiteKing 
4460             && boards[moveNum][toY][toX] == WhiteRook)
4461            || (boards[moveNum][fromY][fromX] == BlackKing 
4462                && boards[moveNum][toY][toX] == BlackRook)) {
4463           if(toX > fromX) SendToProgram("O-O\n", cps);
4464           else SendToProgram("O-O-O\n", cps);
4465         }
4466         else SendToProgram(moveList[moveNum], cps);
4467       }
4468       else SendToProgram(moveList[moveNum], cps);
4469       /* End of additions by Tord */
4470     }
4471
4472     /* [HGM] setting up the opening has brought engine in force mode! */
4473     /*       Send 'go' if we are in a mode where machine should play. */
4474     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4475         (gameMode == TwoMachinesPlay   ||
4476 #ifdef ZIPPY
4477          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4478 #endif
4479          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4480         SendToProgram("go\n", cps);
4481   if (appData.debugMode) {
4482     fprintf(debugFP, "(extra)\n");
4483   }
4484     }
4485     setboardSpoiledMachineBlack = 0;
4486 }
4487
4488 void
4489 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4490      ChessMove moveType;
4491      int fromX, fromY, toX, toY;
4492 {
4493     char user_move[MSG_SIZ];
4494
4495     switch (moveType) {
4496       default:
4497         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4498                 (int)moveType, fromX, fromY, toX, toY);
4499         DisplayError(user_move + strlen("say "), 0);
4500         break;
4501       case WhiteKingSideCastle:
4502       case BlackKingSideCastle:
4503       case WhiteQueenSideCastleWild:
4504       case BlackQueenSideCastleWild:
4505       /* PUSH Fabien */
4506       case WhiteHSideCastleFR:
4507       case BlackHSideCastleFR:
4508       /* POP Fabien */
4509         sprintf(user_move, "o-o\n");
4510         break;
4511       case WhiteQueenSideCastle:
4512       case BlackQueenSideCastle:
4513       case WhiteKingSideCastleWild:
4514       case BlackKingSideCastleWild:
4515       /* PUSH Fabien */
4516       case WhiteASideCastleFR:
4517       case BlackASideCastleFR:
4518       /* POP Fabien */
4519         sprintf(user_move, "o-o-o\n");
4520         break;
4521       case WhitePromotionQueen:
4522       case BlackPromotionQueen:
4523       case WhitePromotionRook:
4524       case BlackPromotionRook:
4525       case WhitePromotionBishop:
4526       case BlackPromotionBishop:
4527       case WhitePromotionKnight:
4528       case BlackPromotionKnight:
4529       case WhitePromotionKing:
4530       case BlackPromotionKing:
4531       case WhitePromotionChancellor:
4532       case BlackPromotionChancellor:
4533       case WhitePromotionArchbishop:
4534       case BlackPromotionArchbishop:
4535         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4536             sprintf(user_move, "%c%c%c%c=%c\n",
4537                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4538                 PieceToChar(WhiteFerz));
4539         else if(gameInfo.variant == VariantGreat)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteMan));
4543         else
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(PromoPiece(moveType)));
4547         break;
4548       case WhiteDrop:
4549       case BlackDrop:
4550         sprintf(user_move, "%c@%c%c\n",
4551                 ToUpper(PieceToChar((ChessSquare) fromX)),
4552                 AAA + toX, ONE + toY);
4553         break;
4554       case NormalMove:
4555       case WhiteCapturesEnPassant:
4556       case BlackCapturesEnPassant:
4557       case IllegalMove:  /* could be a variant we don't quite understand */
4558         sprintf(user_move, "%c%c%c%c\n",
4559                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4560         break;
4561     }
4562     SendToICS(user_move);
4563     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4564         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4565 }
4566
4567 void
4568 UploadGameEvent()
4569 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4570     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4571     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4572     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4573         DisplayError("You cannot do this while you are playing or observing", 0);
4574         return;
4575     }
4576     if(gameMode != IcsExamining) { // is this ever not the case?
4577         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4578
4579         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4580             sprintf(command, "match %s", ics_handle);
4581         } else { // on FICS we must first go to general examine mode
4582             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4583         }
4584         if(gameInfo.variant != VariantNormal) {
4585             // try figure out wild number, as xboard names are not always valid on ICS
4586             for(i=1; i<=36; i++) {
4587                 sprintf(buf, "wild/%d", i);
4588                 if(StringToVariant(buf) == gameInfo.variant) break;
4589             }
4590             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4591             else if(i == 22) sprintf(buf, "%s fr\n", command);
4592             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4593         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4594         SendToICS(ics_prefix);
4595         SendToICS(buf);
4596         if(startedFromSetupPosition || backwardMostMove != 0) {
4597           fen = PositionToFEN(backwardMostMove, NULL);
4598           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4599             sprintf(buf, "loadfen %s\n", fen);
4600             SendToICS(buf);
4601           } else { // FICS: everything has to set by separate bsetup commands
4602             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4603             sprintf(buf, "bsetup fen %s\n", fen);
4604             SendToICS(buf);
4605             if(!WhiteOnMove(backwardMostMove)) {
4606                 SendToICS("bsetup tomove black\n");
4607             }
4608             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4609             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4610             SendToICS(buf);
4611             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4612             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4613             SendToICS(buf);
4614             i = boards[backwardMostMove][EP_STATUS];
4615             if(i >= 0) { // set e.p.
4616                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4617                 SendToICS(buf);
4618             }
4619             bsetup++;
4620           }
4621         }
4622       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4623             SendToICS("bsetup done\n"); // switch to normal examining.
4624     }
4625     for(i = backwardMostMove; i<last; i++) {
4626         char buf[20];
4627         sprintf(buf, "%s\n", parseList[i]);
4628         SendToICS(buf);
4629     }
4630     SendToICS(ics_prefix);
4631     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4632 }
4633
4634 void
4635 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4636      int rf, ff, rt, ft;
4637      char promoChar;
4638      char move[7];
4639 {
4640     if (rf == DROP_RANK) {
4641         sprintf(move, "%c@%c%c\n",
4642                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4643     } else {
4644         if (promoChar == 'x' || promoChar == NULLCHAR) {
4645             sprintf(move, "%c%c%c%c\n",
4646                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4647         } else {
4648             sprintf(move, "%c%c%c%c%c\n",
4649                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4650         }
4651     }
4652 }
4653
4654 void
4655 ProcessICSInitScript(f)
4656      FILE *f;
4657 {
4658     char buf[MSG_SIZ];
4659
4660     while (fgets(buf, MSG_SIZ, f)) {
4661         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4662     }
4663
4664     fclose(f);
4665 }
4666
4667
4668 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4669 void
4670 AlphaRank(char *move, int n)
4671 {
4672 //    char *p = move, c; int x, y;
4673
4674     if (appData.debugMode) {
4675         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4676     }
4677
4678     if(move[1]=='*' && 
4679        move[2]>='0' && move[2]<='9' &&
4680        move[3]>='a' && move[3]<='x'    ) {
4681         move[1] = '@';
4682         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4683         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4684     } else
4685     if(move[0]>='0' && move[0]<='9' &&
4686        move[1]>='a' && move[1]<='x' &&
4687        move[2]>='0' && move[2]<='9' &&
4688        move[3]>='a' && move[3]<='x'    ) {
4689         /* input move, Shogi -> normal */
4690         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4691         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4692         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4693         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4694     } else
4695     if(move[1]=='@' &&
4696        move[3]>='0' && move[3]<='9' &&
4697        move[2]>='a' && move[2]<='x'    ) {
4698         move[1] = '*';
4699         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4700         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4701     } else
4702     if(
4703        move[0]>='a' && move[0]<='x' &&
4704        move[3]>='0' && move[3]<='9' &&
4705        move[2]>='a' && move[2]<='x'    ) {
4706          /* output move, normal -> Shogi */
4707         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4708         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4709         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4710         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4711         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4712     }
4713     if (appData.debugMode) {
4714         fprintf(debugFP, "   out = '%s'\n", move);
4715     }
4716 }
4717
4718 char yy_textstr[8000];
4719
4720 /* Parser for moves from gnuchess, ICS, or user typein box */
4721 Boolean
4722 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4723      char *move;
4724      int moveNum;
4725      ChessMove *moveType;
4726      int *fromX, *fromY, *toX, *toY;
4727      char *promoChar;
4728 {       
4729     if (appData.debugMode) {
4730         fprintf(debugFP, "move to parse: %s\n", move);
4731     }
4732     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4733
4734     switch (*moveType) {
4735       case WhitePromotionChancellor:
4736       case BlackPromotionChancellor:
4737       case WhitePromotionArchbishop:
4738       case BlackPromotionArchbishop:
4739       case WhitePromotionQueen:
4740       case BlackPromotionQueen:
4741       case WhitePromotionRook:
4742       case BlackPromotionRook:
4743       case WhitePromotionBishop:
4744       case BlackPromotionBishop:
4745       case WhitePromotionKnight:
4746       case BlackPromotionKnight:
4747       case WhitePromotionKing:
4748       case BlackPromotionKing:
4749       case NormalMove:
4750       case WhiteCapturesEnPassant:
4751       case BlackCapturesEnPassant:
4752       case WhiteKingSideCastle:
4753       case WhiteQueenSideCastle:
4754       case BlackKingSideCastle:
4755       case BlackQueenSideCastle:
4756       case WhiteKingSideCastleWild:
4757       case WhiteQueenSideCastleWild:
4758       case BlackKingSideCastleWild:
4759       case BlackQueenSideCastleWild:
4760       /* Code added by Tord: */
4761       case WhiteHSideCastleFR:
4762       case WhiteASideCastleFR:
4763       case BlackHSideCastleFR:
4764       case BlackASideCastleFR:
4765       /* End of code added by Tord */
4766       case IllegalMove:         /* bug or odd chess variant */
4767         *fromX = currentMoveString[0] - AAA;
4768         *fromY = currentMoveString[1] - ONE;
4769         *toX = currentMoveString[2] - AAA;
4770         *toY = currentMoveString[3] - ONE;
4771         *promoChar = currentMoveString[4];
4772         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4773             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4774     if (appData.debugMode) {
4775         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4776     }
4777             *fromX = *fromY = *toX = *toY = 0;
4778             return FALSE;
4779         }
4780         if (appData.testLegality) {
4781           return (*moveType != IllegalMove);
4782         } else {
4783           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4784                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4785         }
4786
4787       case WhiteDrop:
4788       case BlackDrop:
4789         *fromX = *moveType == WhiteDrop ?
4790           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4791           (int) CharToPiece(ToLower(currentMoveString[0]));
4792         *fromY = DROP_RANK;
4793         *toX = currentMoveString[2] - AAA;
4794         *toY = currentMoveString[3] - ONE;
4795         *promoChar = NULLCHAR;
4796         return TRUE;
4797
4798       case AmbiguousMove:
4799       case ImpossibleMove:
4800       case (ChessMove) 0:       /* end of file */
4801       case ElapsedTime:
4802       case Comment:
4803       case PGNTag:
4804       case NAG:
4805       case WhiteWins:
4806       case BlackWins:
4807       case GameIsDrawn:
4808       default:
4809     if (appData.debugMode) {
4810         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4811     }
4812         /* bug? */
4813         *fromX = *fromY = *toX = *toY = 0;
4814         *promoChar = NULLCHAR;
4815         return FALSE;
4816     }
4817 }
4818
4819
4820 void
4821 ParsePV(char *pv, Boolean storeComments)
4822 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4823   int fromX, fromY, toX, toY; char promoChar;
4824   ChessMove moveType;
4825   Boolean valid;
4826   int nr = 0;
4827
4828   endPV = forwardMostMove;
4829   do {
4830     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4831     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4832     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4833 if(appData.debugMode){
4834 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4835 }
4836     if(!valid && nr == 0 &&
4837        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4838         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4839         // Hande case where played move is different from leading PV move
4840         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4841         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4842         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4843         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4844           endPV += 2; // if position different, keep this
4845           moveList[endPV-1][0] = fromX + AAA;
4846           moveList[endPV-1][1] = fromY + ONE;
4847           moveList[endPV-1][2] = toX + AAA;
4848           moveList[endPV-1][3] = toY + ONE;
4849           parseList[endPV-1][0] = NULLCHAR;
4850           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4851         }
4852       }
4853     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4854     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4855     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4856     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4857         valid++; // allow comments in PV
4858         continue;
4859     }
4860     nr++;
4861     if(endPV+1 > framePtr) break; // no space, truncate
4862     if(!valid) break;
4863     endPV++;
4864     CopyBoard(boards[endPV], boards[endPV-1]);
4865     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4866     moveList[endPV-1][0] = fromX + AAA;
4867     moveList[endPV-1][1] = fromY + ONE;
4868     moveList[endPV-1][2] = toX + AAA;
4869     moveList[endPV-1][3] = toY + ONE;
4870     if(storeComments)
4871         CoordsToAlgebraic(boards[endPV - 1],
4872                              PosFlags(endPV - 1),
4873                              fromY, fromX, toY, toX, promoChar,
4874                              parseList[endPV - 1]);
4875     else
4876         parseList[endPV-1][0] = NULLCHAR;
4877   } while(valid);
4878   currentMove = endPV;
4879   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4880   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4881                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4882   DrawPosition(TRUE, boards[currentMove]);
4883 }
4884
4885 static int lastX, lastY;
4886
4887 Boolean
4888 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4889 {
4890         int startPV;
4891
4892         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4893         lastX = x; lastY = y;
4894         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4895         startPV = index;
4896       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4897       index = startPV;
4898         while(buf[index] && buf[index] != '\n') index++;
4899         buf[index] = 0;
4900         ParsePV(buf+startPV, FALSE);
4901         *start = startPV; *end = index-1;
4902         return TRUE;
4903 }
4904
4905 Boolean
4906 LoadPV(int x, int y)
4907 { // called on right mouse click to load PV
4908   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4909   lastX = x; lastY = y;
4910   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4911   return TRUE;
4912 }
4913
4914 void
4915 UnLoadPV()
4916 {
4917   if(endPV < 0) return;
4918   endPV = -1;
4919   currentMove = forwardMostMove;
4920   ClearPremoveHighlights();
4921   DrawPosition(TRUE, boards[currentMove]);
4922 }
4923
4924 void
4925 MovePV(int x, int y, int h)
4926 { // step through PV based on mouse coordinates (called on mouse move)
4927   int margin = h>>3, step = 0;
4928
4929   if(endPV < 0) return;
4930   // we must somehow check if right button is still down (might be released off board!)
4931   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4932   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4933   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4934   if(!step) return;
4935   lastX = x; lastY = y;
4936   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4937   currentMove += step;
4938   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4939   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4940                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4941   DrawPosition(FALSE, boards[currentMove]);
4942 }
4943
4944
4945 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4946 // All positions will have equal probability, but the current method will not provide a unique
4947 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4948 #define DARK 1
4949 #define LITE 2
4950 #define ANY 3
4951
4952 int squaresLeft[4];
4953 int piecesLeft[(int)BlackPawn];
4954 int seed, nrOfShuffles;
4955
4956 void GetPositionNumber()
4957 {       // sets global variable seed
4958         int i;
4959
4960         seed = appData.defaultFrcPosition;
4961         if(seed < 0) { // randomize based on time for negative FRC position numbers
4962                 for(i=0; i<50; i++) seed += random();
4963                 seed = random() ^ random() >> 8 ^ random() << 8;
4964                 if(seed<0) seed = -seed;
4965         }
4966 }
4967
4968 int put(Board board, int pieceType, int rank, int n, int shade)
4969 // put the piece on the (n-1)-th empty squares of the given shade
4970 {
4971         int i;
4972
4973         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4974                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4975                         board[rank][i] = (ChessSquare) pieceType;
4976                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4977                         squaresLeft[ANY]--;
4978                         piecesLeft[pieceType]--; 
4979                         return i;
4980                 }
4981         }
4982         return -1;
4983 }
4984
4985
4986 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4987 // calculate where the next piece goes, (any empty square), and put it there
4988 {
4989         int i;
4990
4991         i = seed % squaresLeft[shade];
4992         nrOfShuffles *= squaresLeft[shade];
4993         seed /= squaresLeft[shade];
4994         put(board, pieceType, rank, i, shade);
4995 }
4996
4997 void AddTwoPieces(Board board, int pieceType, int rank)
4998 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4999 {
5000         int i, n=squaresLeft[ANY], j=n-1, k;
5001
5002         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5003         i = seed % k;  // pick one
5004         nrOfShuffles *= k;
5005         seed /= k;
5006         while(i >= j) i -= j--;
5007         j = n - 1 - j; i += j;
5008         put(board, pieceType, rank, j, ANY);
5009         put(board, pieceType, rank, i, ANY);
5010 }
5011
5012 void SetUpShuffle(Board board, int number)
5013 {
5014         int i, p, first=1;
5015
5016         GetPositionNumber(); nrOfShuffles = 1;
5017
5018         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5019         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5020         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5021
5022         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5023
5024         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5025             p = (int) board[0][i];
5026             if(p < (int) BlackPawn) piecesLeft[p] ++;
5027             board[0][i] = EmptySquare;
5028         }
5029
5030         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5031             // shuffles restricted to allow normal castling put KRR first
5032             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5033                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5034             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5035                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5036             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5037                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5038             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5039                 put(board, WhiteRook, 0, 0, ANY);
5040             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5041         }
5042
5043         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5044             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5045             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5046                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5047                 while(piecesLeft[p] >= 2) {
5048                     AddOnePiece(board, p, 0, LITE);
5049                     AddOnePiece(board, p, 0, DARK);
5050                 }
5051                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5052             }
5053
5054         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5055             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5056             // but we leave King and Rooks for last, to possibly obey FRC restriction
5057             if(p == (int)WhiteRook) continue;
5058             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5059             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5060         }
5061
5062         // now everything is placed, except perhaps King (Unicorn) and Rooks
5063
5064         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5065             // Last King gets castling rights
5066             while(piecesLeft[(int)WhiteUnicorn]) {
5067                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5068                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5069             }
5070
5071             while(piecesLeft[(int)WhiteKing]) {
5072                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5073                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5074             }
5075
5076
5077         } else {
5078             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5079             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5080         }
5081
5082         // Only Rooks can be left; simply place them all
5083         while(piecesLeft[(int)WhiteRook]) {
5084                 i = put(board, WhiteRook, 0, 0, ANY);
5085                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5086                         if(first) {
5087                                 first=0;
5088                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5089                         }
5090                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5091                 }
5092         }
5093         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5094             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5095         }
5096
5097         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5098 }
5099
5100 int SetCharTable( char *table, const char * map )
5101 /* [HGM] moved here from winboard.c because of its general usefulness */
5102 /*       Basically a safe strcpy that uses the last character as King */
5103 {
5104     int result = FALSE; int NrPieces;
5105
5106     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5107                     && NrPieces >= 12 && !(NrPieces&1)) {
5108         int i; /* [HGM] Accept even length from 12 to 34 */
5109
5110         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5111         for( i=0; i<NrPieces/2-1; i++ ) {
5112             table[i] = map[i];
5113             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5114         }
5115         table[(int) WhiteKing]  = map[NrPieces/2-1];
5116         table[(int) BlackKing]  = map[NrPieces-1];
5117
5118         result = TRUE;
5119     }
5120
5121     return result;
5122 }
5123
5124 void Prelude(Board board)
5125 {       // [HGM] superchess: random selection of exo-pieces
5126         int i, j, k; ChessSquare p; 
5127         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5128
5129         GetPositionNumber(); // use FRC position number
5130
5131         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5132             SetCharTable(pieceToChar, appData.pieceToCharTable);
5133             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5134                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5135         }
5136
5137         j = seed%4;                 seed /= 4; 
5138         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5140         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5141         j = seed%3 + (seed%3 >= j); seed /= 3; 
5142         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5143         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5144         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5145         j = seed%3;                 seed /= 3; 
5146         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5147         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5148         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5149         j = seed%2 + (seed%2 >= j); seed /= 2; 
5150         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5151         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5152         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5153         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5154         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5155         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5156         put(board, exoPieces[0],    0, 0, ANY);
5157         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5158 }
5159
5160 void
5161 InitPosition(redraw)
5162      int redraw;
5163 {
5164     ChessSquare (* pieces)[BOARD_FILES];
5165     int i, j, pawnRow, overrule,
5166     oldx = gameInfo.boardWidth,
5167     oldy = gameInfo.boardHeight,
5168     oldh = gameInfo.holdingsWidth,
5169     oldv = gameInfo.variant;
5170
5171     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5172
5173     /* [AS] Initialize pv info list [HGM] and game status */
5174     {
5175         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5176             pvInfoList[i].depth = 0;
5177             boards[i][EP_STATUS] = EP_NONE;
5178             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5179         }
5180
5181         initialRulePlies = 0; /* 50-move counter start */
5182
5183         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5184         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5185     }
5186
5187     
5188     /* [HGM] logic here is completely changed. In stead of full positions */
5189     /* the initialized data only consist of the two backranks. The switch */
5190     /* selects which one we will use, which is than copied to the Board   */
5191     /* initialPosition, which for the rest is initialized by Pawns and    */
5192     /* empty squares. This initial position is then copied to boards[0],  */
5193     /* possibly after shuffling, so that it remains available.            */
5194
5195     gameInfo.holdingsWidth = 0; /* default board sizes */
5196     gameInfo.boardWidth    = 8;
5197     gameInfo.boardHeight   = 8;
5198     gameInfo.holdingsSize  = 0;
5199     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5200     for(i=0; i<BOARD_FILES-2; i++)
5201       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5202     initialPosition[EP_STATUS] = EP_NONE;
5203     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5204
5205     switch (gameInfo.variant) {
5206     case VariantFischeRandom:
5207       shuffleOpenings = TRUE;
5208     default:
5209       pieces = FIDEArray;
5210       break;
5211     case VariantShatranj:
5212       pieces = ShatranjArray;
5213       nrCastlingRights = 0;
5214       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5215       break;
5216     case VariantMakruk:
5217       pieces = makrukArray;
5218       nrCastlingRights = 0;
5219       startedFromSetupPosition = TRUE;
5220       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5221       break;
5222     case VariantTwoKings:
5223       pieces = twoKingsArray;
5224       break;
5225     case VariantCapaRandom:
5226       shuffleOpenings = TRUE;
5227     case VariantCapablanca:
5228       pieces = CapablancaArray;
5229       gameInfo.boardWidth = 10;
5230       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5231       break;
5232     case VariantGothic:
5233       pieces = GothicArray;
5234       gameInfo.boardWidth = 10;
5235       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5236       break;
5237     case VariantJanus:
5238       pieces = JanusArray;
5239       gameInfo.boardWidth = 10;
5240       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5241       nrCastlingRights = 6;
5242         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5243         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5244         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5245         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5246         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5247         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5248       break;
5249     case VariantFalcon:
5250       pieces = FalconArray;
5251       gameInfo.boardWidth = 10;
5252       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5253       break;
5254     case VariantXiangqi:
5255       pieces = XiangqiArray;
5256       gameInfo.boardWidth  = 9;
5257       gameInfo.boardHeight = 10;
5258       nrCastlingRights = 0;
5259       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5260       break;
5261     case VariantShogi:
5262       pieces = ShogiArray;
5263       gameInfo.boardWidth  = 9;
5264       gameInfo.boardHeight = 9;
5265       gameInfo.holdingsSize = 7;
5266       nrCastlingRights = 0;
5267       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5268       break;
5269     case VariantCourier:
5270       pieces = CourierArray;
5271       gameInfo.boardWidth  = 12;
5272       nrCastlingRights = 0;
5273       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5274       break;
5275     case VariantKnightmate:
5276       pieces = KnightmateArray;
5277       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5278       break;
5279     case VariantFairy:
5280       pieces = fairyArray;
5281       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5282       break;
5283     case VariantGreat:
5284       pieces = GreatArray;
5285       gameInfo.boardWidth = 10;
5286       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5287       gameInfo.holdingsSize = 8;
5288       break;
5289     case VariantSuper:
5290       pieces = FIDEArray;
5291       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5292       gameInfo.holdingsSize = 8;
5293       startedFromSetupPosition = TRUE;
5294       break;
5295     case VariantCrazyhouse:
5296     case VariantBughouse:
5297       pieces = FIDEArray;
5298       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5299       gameInfo.holdingsSize = 5;
5300       break;
5301     case VariantWildCastle:
5302       pieces = FIDEArray;
5303       /* !!?shuffle with kings guaranteed to be on d or e file */
5304       shuffleOpenings = 1;
5305       break;
5306     case VariantNoCastle:
5307       pieces = FIDEArray;
5308       nrCastlingRights = 0;
5309       /* !!?unconstrained back-rank shuffle */
5310       shuffleOpenings = 1;
5311       break;
5312     }
5313
5314     overrule = 0;
5315     if(appData.NrFiles >= 0) {
5316         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5317         gameInfo.boardWidth = appData.NrFiles;
5318     }
5319     if(appData.NrRanks >= 0) {
5320         gameInfo.boardHeight = appData.NrRanks;
5321     }
5322     if(appData.holdingsSize >= 0) {
5323         i = appData.holdingsSize;
5324         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5325         gameInfo.holdingsSize = i;
5326     }
5327     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5328     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5329         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5330
5331     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5332     if(pawnRow < 1) pawnRow = 1;
5333     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5334
5335     /* User pieceToChar list overrules defaults */
5336     if(appData.pieceToCharTable != NULL)
5337         SetCharTable(pieceToChar, appData.pieceToCharTable);
5338
5339     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5340
5341         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5342             s = (ChessSquare) 0; /* account holding counts in guard band */
5343         for( i=0; i<BOARD_HEIGHT; i++ )
5344             initialPosition[i][j] = s;
5345
5346         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5347         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5348         initialPosition[pawnRow][j] = WhitePawn;
5349         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5350         if(gameInfo.variant == VariantXiangqi) {
5351             if(j&1) {
5352                 initialPosition[pawnRow][j] = 
5353                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5354                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5355                    initialPosition[2][j] = WhiteCannon;
5356                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5357                 }
5358             }
5359         }
5360         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5361     }
5362     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5363
5364             j=BOARD_LEFT+1;
5365             initialPosition[1][j] = WhiteBishop;
5366             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5367             j=BOARD_RGHT-2;
5368             initialPosition[1][j] = WhiteRook;
5369             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5370     }
5371
5372     if( nrCastlingRights == -1) {
5373         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5374         /*       This sets default castling rights from none to normal corners   */
5375         /* Variants with other castling rights must set them themselves above    */
5376         nrCastlingRights = 6;
5377        
5378         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5379         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5380         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5381         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5382         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5383         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5384      }
5385
5386      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5387      if(gameInfo.variant == VariantGreat) { // promotion commoners
5388         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5389         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5390         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5391         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5392      }
5393   if (appData.debugMode) {
5394     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5395   }
5396     if(shuffleOpenings) {
5397         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5398         startedFromSetupPosition = TRUE;
5399     }
5400     if(startedFromPositionFile) {
5401       /* [HGM] loadPos: use PositionFile for every new game */
5402       CopyBoard(initialPosition, filePosition);
5403       for(i=0; i<nrCastlingRights; i++)
5404           initialRights[i] = filePosition[CASTLING][i];
5405       startedFromSetupPosition = TRUE;
5406     }
5407
5408     CopyBoard(boards[0], initialPosition);
5409
5410     if(oldx != gameInfo.boardWidth ||
5411        oldy != gameInfo.boardHeight ||
5412        oldh != gameInfo.holdingsWidth
5413 #ifdef GOTHIC
5414        || oldv == VariantGothic ||        // For licensing popups
5415        gameInfo.variant == VariantGothic
5416 #endif
5417 #ifdef FALCON
5418        || oldv == VariantFalcon ||
5419        gameInfo.variant == VariantFalcon
5420 #endif
5421                                          )
5422             InitDrawingSizes(-2 ,0);
5423
5424     if (redraw)
5425       DrawPosition(TRUE, boards[currentMove]);
5426 }
5427
5428 void
5429 SendBoard(cps, moveNum)
5430      ChessProgramState *cps;
5431      int moveNum;
5432 {
5433     char message[MSG_SIZ];
5434     
5435     if (cps->useSetboard) {
5436       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5437       sprintf(message, "setboard %s\n", fen);
5438       SendToProgram(message, cps);
5439       free(fen);
5440
5441     } else {
5442       ChessSquare *bp;
5443       int i, j;
5444       /* Kludge to set black to move, avoiding the troublesome and now
5445        * deprecated "black" command.
5446        */
5447       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5448
5449       SendToProgram("edit\n", cps);
5450       SendToProgram("#\n", cps);
5451       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5452         bp = &boards[moveNum][i][BOARD_LEFT];
5453         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5454           if ((int) *bp < (int) BlackPawn) {
5455             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5456                     AAA + j, ONE + i);
5457             if(message[0] == '+' || message[0] == '~') {
5458                 sprintf(message, "%c%c%c+\n",
5459                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5460                         AAA + j, ONE + i);
5461             }
5462             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5463                 message[1] = BOARD_RGHT   - 1 - j + '1';
5464                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5465             }
5466             SendToProgram(message, cps);
5467           }
5468         }
5469       }
5470     
5471       SendToProgram("c\n", cps);
5472       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5473         bp = &boards[moveNum][i][BOARD_LEFT];
5474         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5475           if (((int) *bp != (int) EmptySquare)
5476               && ((int) *bp >= (int) BlackPawn)) {
5477             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5478                     AAA + j, ONE + i);
5479             if(message[0] == '+' || message[0] == '~') {
5480                 sprintf(message, "%c%c%c+\n",
5481                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5482                         AAA + j, ONE + i);
5483             }
5484             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5485                 message[1] = BOARD_RGHT   - 1 - j + '1';
5486                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5487             }
5488             SendToProgram(message, cps);
5489           }
5490         }
5491       }
5492     
5493       SendToProgram(".\n", cps);
5494     }
5495     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5496 }
5497
5498 static int autoQueen; // [HGM] oneclick
5499
5500 int
5501 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5502 {
5503     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5504     /* [HGM] add Shogi promotions */
5505     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5506     ChessSquare piece;
5507     ChessMove moveType;
5508     Boolean premove;
5509
5510     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5511     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5512
5513     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5514       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5515         return FALSE;
5516
5517     piece = boards[currentMove][fromY][fromX];
5518     if(gameInfo.variant == VariantShogi) {
5519         promotionZoneSize = 3;
5520         highestPromotingPiece = (int)WhiteFerz;
5521     } else if(gameInfo.variant == VariantMakruk) {
5522         promotionZoneSize = 3;
5523     }
5524
5525     // next weed out all moves that do not touch the promotion zone at all
5526     if((int)piece >= BlackPawn) {
5527         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5528              return FALSE;
5529         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5530     } else {
5531         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5532            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5533     }
5534
5535     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5536
5537     // weed out mandatory Shogi promotions
5538     if(gameInfo.variant == VariantShogi) {
5539         if(piece >= BlackPawn) {
5540             if(toY == 0 && piece == BlackPawn ||
5541                toY == 0 && piece == BlackQueen ||
5542                toY <= 1 && piece == BlackKnight) {
5543                 *promoChoice = '+';
5544                 return FALSE;
5545             }
5546         } else {
5547             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5548                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5549                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5550                 *promoChoice = '+';
5551                 return FALSE;
5552             }
5553         }
5554     }
5555
5556     // weed out obviously illegal Pawn moves
5557     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5558         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5559         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5560         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5561         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5562         // note we are not allowed to test for valid (non-)capture, due to premove
5563     }
5564
5565     // we either have a choice what to promote to, or (in Shogi) whether to promote
5566     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5567         *promoChoice = PieceToChar(BlackFerz);  // no choice
5568         return FALSE;
5569     }
5570     if(autoQueen) { // predetermined
5571         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5572              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5573         else *promoChoice = PieceToChar(BlackQueen);
5574         return FALSE;
5575     }
5576
5577     // suppress promotion popup on illegal moves that are not premoves
5578     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5579               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5580     if(appData.testLegality && !premove) {
5581         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5582                         fromY, fromX, toY, toX, NULLCHAR);
5583         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5584            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5585             return FALSE;
5586     }
5587
5588     return TRUE;
5589 }
5590
5591 int
5592 InPalace(row, column)
5593      int row, column;
5594 {   /* [HGM] for Xiangqi */
5595     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5596          column < (BOARD_WIDTH + 4)/2 &&
5597          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5598     return FALSE;
5599 }
5600
5601 int
5602 PieceForSquare (x, y)
5603      int x;
5604      int y;
5605 {
5606   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5607      return -1;
5608   else
5609      return boards[currentMove][y][x];
5610 }
5611
5612 int
5613 OKToStartUserMove(x, y)
5614      int x, y;
5615 {
5616     ChessSquare from_piece;
5617     int white_piece;
5618
5619     if (matchMode) return FALSE;
5620     if (gameMode == EditPosition) return TRUE;
5621
5622     if (x >= 0 && y >= 0)
5623       from_piece = boards[currentMove][y][x];
5624     else
5625       from_piece = EmptySquare;
5626
5627     if (from_piece == EmptySquare) return FALSE;
5628
5629     white_piece = (int)from_piece >= (int)WhitePawn &&
5630       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5631
5632     switch (gameMode) {
5633       case PlayFromGameFile:
5634       case AnalyzeFile:
5635       case TwoMachinesPlay:
5636       case EndOfGame:
5637         return FALSE;
5638
5639       case IcsObserving:
5640       case IcsIdle:
5641         return FALSE;
5642
5643       case MachinePlaysWhite:
5644       case IcsPlayingBlack:
5645         if (appData.zippyPlay) return FALSE;
5646         if (white_piece) {
5647             DisplayMoveError(_("You are playing Black"));
5648             return FALSE;
5649         }
5650         break;
5651
5652       case MachinePlaysBlack:
5653       case IcsPlayingWhite:
5654         if (appData.zippyPlay) return FALSE;
5655         if (!white_piece) {
5656             DisplayMoveError(_("You are playing White"));
5657             return FALSE;
5658         }
5659         break;
5660
5661       case EditGame:
5662         if (!white_piece && WhiteOnMove(currentMove)) {
5663             DisplayMoveError(_("It is White's turn"));
5664             return FALSE;
5665         }           
5666         if (white_piece && !WhiteOnMove(currentMove)) {
5667             DisplayMoveError(_("It is Black's turn"));
5668             return FALSE;
5669         }           
5670         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5671             /* Editing correspondence game history */
5672             /* Could disallow this or prompt for confirmation */
5673             cmailOldMove = -1;
5674         }
5675         break;
5676
5677       case BeginningOfGame:
5678         if (appData.icsActive) return FALSE;
5679         if (!appData.noChessProgram) {
5680             if (!white_piece) {
5681                 DisplayMoveError(_("You are playing White"));
5682                 return FALSE;
5683             }
5684         }
5685         break;
5686         
5687       case Training:
5688         if (!white_piece && WhiteOnMove(currentMove)) {
5689             DisplayMoveError(_("It is White's turn"));
5690             return FALSE;
5691         }           
5692         if (white_piece && !WhiteOnMove(currentMove)) {
5693             DisplayMoveError(_("It is Black's turn"));
5694             return FALSE;
5695         }           
5696         break;
5697
5698       default:
5699       case IcsExamining:
5700         break;
5701     }
5702     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5703         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5704         && gameMode != AnalyzeFile && gameMode != Training) {
5705         DisplayMoveError(_("Displayed position is not current"));
5706         return FALSE;
5707     }
5708     return TRUE;
5709 }
5710
5711 Boolean
5712 OnlyMove(int *x, int *y, Boolean captures) {
5713     DisambiguateClosure cl;
5714     if (appData.zippyPlay) return FALSE;
5715     switch(gameMode) {
5716       case MachinePlaysBlack:
5717       case IcsPlayingWhite:
5718       case BeginningOfGame:
5719         if(!WhiteOnMove(currentMove)) return FALSE;
5720         break;
5721       case MachinePlaysWhite:
5722       case IcsPlayingBlack:
5723         if(WhiteOnMove(currentMove)) return FALSE;
5724         break;
5725       default:
5726         return FALSE;
5727     }
5728     cl.pieceIn = EmptySquare; 
5729     cl.rfIn = *y;
5730     cl.ffIn = *x;
5731     cl.rtIn = -1;
5732     cl.ftIn = -1;
5733     cl.promoCharIn = NULLCHAR;
5734     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5735     if( cl.kind == NormalMove ||
5736         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5737         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5738         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5739         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5740       fromX = cl.ff;
5741       fromY = cl.rf;
5742       *x = cl.ft;
5743       *y = cl.rt;
5744       return TRUE;
5745     }
5746     if(cl.kind != ImpossibleMove) return FALSE;
5747     cl.pieceIn = EmptySquare;
5748     cl.rfIn = -1;
5749     cl.ffIn = -1;
5750     cl.rtIn = *y;
5751     cl.ftIn = *x;
5752     cl.promoCharIn = NULLCHAR;
5753     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5754     if( cl.kind == NormalMove ||
5755         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5756         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5757         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5758         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5759       fromX = cl.ff;
5760       fromY = cl.rf;
5761       *x = cl.ft;
5762       *y = cl.rt;
5763       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5764       return TRUE;
5765     }
5766     return FALSE;
5767 }
5768
5769 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5770 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5771 int lastLoadGameUseList = FALSE;
5772 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5773 ChessMove lastLoadGameStart = (ChessMove) 0;
5774
5775 ChessMove
5776 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5777      int fromX, fromY, toX, toY;
5778      int promoChar;
5779      Boolean captureOwn;
5780 {
5781     ChessMove moveType;
5782     ChessSquare pdown, pup;
5783
5784     /* Check if the user is playing in turn.  This is complicated because we
5785        let the user "pick up" a piece before it is his turn.  So the piece he
5786        tried to pick up may have been captured by the time he puts it down!
5787        Therefore we use the color the user is supposed to be playing in this
5788        test, not the color of the piece that is currently on the starting
5789        square---except in EditGame mode, where the user is playing both
5790        sides; fortunately there the capture race can't happen.  (It can
5791        now happen in IcsExamining mode, but that's just too bad.  The user
5792        will get a somewhat confusing message in that case.)
5793        */
5794
5795     switch (gameMode) {
5796       case PlayFromGameFile:
5797       case AnalyzeFile:
5798       case TwoMachinesPlay:
5799       case EndOfGame:
5800       case IcsObserving:
5801       case IcsIdle:
5802         /* We switched into a game mode where moves are not accepted,
5803            perhaps while the mouse button was down. */
5804         return ImpossibleMove;
5805
5806       case MachinePlaysWhite:
5807         /* User is moving for Black */
5808         if (WhiteOnMove(currentMove)) {
5809             DisplayMoveError(_("It is White's turn"));
5810             return ImpossibleMove;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815         /* User is moving for White */
5816         if (!WhiteOnMove(currentMove)) {
5817             DisplayMoveError(_("It is Black's turn"));
5818             return ImpossibleMove;
5819         }
5820         break;
5821
5822       case EditGame:
5823       case IcsExamining:
5824       case BeginningOfGame:
5825       case AnalyzeMode:
5826       case Training:
5827         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5828             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5829             /* User is moving for Black */
5830             if (WhiteOnMove(currentMove)) {
5831                 DisplayMoveError(_("It is White's turn"));
5832                 return ImpossibleMove;
5833             }
5834         } else {
5835             /* User is moving for White */
5836             if (!WhiteOnMove(currentMove)) {
5837                 DisplayMoveError(_("It is Black's turn"));
5838                 return ImpossibleMove;
5839             }
5840         }
5841         break;
5842
5843       case IcsPlayingBlack:
5844         /* User is moving for Black */
5845         if (WhiteOnMove(currentMove)) {
5846             if (!appData.premove) {
5847                 DisplayMoveError(_("It is White's turn"));
5848             } else if (toX >= 0 && toY >= 0) {
5849                 premoveToX = toX;
5850                 premoveToY = toY;
5851                 premoveFromX = fromX;
5852                 premoveFromY = fromY;
5853                 premovePromoChar = promoChar;
5854                 gotPremove = 1;
5855                 if (appData.debugMode) 
5856                     fprintf(debugFP, "Got premove: fromX %d,"
5857                             "fromY %d, toX %d, toY %d\n",
5858                             fromX, fromY, toX, toY);
5859             }
5860             return ImpossibleMove;
5861         }
5862         break;
5863
5864       case IcsPlayingWhite:
5865         /* User is moving for White */
5866         if (!WhiteOnMove(currentMove)) {
5867             if (!appData.premove) {
5868                 DisplayMoveError(_("It is Black's turn"));
5869             } else if (toX >= 0 && toY >= 0) {
5870                 premoveToX = toX;
5871                 premoveToY = toY;
5872                 premoveFromX = fromX;
5873                 premoveFromY = fromY;
5874                 premovePromoChar = promoChar;
5875                 gotPremove = 1;
5876                 if (appData.debugMode) 
5877                     fprintf(debugFP, "Got premove: fromX %d,"
5878                             "fromY %d, toX %d, toY %d\n",
5879                             fromX, fromY, toX, toY);
5880             }
5881             return ImpossibleMove;
5882         }
5883         break;
5884
5885       default:
5886         break;
5887
5888       case EditPosition:
5889         /* EditPosition, empty square, or different color piece;
5890            click-click move is possible */
5891         if (toX == -2 || toY == -2) {
5892             boards[0][fromY][fromX] = EmptySquare;
5893             return AmbiguousMove;
5894         } else if (toX >= 0 && toY >= 0) {
5895             boards[0][toY][toX] = boards[0][fromY][fromX];
5896             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5897                 if(boards[0][fromY][0] != EmptySquare) {
5898                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5899                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5900                 }
5901             } else
5902             if(fromX == BOARD_RGHT+1) {
5903                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5904                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5905                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5906                 }
5907             } else
5908             boards[0][fromY][fromX] = EmptySquare;
5909             return AmbiguousMove;
5910         }
5911         return ImpossibleMove;
5912     }
5913
5914     if(toX < 0 || toY < 0) return ImpossibleMove;
5915     pdown = boards[currentMove][fromY][fromX];
5916     pup = boards[currentMove][toY][toX];
5917
5918     /* [HGM] If move started in holdings, it means a drop */
5919     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5920          if( pup != EmptySquare ) return ImpossibleMove;
5921          if(appData.testLegality) {
5922              /* it would be more logical if LegalityTest() also figured out
5923               * which drops are legal. For now we forbid pawns on back rank.
5924               * Shogi is on its own here...
5925               */
5926              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5927                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5928                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5929          }
5930          return WhiteDrop; /* Not needed to specify white or black yet */
5931     }
5932
5933     /* [HGM] always test for legality, to get promotion info */
5934     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5935                                          fromY, fromX, toY, toX, promoChar);
5936     /* [HGM] but possibly ignore an IllegalMove result */
5937     if (appData.testLegality) {
5938         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5939             DisplayMoveError(_("Illegal move"));
5940             return ImpossibleMove;
5941         }
5942     }
5943
5944     return moveType;
5945     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5946        function is made into one that returns an OK move type if FinishMove
5947        should be called. This to give the calling driver routine the
5948        opportunity to finish the userMove input with a promotion popup,
5949        without bothering the user with this for invalid or illegal moves */
5950
5951 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5952 }
5953
5954 /* Common tail of UserMoveEvent and DropMenuEvent */
5955 int
5956 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5957      ChessMove moveType;
5958      int fromX, fromY, toX, toY;
5959      /*char*/int promoChar;
5960 {
5961     char *bookHit = 0;
5962
5963     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5964         // [HGM] superchess: suppress promotions to non-available piece
5965         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5966         if(WhiteOnMove(currentMove)) {
5967             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5968         } else {
5969             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5970         }
5971     }
5972
5973     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5974        move type in caller when we know the move is a legal promotion */
5975     if(moveType == NormalMove && promoChar)
5976         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5977
5978     /* [HGM] convert drag-and-drop piece drops to standard form */
5979     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5980          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5981            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5982                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5983            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5984            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5985            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5986            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5987          fromY = DROP_RANK;
5988     }
5989
5990     /* [HGM] <popupFix> The following if has been moved here from
5991        UserMoveEvent(). Because it seemed to belong here (why not allow
5992        piece drops in training games?), and because it can only be
5993        performed after it is known to what we promote. */
5994     if (gameMode == Training) {
5995       /* compare the move played on the board to the next move in the
5996        * game. If they match, display the move and the opponent's response. 
5997        * If they don't match, display an error message.
5998        */
5999       int saveAnimate;
6000       Board testBoard;
6001       CopyBoard(testBoard, boards[currentMove]);
6002       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6003
6004       if (CompareBoards(testBoard, boards[currentMove+1])) {
6005         ForwardInner(currentMove+1);
6006
6007         /* Autoplay the opponent's response.
6008          * if appData.animate was TRUE when Training mode was entered,
6009          * the response will be animated.
6010          */
6011         saveAnimate = appData.animate;
6012         appData.animate = animateTraining;
6013         ForwardInner(currentMove+1);
6014         appData.animate = saveAnimate;
6015
6016         /* check for the end of the game */
6017         if (currentMove >= forwardMostMove) {
6018           gameMode = PlayFromGameFile;
6019           ModeHighlight();
6020           SetTrainingModeOff();
6021           DisplayInformation(_("End of game"));
6022         }
6023       } else {
6024         DisplayError(_("Incorrect move"), 0);
6025       }
6026       return 1;
6027     }
6028
6029   /* Ok, now we know that the move is good, so we can kill
6030      the previous line in Analysis Mode */
6031   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6032                                 && currentMove < forwardMostMove) {
6033     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6034   }
6035
6036   /* If we need the chess program but it's dead, restart it */
6037   ResurrectChessProgram();
6038
6039   /* A user move restarts a paused game*/
6040   if (pausing)
6041     PauseEvent();
6042
6043   thinkOutput[0] = NULLCHAR;
6044
6045   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6046
6047   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6048
6049   if (gameMode == BeginningOfGame) {
6050     if (appData.noChessProgram) {
6051       gameMode = EditGame;
6052       SetGameInfo();
6053     } else {
6054       char buf[MSG_SIZ];
6055       gameMode = MachinePlaysBlack;
6056       StartClocks();
6057       SetGameInfo();
6058       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6059       DisplayTitle(buf);
6060       if (first.sendName) {
6061         sprintf(buf, "name %s\n", gameInfo.white);
6062         SendToProgram(buf, &first);
6063       }
6064       StartClocks();
6065     }
6066     ModeHighlight();
6067   }
6068
6069   /* Relay move to ICS or chess engine */
6070   if (appData.icsActive) {
6071     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6072         gameMode == IcsExamining) {
6073       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6074         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6075         SendToICS("draw ");
6076         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6077       }
6078       // also send plain move, in case ICS does not understand atomic claims
6079       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6080       ics_user_moved = 1;
6081     }
6082   } else {
6083     if (first.sendTime && (gameMode == BeginningOfGame ||
6084                            gameMode == MachinePlaysWhite ||
6085                            gameMode == MachinePlaysBlack)) {
6086       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6087     }
6088     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6089          // [HGM] book: if program might be playing, let it use book
6090         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6091         first.maybeThinking = TRUE;
6092     } else SendMoveToProgram(forwardMostMove-1, &first);
6093     if (currentMove == cmailOldMove + 1) {
6094       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6095     }
6096   }
6097
6098   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099
6100   switch (gameMode) {
6101   case EditGame:
6102     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6103     case MT_NONE:
6104     case MT_CHECK:
6105       break;
6106     case MT_CHECKMATE:
6107     case MT_STAINMATE:
6108       if (WhiteOnMove(currentMove)) {
6109         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6110       } else {
6111         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6112       }
6113       break;
6114     case MT_STALEMATE:
6115       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6116       break;
6117     }
6118     break;
6119     
6120   case MachinePlaysBlack:
6121   case MachinePlaysWhite:
6122     /* disable certain menu options while machine is thinking */
6123     SetMachineThinkingEnables();
6124     break;
6125
6126   default:
6127     break;
6128   }
6129
6130   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6131         
6132   if(bookHit) { // [HGM] book: simulate book reply
6133         static char bookMove[MSG_SIZ]; // a bit generous?
6134
6135         programStats.nodes = programStats.depth = programStats.time = 
6136         programStats.score = programStats.got_only_move = 0;
6137         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6138
6139         strcpy(bookMove, "move ");
6140         strcat(bookMove, bookHit);
6141         HandleMachineMove(bookMove, &first);
6142   }
6143   return 1;
6144 }
6145
6146 void
6147 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6148      int fromX, fromY, toX, toY;
6149      int promoChar;
6150 {
6151     /* [HGM] This routine was added to allow calling of its two logical
6152        parts from other modules in the old way. Before, UserMoveEvent()
6153        automatically called FinishMove() if the move was OK, and returned
6154        otherwise. I separated the two, in order to make it possible to
6155        slip a promotion popup in between. But that it always needs two
6156        calls, to the first part, (now called UserMoveTest() ), and to
6157        FinishMove if the first part succeeded. Calls that do not need
6158        to do anything in between, can call this routine the old way. 
6159     */
6160     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6161 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6162     if(moveType == AmbiguousMove)
6163         DrawPosition(FALSE, boards[currentMove]);
6164     else if(moveType != ImpossibleMove && moveType != Comment)
6165         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6166 }
6167
6168 void
6169 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6170      Board board;
6171      int flags;
6172      ChessMove kind;
6173      int rf, ff, rt, ft;
6174      VOIDSTAR closure;
6175 {
6176     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6177     Markers *m = (Markers *) closure;
6178     if(rf == fromY && ff == fromX)
6179         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6180                          || kind == WhiteCapturesEnPassant
6181                          || kind == BlackCapturesEnPassant);
6182     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6183 }
6184
6185 void
6186 MarkTargetSquares(int clear)
6187 {
6188   int x, y;
6189   if(!appData.markers || !appData.highlightDragging || 
6190      !appData.testLegality || gameMode == EditPosition) return;
6191   if(clear) {
6192     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6193   } else {
6194     int capt = 0;
6195     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6196     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6197       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6198       if(capt)
6199       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6200     }
6201   }
6202   DrawPosition(TRUE, NULL);
6203 }
6204
6205 void LeftClick(ClickType clickType, int xPix, int yPix)
6206 {
6207     int x, y;
6208     Boolean saveAnimate;
6209     static int second = 0, promotionChoice = 0;
6210     char promoChoice = NULLCHAR;
6211
6212     if(appData.seekGraph && appData.icsActive && loggedOn &&
6213         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6214         SeekGraphClick(clickType, xPix, yPix, 0);
6215         return;
6216     }
6217
6218     if (clickType == Press) ErrorPopDown();
6219     MarkTargetSquares(1);
6220
6221     x = EventToSquare(xPix, BOARD_WIDTH);
6222     y = EventToSquare(yPix, BOARD_HEIGHT);
6223     if (!flipView && y >= 0) {
6224         y = BOARD_HEIGHT - 1 - y;
6225     }
6226     if (flipView && x >= 0) {
6227         x = BOARD_WIDTH - 1 - x;
6228     }
6229
6230     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6231         if(clickType == Release) return; // ignore upclick of click-click destination
6232         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6233         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6234         if(gameInfo.holdingsWidth && 
6235                 (WhiteOnMove(currentMove) 
6236                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6237                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6238             // click in right holdings, for determining promotion piece
6239             ChessSquare p = boards[currentMove][y][x];
6240             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6241             if(p != EmptySquare) {
6242                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6243                 fromX = fromY = -1;
6244                 return;
6245             }
6246         }
6247         DrawPosition(FALSE, boards[currentMove]);
6248         return;
6249     }
6250
6251     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6252     if(clickType == Press
6253             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6254               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6255               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6256         return;
6257
6258     autoQueen = appData.alwaysPromoteToQueen;
6259
6260     if (fromX == -1) {
6261       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6262         if (clickType == Press) {
6263             /* First square */
6264             if (OKToStartUserMove(x, y)) {
6265                 fromX = x;
6266                 fromY = y;
6267                 second = 0;
6268                 MarkTargetSquares(0);
6269                 DragPieceBegin(xPix, yPix);
6270                 if (appData.highlightDragging) {
6271                     SetHighlights(x, y, -1, -1);
6272                 }
6273             }
6274         }
6275         return;
6276       }
6277     }
6278
6279     /* fromX != -1 */
6280     if (clickType == Press && gameMode != EditPosition) {
6281         ChessSquare fromP;
6282         ChessSquare toP;
6283         int frc;
6284
6285         // ignore off-board to clicks
6286         if(y < 0 || x < 0) return;
6287
6288         /* Check if clicking again on the same color piece */
6289         fromP = boards[currentMove][fromY][fromX];
6290         toP = boards[currentMove][y][x];
6291         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6292         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6293              WhitePawn <= toP && toP <= WhiteKing &&
6294              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6295              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6296             (BlackPawn <= fromP && fromP <= BlackKing && 
6297              BlackPawn <= toP && toP <= BlackKing &&
6298              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6299              !(fromP == BlackKing && toP == BlackRook && frc))) {
6300             /* Clicked again on same color piece -- changed his mind */
6301             second = (x == fromX && y == fromY);
6302            if(!second || !OnlyMove(&x, &y, TRUE)) {
6303             if (appData.highlightDragging) {
6304                 SetHighlights(x, y, -1, -1);
6305             } else {
6306                 ClearHighlights();
6307             }
6308             if (OKToStartUserMove(x, y)) {
6309                 fromX = x;
6310                 fromY = y;
6311                 MarkTargetSquares(0);
6312                 DragPieceBegin(xPix, yPix);
6313             }
6314             return;
6315            }
6316         }
6317         // ignore clicks on holdings
6318         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6319     }
6320
6321     if (clickType == Release && x == fromX && y == fromY) {
6322         DragPieceEnd(xPix, yPix);
6323         if (appData.animateDragging) {
6324             /* Undo animation damage if any */
6325             DrawPosition(FALSE, NULL);
6326         }
6327         if (second) {
6328             /* Second up/down in same square; just abort move */
6329             second = 0;
6330             fromX = fromY = -1;
6331             ClearHighlights();
6332             gotPremove = 0;
6333             ClearPremoveHighlights();
6334         } else {
6335             /* First upclick in same square; start click-click mode */
6336             SetHighlights(x, y, -1, -1);
6337         }
6338         return;
6339     }
6340
6341     /* we now have a different from- and (possibly off-board) to-square */
6342     /* Completed move */
6343     toX = x;
6344     toY = y;
6345     saveAnimate = appData.animate;
6346     if (clickType == Press) {
6347         /* Finish clickclick move */
6348         if (appData.animate || appData.highlightLastMove) {
6349             SetHighlights(fromX, fromY, toX, toY);
6350         } else {
6351             ClearHighlights();
6352         }
6353     } else {
6354         /* Finish drag move */
6355         if (appData.highlightLastMove) {
6356             SetHighlights(fromX, fromY, toX, toY);
6357         } else {
6358             ClearHighlights();
6359         }
6360         DragPieceEnd(xPix, yPix);
6361         /* Don't animate move and drag both */
6362         appData.animate = FALSE;
6363     }
6364
6365     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6366     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6367         ChessSquare piece = boards[currentMove][fromY][fromX];
6368         if(gameMode == EditPosition && piece != EmptySquare &&
6369            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6370             int n;
6371              
6372             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6373                 n = PieceToNumber(piece - (int)BlackPawn);
6374                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6375                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6376                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6377             } else
6378             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6379                 n = PieceToNumber(piece);
6380                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6381                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6382                 boards[currentMove][n][BOARD_WIDTH-2]++;
6383             }
6384             boards[currentMove][fromY][fromX] = EmptySquare;
6385         }
6386         ClearHighlights();
6387         fromX = fromY = -1;
6388         DrawPosition(TRUE, boards[currentMove]);
6389         return;
6390     }
6391
6392     // off-board moves should not be highlighted
6393     if(x < 0 || x < 0) ClearHighlights();
6394
6395     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6396         SetHighlights(fromX, fromY, toX, toY);
6397         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6398             // [HGM] super: promotion to captured piece selected from holdings
6399             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6400             promotionChoice = TRUE;
6401             // kludge follows to temporarily execute move on display, without promoting yet
6402             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6403             boards[currentMove][toY][toX] = p;
6404             DrawPosition(FALSE, boards[currentMove]);
6405             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6406             boards[currentMove][toY][toX] = q;
6407             DisplayMessage("Click in holdings to choose piece", "");
6408             return;
6409         }
6410         PromotionPopUp();
6411     } else {
6412         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6413         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6414         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6415         fromX = fromY = -1;
6416     }
6417     appData.animate = saveAnimate;
6418     if (appData.animate || appData.animateDragging) {
6419         /* Undo animation damage if needed */
6420         DrawPosition(FALSE, NULL);
6421     }
6422 }
6423
6424 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6425 {   // front-end-free part taken out of PieceMenuPopup
6426     int whichMenu; int xSqr, ySqr;
6427
6428     if(seekGraphUp) { // [HGM] seekgraph
6429         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6430         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6431         return -2;
6432     }
6433
6434     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6435          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6436         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6437         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6438         return -2;
6439     }
6440
6441     xSqr = EventToSquare(x, BOARD_WIDTH);
6442     ySqr = EventToSquare(y, BOARD_HEIGHT);
6443     if (action == Release) UnLoadPV(); // [HGM] pv
6444     if (action != Press) return -2; // return code to be ignored
6445     switch (gameMode) {
6446       case IcsExamining:
6447         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6448       case EditPosition:
6449         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6450         if (xSqr < 0 || ySqr < 0) return -1;\r
6451         whichMenu = 0; // edit-position menu
6452         break;
6453       case IcsObserving:
6454         if(!appData.icsEngineAnalyze) return -1;
6455       case IcsPlayingWhite:
6456       case IcsPlayingBlack:
6457         if(!appData.zippyPlay) goto noZip;
6458       case AnalyzeMode:
6459       case AnalyzeFile:
6460       case MachinePlaysWhite:
6461       case MachinePlaysBlack:
6462       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6463         if (!appData.dropMenu) {
6464           LoadPV(x, y);
6465           return 2; // flag front-end to grab mouse events
6466         }
6467         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6468            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6469       case EditGame:
6470       noZip:
6471         if (xSqr < 0 || ySqr < 0) return -1;
6472         if (!appData.dropMenu || appData.testLegality &&
6473             gameInfo.variant != VariantBughouse &&
6474             gameInfo.variant != VariantCrazyhouse) return -1;
6475         whichMenu = 1; // drop menu
6476         break;
6477       default:
6478         return -1;
6479     }
6480
6481     if (((*fromX = xSqr) < 0) ||
6482         ((*fromY = ySqr) < 0)) {
6483         *fromX = *fromY = -1;
6484         return -1;
6485     }
6486     if (flipView)
6487       *fromX = BOARD_WIDTH - 1 - *fromX;
6488     else
6489       *fromY = BOARD_HEIGHT - 1 - *fromY;
6490
6491     return whichMenu;
6492 }
6493
6494 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6495 {
6496 //    char * hint = lastHint;
6497     FrontEndProgramStats stats;
6498
6499     stats.which = cps == &first ? 0 : 1;
6500     stats.depth = cpstats->depth;
6501     stats.nodes = cpstats->nodes;
6502     stats.score = cpstats->score;
6503     stats.time = cpstats->time;
6504     stats.pv = cpstats->movelist;
6505     stats.hint = lastHint;
6506     stats.an_move_index = 0;
6507     stats.an_move_count = 0;
6508
6509     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6510         stats.hint = cpstats->move_name;
6511         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6512         stats.an_move_count = cpstats->nr_moves;
6513     }
6514
6515     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6516
6517     SetProgramStats( &stats );
6518 }
6519
6520 int
6521 Adjudicate(ChessProgramState *cps)
6522 {       // [HGM] some adjudications useful with buggy engines
6523         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6524         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6525         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6526         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6527         int k, count = 0; static int bare = 1;
6528         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6529         Boolean canAdjudicate = !appData.icsActive;
6530
6531         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6532         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6533             if( appData.testLegality )
6534             {   /* [HGM] Some more adjudications for obstinate engines */
6535                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6536                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6537                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6538                 static int moveCount = 6;
6539                 ChessMove result;
6540                 char *reason = NULL;
6541
6542                 /* Count what is on board. */
6543                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6544                 {   ChessSquare p = boards[forwardMostMove][i][j];
6545                     int m=i;
6546
6547                     switch((int) p)
6548                     {   /* count B,N,R and other of each side */
6549                         case WhiteKing:
6550                         case BlackKing:
6551                              NrK++; break; // [HGM] atomic: count Kings
6552                         case WhiteKnight:
6553                              NrWN++; break;
6554                         case WhiteBishop:
6555                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6556                              bishopsColor |= 1 << ((i^j)&1);
6557                              NrWB++; break;
6558                         case BlackKnight:
6559                              NrBN++; break;
6560                         case BlackBishop:
6561                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6562                              bishopsColor |= 1 << ((i^j)&1);
6563                              NrBB++; break;
6564                         case WhiteRook:
6565                              NrWR++; break;
6566                         case BlackRook:
6567                              NrBR++; break;
6568                         case WhiteQueen:
6569                              NrWQ++; break;
6570                         case BlackQueen:
6571                              NrBQ++; break;
6572                         case EmptySquare: 
6573                              break;
6574                         case BlackPawn:
6575                              m = 7-i;
6576                         case WhitePawn:
6577                              PawnAdvance += m; NrPawns++;
6578                     }
6579                     NrPieces += (p != EmptySquare);
6580                     NrW += ((int)p < (int)BlackPawn);
6581                     if(gameInfo.variant == VariantXiangqi && 
6582                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6583                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6584                         NrW -= ((int)p < (int)BlackPawn);
6585                     }
6586                 }
6587
6588                 /* Some material-based adjudications that have to be made before stalemate test */
6589                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6590                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6591                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6592                      if(canAdjudicate && appData.checkMates) {
6593                          if(engineOpponent)
6594                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6595                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6596                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6597                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6598                          return 1;
6599                      }
6600                 }
6601
6602                 /* Bare King in Shatranj (loses) or Losers (wins) */
6603                 if( NrW == 1 || NrPieces - NrW == 1) {
6604                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6605                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6606                      if(canAdjudicate && appData.checkMates) {
6607                          if(engineOpponent)
6608                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6609                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6610                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6611                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6612                          return 1;
6613                      }
6614                   } else
6615                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6616                   {    /* bare King */
6617                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6618                         if(canAdjudicate && appData.checkMates) {
6619                             /* but only adjudicate if adjudication enabled */
6620                             if(engineOpponent)
6621                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6622                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6623                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6624                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6625                             return 1;
6626                         }
6627                   }
6628                 } else bare = 1;
6629
6630
6631             // don't wait for engine to announce game end if we can judge ourselves
6632             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6633               case MT_CHECK:
6634                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6635                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6636                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6637                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6638                             checkCnt++;
6639                         if(checkCnt >= 2) {
6640                             reason = "Xboard adjudication: 3rd check";
6641                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6642                             break;
6643                         }
6644                     }
6645                 }
6646               case MT_NONE:
6647               default:
6648                 break;
6649               case MT_STALEMATE:
6650               case MT_STAINMATE:
6651                 reason = "Xboard adjudication: Stalemate";
6652                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6653                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6654                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6655                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6656                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6657                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6658                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6659                                                                         EP_CHECKMATE : EP_WINS);
6660                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6661                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6662                 }
6663                 break;
6664               case MT_CHECKMATE:
6665                 reason = "Xboard adjudication: Checkmate";
6666                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6667                 break;
6668             }
6669
6670                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6671                     case EP_STALEMATE:
6672                         result = GameIsDrawn; break;
6673                     case EP_CHECKMATE:
6674                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6675                     case EP_WINS:
6676                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6677                     default:
6678                         result = (ChessMove) 0;
6679                 }
6680                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6681                     if(engineOpponent)
6682                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6683                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6684                     GameEnds( result, reason, GE_XBOARD );
6685                     return 1;
6686                 }
6687
6688                 /* Next absolutely insufficient mating material. */
6689                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6690                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6691                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6692                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6693                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6694
6695                      /* always flag draws, for judging claims */
6696                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6697
6698                      if(canAdjudicate && appData.materialDraws) {
6699                          /* but only adjudicate them if adjudication enabled */
6700                          if(engineOpponent) {
6701                            SendToProgram("force\n", engineOpponent); // suppress reply
6702                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6703                          }
6704                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6705                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6706                          return 1;
6707                      }
6708                 }
6709
6710                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6711                 if(NrPieces == 4 && 
6712                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6713                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6714                    || NrWN==2 || NrBN==2     /* KNNK */
6715                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6716                   ) ) {
6717                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6718                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6719                           if(engineOpponent) {
6720                             SendToProgram("force\n", engineOpponent); // suppress reply
6721                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6722                           }
6723                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6724                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6725                           return 1;
6726                      }
6727                 } else moveCount = 6;
6728             }
6729         }
6730           
6731         if (appData.debugMode) { int i;
6732             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6733                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6734                     appData.drawRepeats);
6735             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6736               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6737             
6738         }
6739
6740         // Repetition draws and 50-move rule can be applied independently of legality testing
6741
6742                 /* Check for rep-draws */
6743                 count = 0;
6744                 for(k = forwardMostMove-2;
6745                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6746                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6747                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6748                     k-=2)
6749                 {   int rights=0;
6750                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6751                         /* compare castling rights */
6752                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6753                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6754                                 rights++; /* King lost rights, while rook still had them */
6755                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6756                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6757                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6758                                    rights++; /* but at least one rook lost them */
6759                         }
6760                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6761                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6762                                 rights++; 
6763                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6764                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6765                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6766                                    rights++;
6767                         }
6768                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6769                             && appData.drawRepeats > 1) {
6770                              /* adjudicate after user-specified nr of repeats */
6771                              if(engineOpponent) {
6772                                SendToProgram("force\n", engineOpponent); // suppress reply
6773                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6774                              }
6775                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6776                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6777                                 // [HGM] xiangqi: check for forbidden perpetuals
6778                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6779                                 for(m=forwardMostMove; m>k; m-=2) {
6780                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6781                                         ourPerpetual = 0; // the current mover did not always check
6782                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6783                                         hisPerpetual = 0; // the opponent did not always check
6784                                 }
6785                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6786                                                                         ourPerpetual, hisPerpetual);
6787                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6788                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6789                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6790                                     return 1;
6791                                 }
6792                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6793                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6794                                 // Now check for perpetual chases
6795                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6796                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6797                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6798                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6799                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6800                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6801                                         return 1;
6802                                     }
6803                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6804                                         break; // Abort repetition-checking loop.
6805                                 }
6806                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6807                              }
6808                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6809                              return 1;
6810                         }
6811                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6812                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6813                     }
6814                 }
6815
6816                 /* Now we test for 50-move draws. Determine ply count */
6817                 count = forwardMostMove;
6818                 /* look for last irreversble move */
6819                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6820                     count--;
6821                 /* if we hit starting position, add initial plies */
6822                 if( count == backwardMostMove )
6823                     count -= initialRulePlies;
6824                 count = forwardMostMove - count; 
6825                 if( count >= 100)
6826                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6827                          /* this is used to judge if draw claims are legal */
6828                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6829                          if(engineOpponent) {
6830                            SendToProgram("force\n", engineOpponent); // suppress reply
6831                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6832                          }
6833                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6834                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6835                          return 1;
6836                 }
6837
6838                 /* if draw offer is pending, treat it as a draw claim
6839                  * when draw condition present, to allow engines a way to
6840                  * claim draws before making their move to avoid a race
6841                  * condition occurring after their move
6842                  */
6843                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6844                          char *p = NULL;
6845                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6846                              p = "Draw claim: 50-move rule";
6847                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6848                              p = "Draw claim: 3-fold repetition";
6849                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6850                              p = "Draw claim: insufficient mating material";
6851                          if( p != NULL && canAdjudicate) {
6852                              if(engineOpponent) {
6853                                SendToProgram("force\n", engineOpponent); // suppress reply
6854                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6855                              }
6856                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6857                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6858                              return 1;
6859                          }
6860                 }
6861
6862                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6863                     if(engineOpponent) {
6864                       SendToProgram("force\n", engineOpponent); // suppress reply
6865                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6866                     }
6867                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6868                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6869                     return 1;
6870                 }
6871         return 0;
6872 }
6873
6874 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6875 {   // [HGM] book: this routine intercepts moves to simulate book replies
6876     char *bookHit = NULL;
6877
6878     //first determine if the incoming move brings opponent into his book
6879     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6880         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6881     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6882     if(bookHit != NULL && !cps->bookSuspend) {
6883         // make sure opponent is not going to reply after receiving move to book position
6884         SendToProgram("force\n", cps);
6885         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6886     }
6887     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6888     // now arrange restart after book miss
6889     if(bookHit) {
6890         // after a book hit we never send 'go', and the code after the call to this routine
6891         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6892         char buf[MSG_SIZ];
6893         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6894         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6895         SendToProgram(buf, cps);
6896         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6897     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6898         SendToProgram("go\n", cps);
6899         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6900     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6901         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6902             SendToProgram("go\n", cps); 
6903         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6904     }
6905     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6906 }
6907
6908 char *savedMessage;
6909 ChessProgramState *savedState;
6910 void DeferredBookMove(void)
6911 {
6912         if(savedState->lastPing != savedState->lastPong)
6913                     ScheduleDelayedEvent(DeferredBookMove, 10);
6914         else
6915         HandleMachineMove(savedMessage, savedState);
6916 }
6917
6918 void
6919 HandleMachineMove(message, cps)
6920      char *message;
6921      ChessProgramState *cps;
6922 {
6923     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6924     char realname[MSG_SIZ];
6925     int fromX, fromY, toX, toY;
6926     ChessMove moveType;
6927     char promoChar;
6928     char *p;
6929     int machineWhite;
6930     char *bookHit;
6931
6932     cps->userError = 0;
6933
6934 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6935     /*
6936      * Kludge to ignore BEL characters
6937      */
6938     while (*message == '\007') message++;
6939
6940     /*
6941      * [HGM] engine debug message: ignore lines starting with '#' character
6942      */
6943     if(cps->debug && *message == '#') return;
6944
6945     /*
6946      * Look for book output
6947      */
6948     if (cps == &first && bookRequested) {
6949         if (message[0] == '\t' || message[0] == ' ') {
6950             /* Part of the book output is here; append it */
6951             strcat(bookOutput, message);
6952             strcat(bookOutput, "  \n");
6953             return;
6954         } else if (bookOutput[0] != NULLCHAR) {
6955             /* All of book output has arrived; display it */
6956             char *p = bookOutput;
6957             while (*p != NULLCHAR) {
6958                 if (*p == '\t') *p = ' ';
6959                 p++;
6960             }
6961             DisplayInformation(bookOutput);
6962             bookRequested = FALSE;
6963             /* Fall through to parse the current output */
6964         }
6965     }
6966
6967     /*
6968      * Look for machine move.
6969      */
6970     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6971         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6972     {
6973         /* This method is only useful on engines that support ping */
6974         if (cps->lastPing != cps->lastPong) {
6975           if (gameMode == BeginningOfGame) {
6976             /* Extra move from before last new; ignore */
6977             if (appData.debugMode) {
6978                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6979             }
6980           } else {
6981             if (appData.debugMode) {
6982                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6983                         cps->which, gameMode);
6984             }
6985
6986             SendToProgram("undo\n", cps);
6987           }
6988           return;
6989         }
6990
6991         switch (gameMode) {
6992           case BeginningOfGame:
6993             /* Extra move from before last reset; ignore */
6994             if (appData.debugMode) {
6995                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6996             }
6997             return;
6998
6999           case EndOfGame:
7000           case IcsIdle:
7001           default:
7002             /* Extra move after we tried to stop.  The mode test is
7003                not a reliable way of detecting this problem, but it's
7004                the best we can do on engines that don't support ping.
7005             */
7006             if (appData.debugMode) {
7007                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7008                         cps->which, gameMode);
7009             }
7010             SendToProgram("undo\n", cps);
7011             return;
7012
7013           case MachinePlaysWhite:
7014           case IcsPlayingWhite:
7015             machineWhite = TRUE;
7016             break;
7017
7018           case MachinePlaysBlack:
7019           case IcsPlayingBlack:
7020             machineWhite = FALSE;
7021             break;
7022
7023           case TwoMachinesPlay:
7024             machineWhite = (cps->twoMachinesColor[0] == 'w');
7025             break;
7026         }
7027         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7028             if (appData.debugMode) {
7029                 fprintf(debugFP,
7030                         "Ignoring move out of turn by %s, gameMode %d"
7031                         ", forwardMost %d\n",
7032                         cps->which, gameMode, forwardMostMove);
7033             }
7034             return;
7035         }
7036
7037     if (appData.debugMode) { int f = forwardMostMove;
7038         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7039                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7040                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7041     }
7042         if(cps->alphaRank) AlphaRank(machineMove, 4);
7043         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7044                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7045             /* Machine move could not be parsed; ignore it. */
7046             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7047                     machineMove, cps->which);
7048             DisplayError(buf1, 0);
7049             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7050                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7051             if (gameMode == TwoMachinesPlay) {
7052               GameEnds(machineWhite ? BlackWins : WhiteWins,
7053                        buf1, GE_XBOARD);
7054             }
7055             return;
7056         }
7057
7058         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7059         /* So we have to redo legality test with true e.p. status here,  */
7060         /* to make sure an illegal e.p. capture does not slip through,   */
7061         /* to cause a forfeit on a justified illegal-move complaint      */
7062         /* of the opponent.                                              */
7063         if( gameMode==TwoMachinesPlay && appData.testLegality
7064             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7065                                                               ) {
7066            ChessMove moveType;
7067            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7068                              fromY, fromX, toY, toX, promoChar);
7069             if (appData.debugMode) {
7070                 int i;
7071                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7072                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7073                 fprintf(debugFP, "castling rights\n");
7074             }
7075             if(moveType == IllegalMove) {
7076                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7077                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7078                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7079                            buf1, GE_XBOARD);
7080                 return;
7081            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7082            /* [HGM] Kludge to handle engines that send FRC-style castling
7083               when they shouldn't (like TSCP-Gothic) */
7084            switch(moveType) {
7085              case WhiteASideCastleFR:
7086              case BlackASideCastleFR:
7087                toX+=2;
7088                currentMoveString[2]++;
7089                break;
7090              case WhiteHSideCastleFR:
7091              case BlackHSideCastleFR:
7092                toX--;
7093                currentMoveString[2]--;
7094                break;
7095              default: ; // nothing to do, but suppresses warning of pedantic compilers
7096            }
7097         }
7098         hintRequested = FALSE;
7099         lastHint[0] = NULLCHAR;
7100         bookRequested = FALSE;
7101         /* Program may be pondering now */
7102         cps->maybeThinking = TRUE;
7103         if (cps->sendTime == 2) cps->sendTime = 1;
7104         if (cps->offeredDraw) cps->offeredDraw--;
7105
7106         /* currentMoveString is set as a side-effect of ParseOneMove */
7107         strcpy(machineMove, currentMoveString);
7108         strcat(machineMove, "\n");
7109         strcpy(moveList[forwardMostMove], machineMove);
7110
7111         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7112
7113         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7114         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7115             int count = 0;
7116
7117             while( count < adjudicateLossPlies ) {
7118                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7119
7120                 if( count & 1 ) {
7121                     score = -score; /* Flip score for winning side */
7122                 }
7123
7124                 if( score > adjudicateLossThreshold ) {
7125                     break;
7126                 }
7127
7128                 count++;
7129             }
7130
7131             if( count >= adjudicateLossPlies ) {
7132                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7133
7134                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7135                     "Xboard adjudication", 
7136                     GE_XBOARD );
7137
7138                 return;
7139             }
7140         }
7141
7142         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7143
7144 #if ZIPPY
7145         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7146             first.initDone) {
7147           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7148                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7149                 SendToICS("draw ");
7150                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7151           }
7152           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7153           ics_user_moved = 1;
7154           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7155                 char buf[3*MSG_SIZ];
7156
7157                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7158                         programStats.score / 100.,
7159                         programStats.depth,
7160                         programStats.time / 100.,
7161                         (unsigned int)programStats.nodes,
7162                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7163                         programStats.movelist);
7164                 SendToICS(buf);
7165 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7166           }
7167         }
7168 #endif
7169
7170         /* [AS] Save move info and clear stats for next move */
7171         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7172         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7173         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7174         ClearProgramStats();
7175         thinkOutput[0] = NULLCHAR;
7176         hiddenThinkOutputState = 0;
7177
7178         bookHit = NULL;
7179         if (gameMode == TwoMachinesPlay) {
7180             /* [HGM] relaying draw offers moved to after reception of move */
7181             /* and interpreting offer as claim if it brings draw condition */
7182             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7183                 SendToProgram("draw\n", cps->other);
7184             }
7185             if (cps->other->sendTime) {
7186                 SendTimeRemaining(cps->other,
7187                                   cps->other->twoMachinesColor[0] == 'w');
7188             }
7189             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7190             if (firstMove && !bookHit) {
7191                 firstMove = FALSE;
7192                 if (cps->other->useColors) {
7193                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7194                 }
7195                 SendToProgram("go\n", cps->other);
7196             }
7197             cps->other->maybeThinking = TRUE;
7198         }
7199
7200         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7201         
7202         if (!pausing && appData.ringBellAfterMoves) {
7203             RingBell();
7204         }
7205
7206         /* 
7207          * Reenable menu items that were disabled while
7208          * machine was thinking
7209          */
7210         if (gameMode != TwoMachinesPlay)
7211             SetUserThinkingEnables();
7212
7213         // [HGM] book: after book hit opponent has received move and is now in force mode
7214         // force the book reply into it, and then fake that it outputted this move by jumping
7215         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7216         if(bookHit) {
7217                 static char bookMove[MSG_SIZ]; // a bit generous?
7218
7219                 strcpy(bookMove, "move ");
7220                 strcat(bookMove, bookHit);
7221                 message = bookMove;
7222                 cps = cps->other;
7223                 programStats.nodes = programStats.depth = programStats.time = 
7224                 programStats.score = programStats.got_only_move = 0;
7225                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7226
7227                 if(cps->lastPing != cps->lastPong) {
7228                     savedMessage = message; // args for deferred call
7229                     savedState = cps;
7230                     ScheduleDelayedEvent(DeferredBookMove, 10);
7231                     return;
7232                 }
7233                 goto FakeBookMove;
7234         }
7235
7236         return;
7237     }
7238
7239     /* Set special modes for chess engines.  Later something general
7240      *  could be added here; for now there is just one kludge feature,
7241      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7242      *  when "xboard" is given as an interactive command.
7243      */
7244     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7245         cps->useSigint = FALSE;
7246         cps->useSigterm = FALSE;
7247     }
7248     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7249       ParseFeatures(message+8, cps);
7250       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7251     }
7252
7253     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7254      * want this, I was asked to put it in, and obliged.
7255      */
7256     if (!strncmp(message, "setboard ", 9)) {
7257         Board initial_position;
7258
7259         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7260
7261         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7262             DisplayError(_("Bad FEN received from engine"), 0);
7263             return ;
7264         } else {
7265            Reset(TRUE, FALSE);
7266            CopyBoard(boards[0], initial_position);
7267            initialRulePlies = FENrulePlies;
7268            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7269            else gameMode = MachinePlaysBlack;                 
7270            DrawPosition(FALSE, boards[currentMove]);
7271         }
7272         return;
7273     }
7274
7275     /*
7276      * Look for communication commands
7277      */
7278     if (!strncmp(message, "telluser ", 9)) {
7279         DisplayNote(message + 9);
7280         return;
7281     }
7282     if (!strncmp(message, "tellusererror ", 14)) {
7283         cps->userError = 1;
7284         DisplayError(message + 14, 0);
7285         return;
7286     }
7287     if (!strncmp(message, "tellopponent ", 13)) {
7288       if (appData.icsActive) {
7289         if (loggedOn) {
7290           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7291           SendToICS(buf1);
7292         }
7293       } else {
7294         DisplayNote(message + 13);
7295       }
7296       return;
7297     }
7298     if (!strncmp(message, "tellothers ", 11)) {
7299       if (appData.icsActive) {
7300         if (loggedOn) {
7301           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7302           SendToICS(buf1);
7303         }
7304       }
7305       return;
7306     }
7307     if (!strncmp(message, "tellall ", 8)) {
7308       if (appData.icsActive) {
7309         if (loggedOn) {
7310           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7311           SendToICS(buf1);
7312         }
7313       } else {
7314         DisplayNote(message + 8);
7315       }
7316       return;
7317     }
7318     if (strncmp(message, "warning", 7) == 0) {
7319         /* Undocumented feature, use tellusererror in new code */
7320         DisplayError(message, 0);
7321         return;
7322     }
7323     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7324         strcpy(realname, cps->tidy);
7325         strcat(realname, " query");
7326         AskQuestion(realname, buf2, buf1, cps->pr);
7327         return;
7328     }
7329     /* Commands from the engine directly to ICS.  We don't allow these to be 
7330      *  sent until we are logged on. Crafty kibitzes have been known to 
7331      *  interfere with the login process.
7332      */
7333     if (loggedOn) {
7334         if (!strncmp(message, "tellics ", 8)) {
7335             SendToICS(message + 8);
7336             SendToICS("\n");
7337             return;
7338         }
7339         if (!strncmp(message, "tellicsnoalias ", 15)) {
7340             SendToICS(ics_prefix);
7341             SendToICS(message + 15);
7342             SendToICS("\n");
7343             return;
7344         }
7345         /* The following are for backward compatibility only */
7346         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7347             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7348             SendToICS(ics_prefix);
7349             SendToICS(message);
7350             SendToICS("\n");
7351             return;
7352         }
7353     }
7354     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7355         return;
7356     }
7357     /*
7358      * If the move is illegal, cancel it and redraw the board.
7359      * Also deal with other error cases.  Matching is rather loose
7360      * here to accommodate engines written before the spec.
7361      */
7362     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7363         strncmp(message, "Error", 5) == 0) {
7364         if (StrStr(message, "name") || 
7365             StrStr(message, "rating") || StrStr(message, "?") ||
7366             StrStr(message, "result") || StrStr(message, "board") ||
7367             StrStr(message, "bk") || StrStr(message, "computer") ||
7368             StrStr(message, "variant") || StrStr(message, "hint") ||
7369             StrStr(message, "random") || StrStr(message, "depth") ||
7370             StrStr(message, "accepted")) {
7371             return;
7372         }
7373         if (StrStr(message, "protover")) {
7374           /* Program is responding to input, so it's apparently done
7375              initializing, and this error message indicates it is
7376              protocol version 1.  So we don't need to wait any longer
7377              for it to initialize and send feature commands. */
7378           FeatureDone(cps, 1);
7379           cps->protocolVersion = 1;
7380           return;
7381         }
7382         cps->maybeThinking = FALSE;
7383
7384         if (StrStr(message, "draw")) {
7385             /* Program doesn't have "draw" command */
7386             cps->sendDrawOffers = 0;
7387             return;
7388         }
7389         if (cps->sendTime != 1 &&
7390             (StrStr(message, "time") || StrStr(message, "otim"))) {
7391           /* Program apparently doesn't have "time" or "otim" command */
7392           cps->sendTime = 0;
7393           return;
7394         }
7395         if (StrStr(message, "analyze")) {
7396             cps->analysisSupport = FALSE;
7397             cps->analyzing = FALSE;
7398             Reset(FALSE, TRUE);
7399             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7400             DisplayError(buf2, 0);
7401             return;
7402         }
7403         if (StrStr(message, "(no matching move)st")) {
7404           /* Special kludge for GNU Chess 4 only */
7405           cps->stKludge = TRUE;
7406           SendTimeControl(cps, movesPerSession, timeControl,
7407                           timeIncrement, appData.searchDepth,
7408                           searchTime);
7409           return;
7410         }
7411         if (StrStr(message, "(no matching move)sd")) {
7412           /* Special kludge for GNU Chess 4 only */
7413           cps->sdKludge = TRUE;
7414           SendTimeControl(cps, movesPerSession, timeControl,
7415                           timeIncrement, appData.searchDepth,
7416                           searchTime);
7417           return;
7418         }
7419         if (!StrStr(message, "llegal")) {
7420             return;
7421         }
7422         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7423             gameMode == IcsIdle) return;
7424         if (forwardMostMove <= backwardMostMove) return;
7425         if (pausing) PauseEvent();
7426       if(appData.forceIllegal) {
7427             // [HGM] illegal: machine refused move; force position after move into it
7428           SendToProgram("force\n", cps);
7429           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7430                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7431                 // when black is to move, while there might be nothing on a2 or black
7432                 // might already have the move. So send the board as if white has the move.
7433                 // But first we must change the stm of the engine, as it refused the last move
7434                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7435                 if(WhiteOnMove(forwardMostMove)) {
7436                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7437                     SendBoard(cps, forwardMostMove); // kludgeless board
7438                 } else {
7439                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7440                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7441                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7442                 }
7443           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7444             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7445                  gameMode == TwoMachinesPlay)
7446               SendToProgram("go\n", cps);
7447             return;
7448       } else
7449         if (gameMode == PlayFromGameFile) {
7450             /* Stop reading this game file */
7451             gameMode = EditGame;
7452             ModeHighlight();
7453         }
7454         currentMove = forwardMostMove-1;
7455         DisplayMove(currentMove-1); /* before DisplayMoveError */
7456         SwitchClocks(forwardMostMove-1); // [HGM] race
7457         DisplayBothClocks();
7458         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7459                 parseList[currentMove], cps->which);
7460         DisplayMoveError(buf1);
7461         DrawPosition(FALSE, boards[currentMove]);
7462
7463         /* [HGM] illegal-move claim should forfeit game when Xboard */
7464         /* only passes fully legal moves                            */
7465         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7466             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7467                                 "False illegal-move claim", GE_XBOARD );
7468         }
7469         return;
7470     }
7471     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7472         /* Program has a broken "time" command that
7473            outputs a string not ending in newline.
7474            Don't use it. */
7475         cps->sendTime = 0;
7476     }
7477     
7478     /*
7479      * If chess program startup fails, exit with an error message.
7480      * Attempts to recover here are futile.
7481      */
7482     if ((StrStr(message, "unknown host") != NULL)
7483         || (StrStr(message, "No remote directory") != NULL)
7484         || (StrStr(message, "not found") != NULL)
7485         || (StrStr(message, "No such file") != NULL)
7486         || (StrStr(message, "can't alloc") != NULL)
7487         || (StrStr(message, "Permission denied") != NULL)) {
7488
7489         cps->maybeThinking = FALSE;
7490         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7491                 cps->which, cps->program, cps->host, message);
7492         RemoveInputSource(cps->isr);
7493         DisplayFatalError(buf1, 0, 1);
7494         return;
7495     }
7496     
7497     /* 
7498      * Look for hint output
7499      */
7500     if (sscanf(message, "Hint: %s", buf1) == 1) {
7501         if (cps == &first && hintRequested) {
7502             hintRequested = FALSE;
7503             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7504                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7505                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7506                                     PosFlags(forwardMostMove),
7507                                     fromY, fromX, toY, toX, promoChar, buf1);
7508                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7509                 DisplayInformation(buf2);
7510             } else {
7511                 /* Hint move could not be parsed!? */
7512               snprintf(buf2, sizeof(buf2),
7513                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7514                         buf1, cps->which);
7515                 DisplayError(buf2, 0);
7516             }
7517         } else {
7518             strcpy(lastHint, buf1);
7519         }
7520         return;
7521     }
7522
7523     /*
7524      * Ignore other messages if game is not in progress
7525      */
7526     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7527         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7528
7529     /*
7530      * look for win, lose, draw, or draw offer
7531      */
7532     if (strncmp(message, "1-0", 3) == 0) {
7533         char *p, *q, *r = "";
7534         p = strchr(message, '{');
7535         if (p) {
7536             q = strchr(p, '}');
7537             if (q) {
7538                 *q = NULLCHAR;
7539                 r = p + 1;
7540             }
7541         }
7542         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7543         return;
7544     } else if (strncmp(message, "0-1", 3) == 0) {
7545         char *p, *q, *r = "";
7546         p = strchr(message, '{');
7547         if (p) {
7548             q = strchr(p, '}');
7549             if (q) {
7550                 *q = NULLCHAR;
7551                 r = p + 1;
7552             }
7553         }
7554         /* Kludge for Arasan 4.1 bug */
7555         if (strcmp(r, "Black resigns") == 0) {
7556             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7557             return;
7558         }
7559         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7560         return;
7561     } else if (strncmp(message, "1/2", 3) == 0) {
7562         char *p, *q, *r = "";
7563         p = strchr(message, '{');
7564         if (p) {
7565             q = strchr(p, '}');
7566             if (q) {
7567                 *q = NULLCHAR;
7568                 r = p + 1;
7569             }
7570         }
7571             
7572         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7573         return;
7574
7575     } else if (strncmp(message, "White resign", 12) == 0) {
7576         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7577         return;
7578     } else if (strncmp(message, "Black resign", 12) == 0) {
7579         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7580         return;
7581     } else if (strncmp(message, "White matches", 13) == 0 ||
7582                strncmp(message, "Black matches", 13) == 0   ) {
7583         /* [HGM] ignore GNUShogi noises */
7584         return;
7585     } else if (strncmp(message, "White", 5) == 0 &&
7586                message[5] != '(' &&
7587                StrStr(message, "Black") == NULL) {
7588         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7589         return;
7590     } else if (strncmp(message, "Black", 5) == 0 &&
7591                message[5] != '(') {
7592         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7593         return;
7594     } else if (strcmp(message, "resign") == 0 ||
7595                strcmp(message, "computer resigns") == 0) {
7596         switch (gameMode) {
7597           case MachinePlaysBlack:
7598           case IcsPlayingBlack:
7599             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7600             break;
7601           case MachinePlaysWhite:
7602           case IcsPlayingWhite:
7603             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7604             break;
7605           case TwoMachinesPlay:
7606             if (cps->twoMachinesColor[0] == 'w')
7607               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7608             else
7609               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7610             break;
7611           default:
7612             /* can't happen */
7613             break;
7614         }
7615         return;
7616     } else if (strncmp(message, "opponent mates", 14) == 0) {
7617         switch (gameMode) {
7618           case MachinePlaysBlack:
7619           case IcsPlayingBlack:
7620             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7621             break;
7622           case MachinePlaysWhite:
7623           case IcsPlayingWhite:
7624             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7625             break;
7626           case TwoMachinesPlay:
7627             if (cps->twoMachinesColor[0] == 'w')
7628               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7629             else
7630               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7631             break;
7632           default:
7633             /* can't happen */
7634             break;
7635         }
7636         return;
7637     } else if (strncmp(message, "computer mates", 14) == 0) {
7638         switch (gameMode) {
7639           case MachinePlaysBlack:
7640           case IcsPlayingBlack:
7641             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7642             break;
7643           case MachinePlaysWhite:
7644           case IcsPlayingWhite:
7645             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7646             break;
7647           case TwoMachinesPlay:
7648             if (cps->twoMachinesColor[0] == 'w')
7649               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7650             else
7651               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7652             break;
7653           default:
7654             /* can't happen */
7655             break;
7656         }
7657         return;
7658     } else if (strncmp(message, "checkmate", 9) == 0) {
7659         if (WhiteOnMove(forwardMostMove)) {
7660             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7661         } else {
7662             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7663         }
7664         return;
7665     } else if (strstr(message, "Draw") != NULL ||
7666                strstr(message, "game is a draw") != NULL) {
7667         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7668         return;
7669     } else if (strstr(message, "offer") != NULL &&
7670                strstr(message, "draw") != NULL) {
7671 #if ZIPPY
7672         if (appData.zippyPlay && first.initDone) {
7673             /* Relay offer to ICS */
7674             SendToICS(ics_prefix);
7675             SendToICS("draw\n");
7676         }
7677 #endif
7678         cps->offeredDraw = 2; /* valid until this engine moves twice */
7679         if (gameMode == TwoMachinesPlay) {
7680             if (cps->other->offeredDraw) {
7681                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7682             /* [HGM] in two-machine mode we delay relaying draw offer      */
7683             /* until after we also have move, to see if it is really claim */
7684             }
7685         } else if (gameMode == MachinePlaysWhite ||
7686                    gameMode == MachinePlaysBlack) {
7687           if (userOfferedDraw) {
7688             DisplayInformation(_("Machine accepts your draw offer"));
7689             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7690           } else {
7691             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7692           }
7693         }
7694     }
7695
7696     
7697     /*
7698      * Look for thinking output
7699      */
7700     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7701           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7702                                 ) {
7703         int plylev, mvleft, mvtot, curscore, time;
7704         char mvname[MOVE_LEN];
7705         u64 nodes; // [DM]
7706         char plyext;
7707         int ignore = FALSE;
7708         int prefixHint = FALSE;
7709         mvname[0] = NULLCHAR;
7710
7711         switch (gameMode) {
7712           case MachinePlaysBlack:
7713           case IcsPlayingBlack:
7714             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7715             break;
7716           case MachinePlaysWhite:
7717           case IcsPlayingWhite:
7718             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7719             break;
7720           case AnalyzeMode:
7721           case AnalyzeFile:
7722             break;
7723           case IcsObserving: /* [DM] icsEngineAnalyze */
7724             if (!appData.icsEngineAnalyze) ignore = TRUE;
7725             break;
7726           case TwoMachinesPlay:
7727             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7728                 ignore = TRUE;
7729             }
7730             break;
7731           default:
7732             ignore = TRUE;
7733             break;
7734         }
7735
7736         if (!ignore) {
7737             buf1[0] = NULLCHAR;
7738             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7739                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7740
7741                 if (plyext != ' ' && plyext != '\t') {
7742                     time *= 100;
7743                 }
7744
7745                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7746                 if( cps->scoreIsAbsolute && 
7747                     ( gameMode == MachinePlaysBlack ||
7748                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7749                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7750                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7751                      !WhiteOnMove(currentMove)
7752                     ) )
7753                 {
7754                     curscore = -curscore;
7755                 }
7756
7757
7758                 programStats.depth = plylev;
7759                 programStats.nodes = nodes;
7760                 programStats.time = time;
7761                 programStats.score = curscore;
7762                 programStats.got_only_move = 0;
7763
7764                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7765                         int ticklen;
7766
7767                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7768                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7769                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7770                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7771                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7772                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7773                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7774                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7775                 }
7776
7777                 /* Buffer overflow protection */
7778                 if (buf1[0] != NULLCHAR) {
7779                     if (strlen(buf1) >= sizeof(programStats.movelist)
7780                         && appData.debugMode) {
7781                         fprintf(debugFP,
7782                                 "PV is too long; using the first %u bytes.\n",
7783                                 (unsigned) sizeof(programStats.movelist) - 1);
7784                     }
7785
7786                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7787                 } else {
7788                     sprintf(programStats.movelist, " no PV\n");
7789                 }
7790
7791                 if (programStats.seen_stat) {
7792                     programStats.ok_to_send = 1;
7793                 }
7794
7795                 if (strchr(programStats.movelist, '(') != NULL) {
7796                     programStats.line_is_book = 1;
7797                     programStats.nr_moves = 0;
7798                     programStats.moves_left = 0;
7799                 } else {
7800                     programStats.line_is_book = 0;
7801                 }
7802
7803                 SendProgramStatsToFrontend( cps, &programStats );
7804
7805                 /* 
7806                     [AS] Protect the thinkOutput buffer from overflow... this
7807                     is only useful if buf1 hasn't overflowed first!
7808                 */
7809                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7810                         plylev, 
7811                         (gameMode == TwoMachinesPlay ?
7812                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7813                         ((double) curscore) / 100.0,
7814                         prefixHint ? lastHint : "",
7815                         prefixHint ? " " : "" );
7816
7817                 if( buf1[0] != NULLCHAR ) {
7818                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7819
7820                     if( strlen(buf1) > max_len ) {
7821                         if( appData.debugMode) {
7822                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7823                         }
7824                         buf1[max_len+1] = '\0';
7825                     }
7826
7827                     strcat( thinkOutput, buf1 );
7828                 }
7829
7830                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7831                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7832                     DisplayMove(currentMove - 1);
7833                 }
7834                 return;
7835
7836             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7837                 /* crafty (9.25+) says "(only move) <move>"
7838                  * if there is only 1 legal move
7839                  */
7840                 sscanf(p, "(only move) %s", buf1);
7841                 sprintf(thinkOutput, "%s (only move)", buf1);
7842                 sprintf(programStats.movelist, "%s (only move)", buf1);
7843                 programStats.depth = 1;
7844                 programStats.nr_moves = 1;
7845                 programStats.moves_left = 1;
7846                 programStats.nodes = 1;
7847                 programStats.time = 1;
7848                 programStats.got_only_move = 1;
7849
7850                 /* Not really, but we also use this member to
7851                    mean "line isn't going to change" (Crafty
7852                    isn't searching, so stats won't change) */
7853                 programStats.line_is_book = 1;
7854
7855                 SendProgramStatsToFrontend( cps, &programStats );
7856                 
7857                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7858                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7859                     DisplayMove(currentMove - 1);
7860                 }
7861                 return;
7862             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7863                               &time, &nodes, &plylev, &mvleft,
7864                               &mvtot, mvname) >= 5) {
7865                 /* The stat01: line is from Crafty (9.29+) in response
7866                    to the "." command */
7867                 programStats.seen_stat = 1;
7868                 cps->maybeThinking = TRUE;
7869
7870                 if (programStats.got_only_move || !appData.periodicUpdates)
7871                   return;
7872
7873                 programStats.depth = plylev;
7874                 programStats.time = time;
7875                 programStats.nodes = nodes;
7876                 programStats.moves_left = mvleft;
7877                 programStats.nr_moves = mvtot;
7878                 strcpy(programStats.move_name, mvname);
7879                 programStats.ok_to_send = 1;
7880                 programStats.movelist[0] = '\0';
7881
7882                 SendProgramStatsToFrontend( cps, &programStats );
7883
7884                 return;
7885
7886             } else if (strncmp(message,"++",2) == 0) {
7887                 /* Crafty 9.29+ outputs this */
7888                 programStats.got_fail = 2;
7889                 return;
7890
7891             } else if (strncmp(message,"--",2) == 0) {
7892                 /* Crafty 9.29+ outputs this */
7893                 programStats.got_fail = 1;
7894                 return;
7895
7896             } else if (thinkOutput[0] != NULLCHAR &&
7897                        strncmp(message, "    ", 4) == 0) {
7898                 unsigned message_len;
7899
7900                 p = message;
7901                 while (*p && *p == ' ') p++;
7902
7903                 message_len = strlen( p );
7904
7905                 /* [AS] Avoid buffer overflow */
7906                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7907                     strcat(thinkOutput, " ");
7908                     strcat(thinkOutput, p);
7909                 }
7910
7911                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7912                     strcat(programStats.movelist, " ");
7913                     strcat(programStats.movelist, p);
7914                 }
7915
7916                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7917                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7918                     DisplayMove(currentMove - 1);
7919                 }
7920                 return;
7921             }
7922         }
7923         else {
7924             buf1[0] = NULLCHAR;
7925
7926             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7927                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7928             {
7929                 ChessProgramStats cpstats;
7930
7931                 if (plyext != ' ' && plyext != '\t') {
7932                     time *= 100;
7933                 }
7934
7935                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7936                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7937                     curscore = -curscore;
7938                 }
7939
7940                 cpstats.depth = plylev;
7941                 cpstats.nodes = nodes;
7942                 cpstats.time = time;
7943                 cpstats.score = curscore;
7944                 cpstats.got_only_move = 0;
7945                 cpstats.movelist[0] = '\0';
7946
7947                 if (buf1[0] != NULLCHAR) {
7948                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7949                 }
7950
7951                 cpstats.ok_to_send = 0;
7952                 cpstats.line_is_book = 0;
7953                 cpstats.nr_moves = 0;
7954                 cpstats.moves_left = 0;
7955
7956                 SendProgramStatsToFrontend( cps, &cpstats );
7957             }
7958         }
7959     }
7960 }
7961
7962
7963 /* Parse a game score from the character string "game", and
7964    record it as the history of the current game.  The game
7965    score is NOT assumed to start from the standard position. 
7966    The display is not updated in any way.
7967    */
7968 void
7969 ParseGameHistory(game)
7970      char *game;
7971 {
7972     ChessMove moveType;
7973     int fromX, fromY, toX, toY, boardIndex;
7974     char promoChar;
7975     char *p, *q;
7976     char buf[MSG_SIZ];
7977
7978     if (appData.debugMode)
7979       fprintf(debugFP, "Parsing game history: %s\n", game);
7980
7981     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7982     gameInfo.site = StrSave(appData.icsHost);
7983     gameInfo.date = PGNDate();
7984     gameInfo.round = StrSave("-");
7985
7986     /* Parse out names of players */
7987     while (*game == ' ') game++;
7988     p = buf;
7989     while (*game != ' ') *p++ = *game++;
7990     *p = NULLCHAR;
7991     gameInfo.white = StrSave(buf);
7992     while (*game == ' ') game++;
7993     p = buf;
7994     while (*game != ' ' && *game != '\n') *p++ = *game++;
7995     *p = NULLCHAR;
7996     gameInfo.black = StrSave(buf);
7997
7998     /* Parse moves */
7999     boardIndex = blackPlaysFirst ? 1 : 0;
8000     yynewstr(game);
8001     for (;;) {
8002         yyboardindex = boardIndex;
8003         moveType = (ChessMove) yylex();
8004         switch (moveType) {
8005           case IllegalMove:             /* maybe suicide chess, etc. */
8006   if (appData.debugMode) {
8007     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8008     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8009     setbuf(debugFP, NULL);
8010   }
8011           case WhitePromotionChancellor:
8012           case BlackPromotionChancellor:
8013           case WhitePromotionArchbishop:
8014           case BlackPromotionArchbishop:
8015           case WhitePromotionQueen:
8016           case BlackPromotionQueen:
8017           case WhitePromotionRook:
8018           case BlackPromotionRook:
8019           case WhitePromotionBishop:
8020           case BlackPromotionBishop:
8021           case WhitePromotionKnight:
8022           case BlackPromotionKnight:
8023           case WhitePromotionKing:
8024           case BlackPromotionKing:
8025           case NormalMove:
8026           case WhiteCapturesEnPassant:
8027           case BlackCapturesEnPassant:
8028           case WhiteKingSideCastle:
8029           case WhiteQueenSideCastle:
8030           case BlackKingSideCastle:
8031           case BlackQueenSideCastle:
8032           case WhiteKingSideCastleWild:
8033           case WhiteQueenSideCastleWild:
8034           case BlackKingSideCastleWild:
8035           case BlackQueenSideCastleWild:
8036           /* PUSH Fabien */
8037           case WhiteHSideCastleFR:
8038           case WhiteASideCastleFR:
8039           case BlackHSideCastleFR:
8040           case BlackASideCastleFR:
8041           /* POP Fabien */
8042             fromX = currentMoveString[0] - AAA;
8043             fromY = currentMoveString[1] - ONE;
8044             toX = currentMoveString[2] - AAA;
8045             toY = currentMoveString[3] - ONE;
8046             promoChar = currentMoveString[4];
8047             break;
8048           case WhiteDrop:
8049           case BlackDrop:
8050             fromX = moveType == WhiteDrop ?
8051               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8052             (int) CharToPiece(ToLower(currentMoveString[0]));
8053             fromY = DROP_RANK;
8054             toX = currentMoveString[2] - AAA;
8055             toY = currentMoveString[3] - ONE;
8056             promoChar = NULLCHAR;
8057             break;
8058           case AmbiguousMove:
8059             /* bug? */
8060             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8061   if (appData.debugMode) {
8062     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8063     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8064     setbuf(debugFP, NULL);
8065   }
8066             DisplayError(buf, 0);
8067             return;
8068           case ImpossibleMove:
8069             /* bug? */
8070             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8071   if (appData.debugMode) {
8072     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8073     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8074     setbuf(debugFP, NULL);
8075   }
8076             DisplayError(buf, 0);
8077             return;
8078           case (ChessMove) 0:   /* end of file */
8079             if (boardIndex < backwardMostMove) {
8080                 /* Oops, gap.  How did that happen? */
8081                 DisplayError(_("Gap in move list"), 0);
8082                 return;
8083             }
8084             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8085             if (boardIndex > forwardMostMove) {
8086                 forwardMostMove = boardIndex;
8087             }
8088             return;
8089           case ElapsedTime:
8090             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8091                 strcat(parseList[boardIndex-1], " ");
8092                 strcat(parseList[boardIndex-1], yy_text);
8093             }
8094             continue;
8095           case Comment:
8096           case PGNTag:
8097           case NAG:
8098           default:
8099             /* ignore */
8100             continue;
8101           case WhiteWins:
8102           case BlackWins:
8103           case GameIsDrawn:
8104           case GameUnfinished:
8105             if (gameMode == IcsExamining) {
8106                 if (boardIndex < backwardMostMove) {
8107                     /* Oops, gap.  How did that happen? */
8108                     return;
8109                 }
8110                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8111                 return;
8112             }
8113             gameInfo.result = moveType;
8114             p = strchr(yy_text, '{');
8115             if (p == NULL) p = strchr(yy_text, '(');
8116             if (p == NULL) {
8117                 p = yy_text;
8118                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8119             } else {
8120                 q = strchr(p, *p == '{' ? '}' : ')');
8121                 if (q != NULL) *q = NULLCHAR;
8122                 p++;
8123             }
8124             gameInfo.resultDetails = StrSave(p);
8125             continue;
8126         }
8127         if (boardIndex >= forwardMostMove &&
8128             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8129             backwardMostMove = blackPlaysFirst ? 1 : 0;
8130             return;
8131         }
8132         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8133                                  fromY, fromX, toY, toX, promoChar,
8134                                  parseList[boardIndex]);
8135         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8136         /* currentMoveString is set as a side-effect of yylex */
8137         strcpy(moveList[boardIndex], currentMoveString);
8138         strcat(moveList[boardIndex], "\n");
8139         boardIndex++;
8140         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8141         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8142           case MT_NONE:
8143           case MT_STALEMATE:
8144           default:
8145             break;
8146           case MT_CHECK:
8147             if(gameInfo.variant != VariantShogi)
8148                 strcat(parseList[boardIndex - 1], "+");
8149             break;
8150           case MT_CHECKMATE:
8151           case MT_STAINMATE:
8152             strcat(parseList[boardIndex - 1], "#");
8153             break;
8154         }
8155     }
8156 }
8157
8158
8159 /* Apply a move to the given board  */
8160 void
8161 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8162      int fromX, fromY, toX, toY;
8163      int promoChar;
8164      Board board;
8165 {
8166   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8167   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8168
8169     /* [HGM] compute & store e.p. status and castling rights for new position */
8170     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8171     { int i;
8172
8173       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8174       oldEP = (signed char)board[EP_STATUS];
8175       board[EP_STATUS] = EP_NONE;
8176
8177       if( board[toY][toX] != EmptySquare ) 
8178            board[EP_STATUS] = EP_CAPTURE;  
8179
8180       if( board[fromY][fromX] == WhitePawn ) {
8181            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8182                board[EP_STATUS] = EP_PAWN_MOVE;
8183            if( toY-fromY==2) {
8184                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8185                         gameInfo.variant != VariantBerolina || toX < fromX)
8186                       board[EP_STATUS] = toX | berolina;
8187                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8188                         gameInfo.variant != VariantBerolina || toX > fromX) 
8189                       board[EP_STATUS] = toX;
8190            }
8191       } else 
8192       if( board[fromY][fromX] == BlackPawn ) {
8193            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8194                board[EP_STATUS] = EP_PAWN_MOVE; 
8195            if( toY-fromY== -2) {
8196                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8197                         gameInfo.variant != VariantBerolina || toX < fromX)
8198                       board[EP_STATUS] = toX | berolina;
8199                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8200                         gameInfo.variant != VariantBerolina || toX > fromX) 
8201                       board[EP_STATUS] = toX;
8202            }
8203        }
8204
8205        for(i=0; i<nrCastlingRights; i++) {
8206            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8207               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8208              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8209        }
8210
8211     }
8212
8213   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8214   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8215        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8216          
8217   if (fromX == toX && fromY == toY) return;
8218
8219   if (fromY == DROP_RANK) {
8220         /* must be first */
8221         piece = board[toY][toX] = (ChessSquare) fromX;
8222   } else {
8223      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8224      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8225      if(gameInfo.variant == VariantKnightmate)
8226          king += (int) WhiteUnicorn - (int) WhiteKing;
8227
8228     /* Code added by Tord: */
8229     /* FRC castling assumed when king captures friendly rook. */
8230     if (board[fromY][fromX] == WhiteKing &&
8231              board[toY][toX] == WhiteRook) {
8232       board[fromY][fromX] = EmptySquare;
8233       board[toY][toX] = EmptySquare;
8234       if(toX > fromX) {
8235         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8236       } else {
8237         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8238       }
8239     } else if (board[fromY][fromX] == BlackKing &&
8240                board[toY][toX] == BlackRook) {
8241       board[fromY][fromX] = EmptySquare;
8242       board[toY][toX] = EmptySquare;
8243       if(toX > fromX) {
8244         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8245       } else {
8246         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8247       }
8248     /* End of code added by Tord */
8249
8250     } else if (board[fromY][fromX] == king
8251         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8252         && toY == fromY && toX > fromX+1) {
8253         board[fromY][fromX] = EmptySquare;
8254         board[toY][toX] = king;
8255         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8256         board[fromY][BOARD_RGHT-1] = EmptySquare;
8257     } else if (board[fromY][fromX] == king
8258         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8259                && toY == fromY && toX < fromX-1) {
8260         board[fromY][fromX] = EmptySquare;
8261         board[toY][toX] = king;
8262         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8263         board[fromY][BOARD_LEFT] = EmptySquare;
8264     } else if (board[fromY][fromX] == WhitePawn
8265                && toY >= BOARD_HEIGHT-promoRank
8266                && gameInfo.variant != VariantXiangqi
8267                ) {
8268         /* white pawn promotion */
8269         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8270         if (board[toY][toX] == EmptySquare) {
8271             board[toY][toX] = WhiteQueen;
8272         }
8273         if(gameInfo.variant==VariantBughouse ||
8274            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8275             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8276         board[fromY][fromX] = EmptySquare;
8277     } else if ((fromY == BOARD_HEIGHT-4)
8278                && (toX != fromX)
8279                && gameInfo.variant != VariantXiangqi
8280                && gameInfo.variant != VariantBerolina
8281                && (board[fromY][fromX] == WhitePawn)
8282                && (board[toY][toX] == EmptySquare)) {
8283         board[fromY][fromX] = EmptySquare;
8284         board[toY][toX] = WhitePawn;
8285         captured = board[toY - 1][toX];
8286         board[toY - 1][toX] = EmptySquare;
8287     } else if ((fromY == BOARD_HEIGHT-4)
8288                && (toX == fromX)
8289                && gameInfo.variant == VariantBerolina
8290                && (board[fromY][fromX] == WhitePawn)
8291                && (board[toY][toX] == EmptySquare)) {
8292         board[fromY][fromX] = EmptySquare;
8293         board[toY][toX] = WhitePawn;
8294         if(oldEP & EP_BEROLIN_A) {
8295                 captured = board[fromY][fromX-1];
8296                 board[fromY][fromX-1] = EmptySquare;
8297         }else{  captured = board[fromY][fromX+1];
8298                 board[fromY][fromX+1] = EmptySquare;
8299         }
8300     } else if (board[fromY][fromX] == king
8301         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8302                && toY == fromY && toX > fromX+1) {
8303         board[fromY][fromX] = EmptySquare;
8304         board[toY][toX] = king;
8305         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8306         board[fromY][BOARD_RGHT-1] = EmptySquare;
8307     } else if (board[fromY][fromX] == king
8308         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8309                && toY == fromY && toX < fromX-1) {
8310         board[fromY][fromX] = EmptySquare;
8311         board[toY][toX] = king;
8312         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8313         board[fromY][BOARD_LEFT] = EmptySquare;
8314     } else if (fromY == 7 && fromX == 3
8315                && board[fromY][fromX] == BlackKing
8316                && toY == 7 && toX == 5) {
8317         board[fromY][fromX] = EmptySquare;
8318         board[toY][toX] = BlackKing;
8319         board[fromY][7] = EmptySquare;
8320         board[toY][4] = BlackRook;
8321     } else if (fromY == 7 && fromX == 3
8322                && board[fromY][fromX] == BlackKing
8323                && toY == 7 && toX == 1) {
8324         board[fromY][fromX] = EmptySquare;
8325         board[toY][toX] = BlackKing;
8326         board[fromY][0] = EmptySquare;
8327         board[toY][2] = BlackRook;
8328     } else if (board[fromY][fromX] == BlackPawn
8329                && toY < promoRank
8330                && gameInfo.variant != VariantXiangqi
8331                ) {
8332         /* black pawn promotion */
8333         board[toY][toX] = CharToPiece(ToLower(promoChar));
8334         if (board[toY][toX] == EmptySquare) {
8335             board[toY][toX] = BlackQueen;
8336         }
8337         if(gameInfo.variant==VariantBughouse ||
8338            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8339             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8340         board[fromY][fromX] = EmptySquare;
8341     } else if ((fromY == 3)
8342                && (toX != fromX)
8343                && gameInfo.variant != VariantXiangqi
8344                && gameInfo.variant != VariantBerolina
8345                && (board[fromY][fromX] == BlackPawn)
8346                && (board[toY][toX] == EmptySquare)) {
8347         board[fromY][fromX] = EmptySquare;
8348         board[toY][toX] = BlackPawn;
8349         captured = board[toY + 1][toX];
8350         board[toY + 1][toX] = EmptySquare;
8351     } else if ((fromY == 3)
8352                && (toX == fromX)
8353                && gameInfo.variant == VariantBerolina
8354                && (board[fromY][fromX] == BlackPawn)
8355                && (board[toY][toX] == EmptySquare)) {
8356         board[fromY][fromX] = EmptySquare;
8357         board[toY][toX] = BlackPawn;
8358         if(oldEP & EP_BEROLIN_A) {
8359                 captured = board[fromY][fromX-1];
8360                 board[fromY][fromX-1] = EmptySquare;
8361         }else{  captured = board[fromY][fromX+1];
8362                 board[fromY][fromX+1] = EmptySquare;
8363         }
8364     } else {
8365         board[toY][toX] = board[fromY][fromX];
8366         board[fromY][fromX] = EmptySquare;
8367     }
8368
8369     /* [HGM] now we promote for Shogi, if needed */
8370     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8371         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8372   }
8373
8374     if (gameInfo.holdingsWidth != 0) {
8375
8376       /* !!A lot more code needs to be written to support holdings  */
8377       /* [HGM] OK, so I have written it. Holdings are stored in the */
8378       /* penultimate board files, so they are automaticlly stored   */
8379       /* in the game history.                                       */
8380       if (fromY == DROP_RANK) {
8381         /* Delete from holdings, by decreasing count */
8382         /* and erasing image if necessary            */
8383         p = (int) fromX;
8384         if(p < (int) BlackPawn) { /* white drop */
8385              p -= (int)WhitePawn;
8386                  p = PieceToNumber((ChessSquare)p);
8387              if(p >= gameInfo.holdingsSize) p = 0;
8388              if(--board[p][BOARD_WIDTH-2] <= 0)
8389                   board[p][BOARD_WIDTH-1] = EmptySquare;
8390              if((int)board[p][BOARD_WIDTH-2] < 0)
8391                         board[p][BOARD_WIDTH-2] = 0;
8392         } else {                  /* black drop */
8393              p -= (int)BlackPawn;
8394                  p = PieceToNumber((ChessSquare)p);
8395              if(p >= gameInfo.holdingsSize) p = 0;
8396              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8397                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8398              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8399                         board[BOARD_HEIGHT-1-p][1] = 0;
8400         }
8401       }
8402       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8403           && gameInfo.variant != VariantBughouse        ) {
8404         /* [HGM] holdings: Add to holdings, if holdings exist */
8405         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8406                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8407                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8408         }
8409         p = (int) captured;
8410         if (p >= (int) BlackPawn) {
8411           p -= (int)BlackPawn;
8412           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8413                   /* in Shogi restore piece to its original  first */
8414                   captured = (ChessSquare) (DEMOTED captured);
8415                   p = DEMOTED p;
8416           }
8417           p = PieceToNumber((ChessSquare)p);
8418           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8419           board[p][BOARD_WIDTH-2]++;
8420           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8421         } else {
8422           p -= (int)WhitePawn;
8423           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8424                   captured = (ChessSquare) (DEMOTED captured);
8425                   p = DEMOTED p;
8426           }
8427           p = PieceToNumber((ChessSquare)p);
8428           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8429           board[BOARD_HEIGHT-1-p][1]++;
8430           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8431         }
8432       }
8433     } else if (gameInfo.variant == VariantAtomic) {
8434       if (captured != EmptySquare) {
8435         int y, x;
8436         for (y = toY-1; y <= toY+1; y++) {
8437           for (x = toX-1; x <= toX+1; x++) {
8438             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8439                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8440               board[y][x] = EmptySquare;
8441             }
8442           }
8443         }
8444         board[toY][toX] = EmptySquare;
8445       }
8446     }
8447     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8448         /* [HGM] Shogi promotions */
8449         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8450     }
8451
8452     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8453                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8454         // [HGM] superchess: take promotion piece out of holdings
8455         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8456         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8457             if(!--board[k][BOARD_WIDTH-2])
8458                 board[k][BOARD_WIDTH-1] = EmptySquare;
8459         } else {
8460             if(!--board[BOARD_HEIGHT-1-k][1])
8461                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8462         }
8463     }
8464
8465 }
8466
8467 /* Updates forwardMostMove */
8468 void
8469 MakeMove(fromX, fromY, toX, toY, promoChar)
8470      int fromX, fromY, toX, toY;
8471      int promoChar;
8472 {
8473 //    forwardMostMove++; // [HGM] bare: moved downstream
8474
8475     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8476         int timeLeft; static int lastLoadFlag=0; int king, piece;
8477         piece = boards[forwardMostMove][fromY][fromX];
8478         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8479         if(gameInfo.variant == VariantKnightmate)
8480             king += (int) WhiteUnicorn - (int) WhiteKing;
8481         if(forwardMostMove == 0) {
8482             if(blackPlaysFirst) 
8483                 fprintf(serverMoves, "%s;", second.tidy);
8484             fprintf(serverMoves, "%s;", first.tidy);
8485             if(!blackPlaysFirst) 
8486                 fprintf(serverMoves, "%s;", second.tidy);
8487         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8488         lastLoadFlag = loadFlag;
8489         // print base move
8490         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8491         // print castling suffix
8492         if( toY == fromY && piece == king ) {
8493             if(toX-fromX > 1)
8494                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8495             if(fromX-toX >1)
8496                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8497         }
8498         // e.p. suffix
8499         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8500              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8501              boards[forwardMostMove][toY][toX] == EmptySquare
8502              && fromX != toX )
8503                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8504         // promotion suffix
8505         if(promoChar != NULLCHAR)
8506                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8507         if(!loadFlag) {
8508             fprintf(serverMoves, "/%d/%d",
8509                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8510             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8511             else                      timeLeft = blackTimeRemaining/1000;
8512             fprintf(serverMoves, "/%d", timeLeft);
8513         }
8514         fflush(serverMoves);
8515     }
8516
8517     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8518       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8519                         0, 1);
8520       return;
8521     }
8522     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8523     if (commentList[forwardMostMove+1] != NULL) {
8524         free(commentList[forwardMostMove+1]);
8525         commentList[forwardMostMove+1] = NULL;
8526     }
8527     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8528     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8529     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8530     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8531     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8532     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8533     gameInfo.result = GameUnfinished;
8534     if (gameInfo.resultDetails != NULL) {
8535         free(gameInfo.resultDetails);
8536         gameInfo.resultDetails = NULL;
8537     }
8538     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8539                               moveList[forwardMostMove - 1]);
8540     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8541                              PosFlags(forwardMostMove - 1),
8542                              fromY, fromX, toY, toX, promoChar,
8543                              parseList[forwardMostMove - 1]);
8544     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8545       case MT_NONE:
8546       case MT_STALEMATE:
8547       default:
8548         break;
8549       case MT_CHECK:
8550         if(gameInfo.variant != VariantShogi)
8551             strcat(parseList[forwardMostMove - 1], "+");
8552         break;
8553       case MT_CHECKMATE:
8554       case MT_STAINMATE:
8555         strcat(parseList[forwardMostMove - 1], "#");
8556         break;
8557     }
8558     if (appData.debugMode) {
8559         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8560     }
8561
8562 }
8563
8564 /* Updates currentMove if not pausing */
8565 void
8566 ShowMove(fromX, fromY, toX, toY)
8567 {
8568     int instant = (gameMode == PlayFromGameFile) ?
8569         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8570     if(appData.noGUI) return;
8571     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8572         if (!instant) {
8573             if (forwardMostMove == currentMove + 1) {
8574                 AnimateMove(boards[forwardMostMove - 1],
8575                             fromX, fromY, toX, toY);
8576             }
8577             if (appData.highlightLastMove) {
8578                 SetHighlights(fromX, fromY, toX, toY);
8579             }
8580         }
8581         currentMove = forwardMostMove;
8582     }
8583
8584     if (instant) return;
8585
8586     DisplayMove(currentMove - 1);
8587     DrawPosition(FALSE, boards[currentMove]);
8588     DisplayBothClocks();
8589     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8590 }
8591
8592 void SendEgtPath(ChessProgramState *cps)
8593 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8594         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8595
8596         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8597
8598         while(*p) {
8599             char c, *q = name+1, *r, *s;
8600
8601             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8602             while(*p && *p != ',') *q++ = *p++;
8603             *q++ = ':'; *q = 0;
8604             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8605                 strcmp(name, ",nalimov:") == 0 ) {
8606                 // take nalimov path from the menu-changeable option first, if it is defined
8607                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8608                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8609             } else
8610             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8611                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8612                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8613                 s = r = StrStr(s, ":") + 1; // beginning of path info
8614                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8615                 c = *r; *r = 0;             // temporarily null-terminate path info
8616                     *--q = 0;               // strip of trailig ':' from name
8617                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8618                 *r = c;
8619                 SendToProgram(buf,cps);     // send egtbpath command for this format
8620             }
8621             if(*p == ',') p++; // read away comma to position for next format name
8622         }
8623 }
8624
8625 void
8626 InitChessProgram(cps, setup)
8627      ChessProgramState *cps;
8628      int setup; /* [HGM] needed to setup FRC opening position */
8629 {
8630     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8631     if (appData.noChessProgram) return;
8632     hintRequested = FALSE;
8633     bookRequested = FALSE;
8634
8635     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8636     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8637     if(cps->memSize) { /* [HGM] memory */
8638         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8639         SendToProgram(buf, cps);
8640     }
8641     SendEgtPath(cps); /* [HGM] EGT */
8642     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8643         sprintf(buf, "cores %d\n", appData.smpCores);
8644         SendToProgram(buf, cps);
8645     }
8646
8647     SendToProgram(cps->initString, cps);
8648     if (gameInfo.variant != VariantNormal &&
8649         gameInfo.variant != VariantLoadable
8650         /* [HGM] also send variant if board size non-standard */
8651         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8652                                             ) {
8653       char *v = VariantName(gameInfo.variant);
8654       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8655         /* [HGM] in protocol 1 we have to assume all variants valid */
8656         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8657         DisplayFatalError(buf, 0, 1);
8658         return;
8659       }
8660
8661       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8662       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8663       if( gameInfo.variant == VariantXiangqi )
8664            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8665       if( gameInfo.variant == VariantShogi )
8666            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8667       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8668            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8669       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8670                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8671            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8672       if( gameInfo.variant == VariantCourier )
8673            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8674       if( gameInfo.variant == VariantSuper )
8675            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8676       if( gameInfo.variant == VariantGreat )
8677            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8678
8679       if(overruled) {
8680            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8681                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8682            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8683            if(StrStr(cps->variants, b) == NULL) { 
8684                // specific sized variant not known, check if general sizing allowed
8685                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8686                    if(StrStr(cps->variants, "boardsize") == NULL) {
8687                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8688                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8689                        DisplayFatalError(buf, 0, 1);
8690                        return;
8691                    }
8692                    /* [HGM] here we really should compare with the maximum supported board size */
8693                }
8694            }
8695       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8696       sprintf(buf, "variant %s\n", b);
8697       SendToProgram(buf, cps);
8698     }
8699     currentlyInitializedVariant = gameInfo.variant;
8700
8701     /* [HGM] send opening position in FRC to first engine */
8702     if(setup) {
8703           SendToProgram("force\n", cps);
8704           SendBoard(cps, 0);
8705           /* engine is now in force mode! Set flag to wake it up after first move. */
8706           setboardSpoiledMachineBlack = 1;
8707     }
8708
8709     if (cps->sendICS) {
8710       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8711       SendToProgram(buf, cps);
8712     }
8713     cps->maybeThinking = FALSE;
8714     cps->offeredDraw = 0;
8715     if (!appData.icsActive) {
8716         SendTimeControl(cps, movesPerSession, timeControl,
8717                         timeIncrement, appData.searchDepth,
8718                         searchTime);
8719     }
8720     if (appData.showThinking 
8721         // [HGM] thinking: four options require thinking output to be sent
8722         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8723                                 ) {
8724         SendToProgram("post\n", cps);
8725     }
8726     SendToProgram("hard\n", cps);
8727     if (!appData.ponderNextMove) {
8728         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8729            it without being sure what state we are in first.  "hard"
8730            is not a toggle, so that one is OK.
8731          */
8732         SendToProgram("easy\n", cps);
8733     }
8734     if (cps->usePing) {
8735       sprintf(buf, "ping %d\n", ++cps->lastPing);
8736       SendToProgram(buf, cps);
8737     }
8738     cps->initDone = TRUE;
8739 }   
8740
8741
8742 void
8743 StartChessProgram(cps)
8744      ChessProgramState *cps;
8745 {
8746     char buf[MSG_SIZ];
8747     int err;
8748
8749     if (appData.noChessProgram) return;
8750     cps->initDone = FALSE;
8751
8752     if (strcmp(cps->host, "localhost") == 0) {
8753         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8754     } else if (*appData.remoteShell == NULLCHAR) {
8755         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8756     } else {
8757         if (*appData.remoteUser == NULLCHAR) {
8758           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8759                     cps->program);
8760         } else {
8761           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8762                     cps->host, appData.remoteUser, cps->program);
8763         }
8764         err = StartChildProcess(buf, "", &cps->pr);
8765     }
8766     
8767     if (err != 0) {
8768         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8769         DisplayFatalError(buf, err, 1);
8770         cps->pr = NoProc;
8771         cps->isr = NULL;
8772         return;
8773     }
8774     
8775     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8776     if (cps->protocolVersion > 1) {
8777       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8778       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8779       cps->comboCnt = 0;  //                and values of combo boxes
8780       SendToProgram(buf, cps);
8781     } else {
8782       SendToProgram("xboard\n", cps);
8783     }
8784 }
8785
8786
8787 void
8788 TwoMachinesEventIfReady P((void))
8789 {
8790   if (first.lastPing != first.lastPong) {
8791     DisplayMessage("", _("Waiting for first chess program"));
8792     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8793     return;
8794   }
8795   if (second.lastPing != second.lastPong) {
8796     DisplayMessage("", _("Waiting for second chess program"));
8797     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8798     return;
8799   }
8800   ThawUI();
8801   TwoMachinesEvent();
8802 }
8803
8804 void
8805 NextMatchGame P((void))
8806 {
8807     int index; /* [HGM] autoinc: step load index during match */
8808     Reset(FALSE, TRUE);
8809     if (*appData.loadGameFile != NULLCHAR) {
8810         index = appData.loadGameIndex;
8811         if(index < 0) { // [HGM] autoinc
8812             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8813             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8814         } 
8815         LoadGameFromFile(appData.loadGameFile,
8816                          index,
8817                          appData.loadGameFile, FALSE);
8818     } else if (*appData.loadPositionFile != NULLCHAR) {
8819         index = appData.loadPositionIndex;
8820         if(index < 0) { // [HGM] autoinc
8821             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8822             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8823         } 
8824         LoadPositionFromFile(appData.loadPositionFile,
8825                              index,
8826                              appData.loadPositionFile);
8827     }
8828     TwoMachinesEventIfReady();
8829 }
8830
8831 void UserAdjudicationEvent( int result )
8832 {
8833     ChessMove gameResult = GameIsDrawn;
8834
8835     if( result > 0 ) {
8836         gameResult = WhiteWins;
8837     }
8838     else if( result < 0 ) {
8839         gameResult = BlackWins;
8840     }
8841
8842     if( gameMode == TwoMachinesPlay ) {
8843         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8844     }
8845 }
8846
8847
8848 // [HGM] save: calculate checksum of game to make games easily identifiable
8849 int StringCheckSum(char *s)
8850 {
8851         int i = 0;
8852         if(s==NULL) return 0;
8853         while(*s) i = i*259 + *s++;
8854         return i;
8855 }
8856
8857 int GameCheckSum()
8858 {
8859         int i, sum=0;
8860         for(i=backwardMostMove; i<forwardMostMove; i++) {
8861                 sum += pvInfoList[i].depth;
8862                 sum += StringCheckSum(parseList[i]);
8863                 sum += StringCheckSum(commentList[i]);
8864                 sum *= 261;
8865         }
8866         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8867         return sum + StringCheckSum(commentList[i]);
8868 } // end of save patch
8869
8870 void
8871 GameEnds(result, resultDetails, whosays)
8872      ChessMove result;
8873      char *resultDetails;
8874      int whosays;
8875 {
8876     GameMode nextGameMode;
8877     int isIcsGame;
8878     char buf[MSG_SIZ];
8879
8880     if(endingGame) return; /* [HGM] crash: forbid recursion */
8881     endingGame = 1;
8882
8883     if (appData.debugMode) {
8884       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8885               result, resultDetails ? resultDetails : "(null)", whosays);
8886     }
8887
8888     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8889
8890     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8891         /* If we are playing on ICS, the server decides when the
8892            game is over, but the engine can offer to draw, claim 
8893            a draw, or resign. 
8894          */
8895 #if ZIPPY
8896         if (appData.zippyPlay && first.initDone) {
8897             if (result == GameIsDrawn) {
8898                 /* In case draw still needs to be claimed */
8899                 SendToICS(ics_prefix);
8900                 SendToICS("draw\n");
8901             } else if (StrCaseStr(resultDetails, "resign")) {
8902                 SendToICS(ics_prefix);
8903                 SendToICS("resign\n");
8904             }
8905         }
8906 #endif
8907         endingGame = 0; /* [HGM] crash */
8908         return;
8909     }
8910
8911     /* If we're loading the game from a file, stop */
8912     if (whosays == GE_FILE) {
8913       (void) StopLoadGameTimer();
8914       gameFileFP = NULL;
8915     }
8916
8917     /* Cancel draw offers */
8918     first.offeredDraw = second.offeredDraw = 0;
8919
8920     /* If this is an ICS game, only ICS can really say it's done;
8921        if not, anyone can. */
8922     isIcsGame = (gameMode == IcsPlayingWhite || 
8923                  gameMode == IcsPlayingBlack || 
8924                  gameMode == IcsObserving    || 
8925                  gameMode == IcsExamining);
8926
8927     if (!isIcsGame || whosays == GE_ICS) {
8928         /* OK -- not an ICS game, or ICS said it was done */
8929         StopClocks();
8930         if (!isIcsGame && !appData.noChessProgram) 
8931           SetUserThinkingEnables();
8932     
8933         /* [HGM] if a machine claims the game end we verify this claim */
8934         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8935             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8936                 char claimer;
8937                 ChessMove trueResult = (ChessMove) -1;
8938
8939                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8940                                             first.twoMachinesColor[0] :
8941                                             second.twoMachinesColor[0] ;
8942
8943                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8944                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8945                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8946                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8947                 } else
8948                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8949                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8950                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8951                 } else
8952                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8953                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8954                 }
8955
8956                 // now verify win claims, but not in drop games, as we don't understand those yet
8957                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8958                                                  || gameInfo.variant == VariantGreat) &&
8959                     (result == WhiteWins && claimer == 'w' ||
8960                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8961                       if (appData.debugMode) {
8962                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8963                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8964                       }
8965                       if(result != trueResult) {
8966                               sprintf(buf, "False win claim: '%s'", resultDetails);
8967                               result = claimer == 'w' ? BlackWins : WhiteWins;
8968                               resultDetails = buf;
8969                       }
8970                 } else
8971                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8972                     && (forwardMostMove <= backwardMostMove ||
8973                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8974                         (claimer=='b')==(forwardMostMove&1))
8975                                                                                   ) {
8976                       /* [HGM] verify: draws that were not flagged are false claims */
8977                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8978                       result = claimer == 'w' ? BlackWins : WhiteWins;
8979                       resultDetails = buf;
8980                 }
8981                 /* (Claiming a loss is accepted no questions asked!) */
8982             }
8983             /* [HGM] bare: don't allow bare King to win */
8984             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8985                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8986                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8987                && result != GameIsDrawn)
8988             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8989                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8990                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8991                         if(p >= 0 && p <= (int)WhiteKing) k++;
8992                 }
8993                 if (appData.debugMode) {
8994                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8995                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8996                 }
8997                 if(k <= 1) {
8998                         result = GameIsDrawn;
8999                         sprintf(buf, "%s but bare king", resultDetails);
9000                         resultDetails = buf;
9001                 }
9002             }
9003         }
9004
9005
9006         if(serverMoves != NULL && !loadFlag) { char c = '=';
9007             if(result==WhiteWins) c = '+';
9008             if(result==BlackWins) c = '-';
9009             if(resultDetails != NULL)
9010                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9011         }
9012         if (resultDetails != NULL) {
9013             gameInfo.result = result;
9014             gameInfo.resultDetails = StrSave(resultDetails);
9015
9016             /* display last move only if game was not loaded from file */
9017             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9018                 DisplayMove(currentMove - 1);
9019     
9020             if (forwardMostMove != 0) {
9021                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9022                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9023                                                                 ) {
9024                     if (*appData.saveGameFile != NULLCHAR) {
9025                         SaveGameToFile(appData.saveGameFile, TRUE);
9026                     } else if (appData.autoSaveGames) {
9027                         AutoSaveGame();
9028                     }
9029                     if (*appData.savePositionFile != NULLCHAR) {
9030                         SavePositionToFile(appData.savePositionFile);
9031                     }
9032                 }
9033             }
9034
9035             /* Tell program how game ended in case it is learning */
9036             /* [HGM] Moved this to after saving the PGN, just in case */
9037             /* engine died and we got here through time loss. In that */
9038             /* case we will get a fatal error writing the pipe, which */
9039             /* would otherwise lose us the PGN.                       */
9040             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9041             /* output during GameEnds should never be fatal anymore   */
9042             if (gameMode == MachinePlaysWhite ||
9043                 gameMode == MachinePlaysBlack ||
9044                 gameMode == TwoMachinesPlay ||
9045                 gameMode == IcsPlayingWhite ||
9046                 gameMode == IcsPlayingBlack ||
9047                 gameMode == BeginningOfGame) {
9048                 char buf[MSG_SIZ];
9049                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9050                         resultDetails);
9051                 if (first.pr != NoProc) {
9052                     SendToProgram(buf, &first);
9053                 }
9054                 if (second.pr != NoProc &&
9055                     gameMode == TwoMachinesPlay) {
9056                     SendToProgram(buf, &second);
9057                 }
9058             }
9059         }
9060
9061         if (appData.icsActive) {
9062             if (appData.quietPlay &&
9063                 (gameMode == IcsPlayingWhite ||
9064                  gameMode == IcsPlayingBlack)) {
9065                 SendToICS(ics_prefix);
9066                 SendToICS("set shout 1\n");
9067             }
9068             nextGameMode = IcsIdle;
9069             ics_user_moved = FALSE;
9070             /* clean up premove.  It's ugly when the game has ended and the
9071              * premove highlights are still on the board.
9072              */
9073             if (gotPremove) {
9074               gotPremove = FALSE;
9075               ClearPremoveHighlights();
9076               DrawPosition(FALSE, boards[currentMove]);
9077             }
9078             if (whosays == GE_ICS) {
9079                 switch (result) {
9080                 case WhiteWins:
9081                     if (gameMode == IcsPlayingWhite)
9082                         PlayIcsWinSound();
9083                     else if(gameMode == IcsPlayingBlack)
9084                         PlayIcsLossSound();
9085                     break;
9086                 case BlackWins:
9087                     if (gameMode == IcsPlayingBlack)
9088                         PlayIcsWinSound();
9089                     else if(gameMode == IcsPlayingWhite)
9090                         PlayIcsLossSound();
9091                     break;
9092                 case GameIsDrawn:
9093                     PlayIcsDrawSound();
9094                     break;
9095                 default:
9096                     PlayIcsUnfinishedSound();
9097                 }
9098             }
9099         } else if (gameMode == EditGame ||
9100                    gameMode == PlayFromGameFile || 
9101                    gameMode == AnalyzeMode || 
9102                    gameMode == AnalyzeFile) {
9103             nextGameMode = gameMode;
9104         } else {
9105             nextGameMode = EndOfGame;
9106         }
9107         pausing = FALSE;
9108         ModeHighlight();
9109     } else {
9110         nextGameMode = gameMode;
9111     }
9112
9113     if (appData.noChessProgram) {
9114         gameMode = nextGameMode;
9115         ModeHighlight();
9116         endingGame = 0; /* [HGM] crash */
9117         return;
9118     }
9119
9120     if (first.reuse) {
9121         /* Put first chess program into idle state */
9122         if (first.pr != NoProc &&
9123             (gameMode == MachinePlaysWhite ||
9124              gameMode == MachinePlaysBlack ||
9125              gameMode == TwoMachinesPlay ||
9126              gameMode == IcsPlayingWhite ||
9127              gameMode == IcsPlayingBlack ||
9128              gameMode == BeginningOfGame)) {
9129             SendToProgram("force\n", &first);
9130             if (first.usePing) {
9131               char buf[MSG_SIZ];
9132               sprintf(buf, "ping %d\n", ++first.lastPing);
9133               SendToProgram(buf, &first);
9134             }
9135         }
9136     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9137         /* Kill off first chess program */
9138         if (first.isr != NULL)
9139           RemoveInputSource(first.isr);
9140         first.isr = NULL;
9141     
9142         if (first.pr != NoProc) {
9143             ExitAnalyzeMode();
9144             DoSleep( appData.delayBeforeQuit );
9145             SendToProgram("quit\n", &first);
9146             DoSleep( appData.delayAfterQuit );
9147             DestroyChildProcess(first.pr, first.useSigterm);
9148         }
9149         first.pr = NoProc;
9150     }
9151     if (second.reuse) {
9152         /* Put second chess program into idle state */
9153         if (second.pr != NoProc &&
9154             gameMode == TwoMachinesPlay) {
9155             SendToProgram("force\n", &second);
9156             if (second.usePing) {
9157               char buf[MSG_SIZ];
9158               sprintf(buf, "ping %d\n", ++second.lastPing);
9159               SendToProgram(buf, &second);
9160             }
9161         }
9162     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9163         /* Kill off second chess program */
9164         if (second.isr != NULL)
9165           RemoveInputSource(second.isr);
9166         second.isr = NULL;
9167     
9168         if (second.pr != NoProc) {
9169             DoSleep( appData.delayBeforeQuit );
9170             SendToProgram("quit\n", &second);
9171             DoSleep( appData.delayAfterQuit );
9172             DestroyChildProcess(second.pr, second.useSigterm);
9173         }
9174         second.pr = NoProc;
9175     }
9176
9177     if (matchMode && gameMode == TwoMachinesPlay) {
9178         switch (result) {
9179         case WhiteWins:
9180           if (first.twoMachinesColor[0] == 'w') {
9181             first.matchWins++;
9182           } else {
9183             second.matchWins++;
9184           }
9185           break;
9186         case BlackWins:
9187           if (first.twoMachinesColor[0] == 'b') {
9188             first.matchWins++;
9189           } else {
9190             second.matchWins++;
9191           }
9192           break;
9193         default:
9194           break;
9195         }
9196         if (matchGame < appData.matchGames) {
9197             char *tmp;
9198             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9199                 tmp = first.twoMachinesColor;
9200                 first.twoMachinesColor = second.twoMachinesColor;
9201                 second.twoMachinesColor = tmp;
9202             }
9203             gameMode = nextGameMode;
9204             matchGame++;
9205             if(appData.matchPause>10000 || appData.matchPause<10)
9206                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9207             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9208             endingGame = 0; /* [HGM] crash */
9209             return;
9210         } else {
9211             char buf[MSG_SIZ];
9212             gameMode = nextGameMode;
9213             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9214                     first.tidy, second.tidy,
9215                     first.matchWins, second.matchWins,
9216                     appData.matchGames - (first.matchWins + second.matchWins));
9217             DisplayFatalError(buf, 0, 0);
9218         }
9219     }
9220     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9221         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9222       ExitAnalyzeMode();
9223     gameMode = nextGameMode;
9224     ModeHighlight();
9225     endingGame = 0;  /* [HGM] crash */
9226 }
9227
9228 /* Assumes program was just initialized (initString sent).
9229    Leaves program in force mode. */
9230 void
9231 FeedMovesToProgram(cps, upto) 
9232      ChessProgramState *cps;
9233      int upto;
9234 {
9235     int i;
9236     
9237     if (appData.debugMode)
9238       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9239               startedFromSetupPosition ? "position and " : "",
9240               backwardMostMove, upto, cps->which);
9241     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9242         // [HGM] variantswitch: make engine aware of new variant
9243         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9244                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9245         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9246         SendToProgram(buf, cps);
9247         currentlyInitializedVariant = gameInfo.variant;
9248     }
9249     SendToProgram("force\n", cps);
9250     if (startedFromSetupPosition) {
9251         SendBoard(cps, backwardMostMove);
9252     if (appData.debugMode) {
9253         fprintf(debugFP, "feedMoves\n");
9254     }
9255     }
9256     for (i = backwardMostMove; i < upto; i++) {
9257         SendMoveToProgram(i, cps);
9258     }
9259 }
9260
9261
9262 void
9263 ResurrectChessProgram()
9264 {
9265      /* The chess program may have exited.
9266         If so, restart it and feed it all the moves made so far. */
9267
9268     if (appData.noChessProgram || first.pr != NoProc) return;
9269     
9270     StartChessProgram(&first);
9271     InitChessProgram(&first, FALSE);
9272     FeedMovesToProgram(&first, currentMove);
9273
9274     if (!first.sendTime) {
9275         /* can't tell gnuchess what its clock should read,
9276            so we bow to its notion. */
9277         ResetClocks();
9278         timeRemaining[0][currentMove] = whiteTimeRemaining;
9279         timeRemaining[1][currentMove] = blackTimeRemaining;
9280     }
9281
9282     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9283                 appData.icsEngineAnalyze) && first.analysisSupport) {
9284       SendToProgram("analyze\n", &first);
9285       first.analyzing = TRUE;
9286     }
9287 }
9288
9289 /*
9290  * Button procedures
9291  */
9292 void
9293 Reset(redraw, init)
9294      int redraw, init;
9295 {
9296     int i;
9297
9298     if (appData.debugMode) {
9299         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9300                 redraw, init, gameMode);
9301     }
9302     CleanupTail(); // [HGM] vari: delete any stored variations
9303     pausing = pauseExamInvalid = FALSE;
9304     startedFromSetupPosition = blackPlaysFirst = FALSE;
9305     firstMove = TRUE;
9306     whiteFlag = blackFlag = FALSE;
9307     userOfferedDraw = FALSE;
9308     hintRequested = bookRequested = FALSE;
9309     first.maybeThinking = FALSE;
9310     second.maybeThinking = FALSE;
9311     first.bookSuspend = FALSE; // [HGM] book
9312     second.bookSuspend = FALSE;
9313     thinkOutput[0] = NULLCHAR;
9314     lastHint[0] = NULLCHAR;
9315     ClearGameInfo(&gameInfo);
9316     gameInfo.variant = StringToVariant(appData.variant);
9317     ics_user_moved = ics_clock_paused = FALSE;
9318     ics_getting_history = H_FALSE;
9319     ics_gamenum = -1;
9320     white_holding[0] = black_holding[0] = NULLCHAR;
9321     ClearProgramStats();
9322     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9323     
9324     ResetFrontEnd();
9325     ClearHighlights();
9326     flipView = appData.flipView;
9327     ClearPremoveHighlights();
9328     gotPremove = FALSE;
9329     alarmSounded = FALSE;
9330
9331     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9332     if(appData.serverMovesName != NULL) {
9333         /* [HGM] prepare to make moves file for broadcasting */
9334         clock_t t = clock();
9335         if(serverMoves != NULL) fclose(serverMoves);
9336         serverMoves = fopen(appData.serverMovesName, "r");
9337         if(serverMoves != NULL) {
9338             fclose(serverMoves);
9339             /* delay 15 sec before overwriting, so all clients can see end */
9340             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9341         }
9342         serverMoves = fopen(appData.serverMovesName, "w");
9343     }
9344
9345     ExitAnalyzeMode();
9346     gameMode = BeginningOfGame;
9347     ModeHighlight();
9348     if(appData.icsActive) gameInfo.variant = VariantNormal;
9349     currentMove = forwardMostMove = backwardMostMove = 0;
9350     InitPosition(redraw);
9351     for (i = 0; i < MAX_MOVES; i++) {
9352         if (commentList[i] != NULL) {
9353             free(commentList[i]);
9354             commentList[i] = NULL;
9355         }
9356     }
9357     ResetClocks();
9358     timeRemaining[0][0] = whiteTimeRemaining;
9359     timeRemaining[1][0] = blackTimeRemaining;
9360     if (first.pr == NULL) {
9361         StartChessProgram(&first);
9362     }
9363     if (init) {
9364             InitChessProgram(&first, startedFromSetupPosition);
9365     }
9366     DisplayTitle("");
9367     DisplayMessage("", "");
9368     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9369     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9370 }
9371
9372 void
9373 AutoPlayGameLoop()
9374 {
9375     for (;;) {
9376         if (!AutoPlayOneMove())
9377           return;
9378         if (matchMode || appData.timeDelay == 0)
9379           continue;
9380         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9381           return;
9382         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9383         break;
9384     }
9385 }
9386
9387
9388 int
9389 AutoPlayOneMove()
9390 {
9391     int fromX, fromY, toX, toY;
9392
9393     if (appData.debugMode) {
9394       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9395     }
9396
9397     if (gameMode != PlayFromGameFile)
9398       return FALSE;
9399
9400     if (currentMove >= forwardMostMove) {
9401       gameMode = EditGame;
9402       ModeHighlight();
9403
9404       /* [AS] Clear current move marker at the end of a game */
9405       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9406
9407       return FALSE;
9408     }
9409     
9410     toX = moveList[currentMove][2] - AAA;
9411     toY = moveList[currentMove][3] - ONE;
9412
9413     if (moveList[currentMove][1] == '@') {
9414         if (appData.highlightLastMove) {
9415             SetHighlights(-1, -1, toX, toY);
9416         }
9417     } else {
9418         fromX = moveList[currentMove][0] - AAA;
9419         fromY = moveList[currentMove][1] - ONE;
9420
9421         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9422
9423         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9424
9425         if (appData.highlightLastMove) {
9426             SetHighlights(fromX, fromY, toX, toY);
9427         }
9428     }
9429     DisplayMove(currentMove);
9430     SendMoveToProgram(currentMove++, &first);
9431     DisplayBothClocks();
9432     DrawPosition(FALSE, boards[currentMove]);
9433     // [HGM] PV info: always display, routine tests if empty
9434     DisplayComment(currentMove - 1, commentList[currentMove]);
9435     return TRUE;
9436 }
9437
9438
9439 int
9440 LoadGameOneMove(readAhead)
9441      ChessMove readAhead;
9442 {
9443     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9444     char promoChar = NULLCHAR;
9445     ChessMove moveType;
9446     char move[MSG_SIZ];
9447     char *p, *q;
9448     
9449     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9450         gameMode != AnalyzeMode && gameMode != Training) {
9451         gameFileFP = NULL;
9452         return FALSE;
9453     }
9454     
9455     yyboardindex = forwardMostMove;
9456     if (readAhead != (ChessMove)0) {
9457       moveType = readAhead;
9458     } else {
9459       if (gameFileFP == NULL)
9460           return FALSE;
9461       moveType = (ChessMove) yylex();
9462     }
9463     
9464     done = FALSE;
9465     switch (moveType) {
9466       case Comment:
9467         if (appData.debugMode) 
9468           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9469         p = yy_text;
9470
9471         /* append the comment but don't display it */
9472         AppendComment(currentMove, p, FALSE);
9473         return TRUE;
9474
9475       case WhiteCapturesEnPassant:
9476       case BlackCapturesEnPassant:
9477       case WhitePromotionChancellor:
9478       case BlackPromotionChancellor:
9479       case WhitePromotionArchbishop:
9480       case BlackPromotionArchbishop:
9481       case WhitePromotionCentaur:
9482       case BlackPromotionCentaur:
9483       case WhitePromotionQueen:
9484       case BlackPromotionQueen:
9485       case WhitePromotionRook:
9486       case BlackPromotionRook:
9487       case WhitePromotionBishop:
9488       case BlackPromotionBishop:
9489       case WhitePromotionKnight:
9490       case BlackPromotionKnight:
9491       case WhitePromotionKing:
9492       case BlackPromotionKing:
9493       case NormalMove:
9494       case WhiteKingSideCastle:
9495       case WhiteQueenSideCastle:
9496       case BlackKingSideCastle:
9497       case BlackQueenSideCastle:
9498       case WhiteKingSideCastleWild:
9499       case WhiteQueenSideCastleWild:
9500       case BlackKingSideCastleWild:
9501       case BlackQueenSideCastleWild:
9502       /* PUSH Fabien */
9503       case WhiteHSideCastleFR:
9504       case WhiteASideCastleFR:
9505       case BlackHSideCastleFR:
9506       case BlackASideCastleFR:
9507       /* POP Fabien */
9508         if (appData.debugMode)
9509           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9510         fromX = currentMoveString[0] - AAA;
9511         fromY = currentMoveString[1] - ONE;
9512         toX = currentMoveString[2] - AAA;
9513         toY = currentMoveString[3] - ONE;
9514         promoChar = currentMoveString[4];
9515         break;
9516
9517       case WhiteDrop:
9518       case BlackDrop:
9519         if (appData.debugMode)
9520           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9521         fromX = moveType == WhiteDrop ?
9522           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9523         (int) CharToPiece(ToLower(currentMoveString[0]));
9524         fromY = DROP_RANK;
9525         toX = currentMoveString[2] - AAA;
9526         toY = currentMoveString[3] - ONE;
9527         break;
9528
9529       case WhiteWins:
9530       case BlackWins:
9531       case GameIsDrawn:
9532       case GameUnfinished:
9533         if (appData.debugMode)
9534           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9535         p = strchr(yy_text, '{');
9536         if (p == NULL) p = strchr(yy_text, '(');
9537         if (p == NULL) {
9538             p = yy_text;
9539             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9540         } else {
9541             q = strchr(p, *p == '{' ? '}' : ')');
9542             if (q != NULL) *q = NULLCHAR;
9543             p++;
9544         }
9545         GameEnds(moveType, p, GE_FILE);
9546         done = TRUE;
9547         if (cmailMsgLoaded) {
9548             ClearHighlights();
9549             flipView = WhiteOnMove(currentMove);
9550             if (moveType == GameUnfinished) flipView = !flipView;
9551             if (appData.debugMode)
9552               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9553         }
9554         break;
9555
9556       case (ChessMove) 0:       /* end of file */
9557         if (appData.debugMode)
9558           fprintf(debugFP, "Parser hit end of file\n");
9559         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9560           case MT_NONE:
9561           case MT_CHECK:
9562             break;
9563           case MT_CHECKMATE:
9564           case MT_STAINMATE:
9565             if (WhiteOnMove(currentMove)) {
9566                 GameEnds(BlackWins, "Black mates", GE_FILE);
9567             } else {
9568                 GameEnds(WhiteWins, "White mates", GE_FILE);
9569             }
9570             break;
9571           case MT_STALEMATE:
9572             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9573             break;
9574         }
9575         done = TRUE;
9576         break;
9577
9578       case MoveNumberOne:
9579         if (lastLoadGameStart == GNUChessGame) {
9580             /* GNUChessGames have numbers, but they aren't move numbers */
9581             if (appData.debugMode)
9582               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9583                       yy_text, (int) moveType);
9584             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9585         }
9586         /* else fall thru */
9587
9588       case XBoardGame:
9589       case GNUChessGame:
9590       case PGNTag:
9591         /* Reached start of next game in file */
9592         if (appData.debugMode)
9593           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9594         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9595           case MT_NONE:
9596           case MT_CHECK:
9597             break;
9598           case MT_CHECKMATE:
9599           case MT_STAINMATE:
9600             if (WhiteOnMove(currentMove)) {
9601                 GameEnds(BlackWins, "Black mates", GE_FILE);
9602             } else {
9603                 GameEnds(WhiteWins, "White mates", GE_FILE);
9604             }
9605             break;
9606           case MT_STALEMATE:
9607             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9608             break;
9609         }
9610         done = TRUE;
9611         break;
9612
9613       case PositionDiagram:     /* should not happen; ignore */
9614       case ElapsedTime:         /* ignore */
9615       case NAG:                 /* ignore */
9616         if (appData.debugMode)
9617           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9618                   yy_text, (int) moveType);
9619         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9620
9621       case IllegalMove:
9622         if (appData.testLegality) {
9623             if (appData.debugMode)
9624               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9625             sprintf(move, _("Illegal move: %d.%s%s"),
9626                     (forwardMostMove / 2) + 1,
9627                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9628             DisplayError(move, 0);
9629             done = TRUE;
9630         } else {
9631             if (appData.debugMode)
9632               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9633                       yy_text, currentMoveString);
9634             fromX = currentMoveString[0] - AAA;
9635             fromY = currentMoveString[1] - ONE;
9636             toX = currentMoveString[2] - AAA;
9637             toY = currentMoveString[3] - ONE;
9638             promoChar = currentMoveString[4];
9639         }
9640         break;
9641
9642       case AmbiguousMove:
9643         if (appData.debugMode)
9644           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9645         sprintf(move, _("Ambiguous move: %d.%s%s"),
9646                 (forwardMostMove / 2) + 1,
9647                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9648         DisplayError(move, 0);
9649         done = TRUE;
9650         break;
9651
9652       default:
9653       case ImpossibleMove:
9654         if (appData.debugMode)
9655           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9656         sprintf(move, _("Illegal move: %d.%s%s"),
9657                 (forwardMostMove / 2) + 1,
9658                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9659         DisplayError(move, 0);
9660         done = TRUE;
9661         break;
9662     }
9663
9664     if (done) {
9665         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9666             DrawPosition(FALSE, boards[currentMove]);
9667             DisplayBothClocks();
9668             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9669               DisplayComment(currentMove - 1, commentList[currentMove]);
9670         }
9671         (void) StopLoadGameTimer();
9672         gameFileFP = NULL;
9673         cmailOldMove = forwardMostMove;
9674         return FALSE;
9675     } else {
9676         /* currentMoveString is set as a side-effect of yylex */
9677         strcat(currentMoveString, "\n");
9678         strcpy(moveList[forwardMostMove], currentMoveString);
9679         
9680         thinkOutput[0] = NULLCHAR;
9681         MakeMove(fromX, fromY, toX, toY, promoChar);
9682         currentMove = forwardMostMove;
9683         return TRUE;
9684     }
9685 }
9686
9687 /* Load the nth game from the given file */
9688 int
9689 LoadGameFromFile(filename, n, title, useList)
9690      char *filename;
9691      int n;
9692      char *title;
9693      /*Boolean*/ int useList;
9694 {
9695     FILE *f;
9696     char buf[MSG_SIZ];
9697
9698     if (strcmp(filename, "-") == 0) {
9699         f = stdin;
9700         title = "stdin";
9701     } else {
9702         f = fopen(filename, "rb");
9703         if (f == NULL) {
9704           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9705             DisplayError(buf, errno);
9706             return FALSE;
9707         }
9708     }
9709     if (fseek(f, 0, 0) == -1) {
9710         /* f is not seekable; probably a pipe */
9711         useList = FALSE;
9712     }
9713     if (useList && n == 0) {
9714         int error = GameListBuild(f);
9715         if (error) {
9716             DisplayError(_("Cannot build game list"), error);
9717         } else if (!ListEmpty(&gameList) &&
9718                    ((ListGame *) gameList.tailPred)->number > 1) {
9719             GameListPopUp(f, title);
9720             return TRUE;
9721         }
9722         GameListDestroy();
9723         n = 1;
9724     }
9725     if (n == 0) n = 1;
9726     return LoadGame(f, n, title, FALSE);
9727 }
9728
9729
9730 void
9731 MakeRegisteredMove()
9732 {
9733     int fromX, fromY, toX, toY;
9734     char promoChar;
9735     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9736         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9737           case CMAIL_MOVE:
9738           case CMAIL_DRAW:
9739             if (appData.debugMode)
9740               fprintf(debugFP, "Restoring %s for game %d\n",
9741                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9742     
9743             thinkOutput[0] = NULLCHAR;
9744             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9745             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9746             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9747             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9748             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9749             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9750             MakeMove(fromX, fromY, toX, toY, promoChar);
9751             ShowMove(fromX, fromY, toX, toY);
9752               
9753             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9754               case MT_NONE:
9755               case MT_CHECK:
9756                 break;
9757                 
9758               case MT_CHECKMATE:
9759               case MT_STAINMATE:
9760                 if (WhiteOnMove(currentMove)) {
9761                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9762                 } else {
9763                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9764                 }
9765                 break;
9766                 
9767               case MT_STALEMATE:
9768                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9769                 break;
9770             }
9771
9772             break;
9773             
9774           case CMAIL_RESIGN:
9775             if (WhiteOnMove(currentMove)) {
9776                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9777             } else {
9778                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9779             }
9780             break;
9781             
9782           case CMAIL_ACCEPT:
9783             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9784             break;
9785               
9786           default:
9787             break;
9788         }
9789     }
9790
9791     return;
9792 }
9793
9794 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9795 int
9796 CmailLoadGame(f, gameNumber, title, useList)
9797      FILE *f;
9798      int gameNumber;
9799      char *title;
9800      int useList;
9801 {
9802     int retVal;
9803
9804     if (gameNumber > nCmailGames) {
9805         DisplayError(_("No more games in this message"), 0);
9806         return FALSE;
9807     }
9808     if (f == lastLoadGameFP) {
9809         int offset = gameNumber - lastLoadGameNumber;
9810         if (offset == 0) {
9811             cmailMsg[0] = NULLCHAR;
9812             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9813                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9814                 nCmailMovesRegistered--;
9815             }
9816             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9817             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9818                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9819             }
9820         } else {
9821             if (! RegisterMove()) return FALSE;
9822         }
9823     }
9824
9825     retVal = LoadGame(f, gameNumber, title, useList);
9826
9827     /* Make move registered during previous look at this game, if any */
9828     MakeRegisteredMove();
9829
9830     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9831         commentList[currentMove]
9832           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9833         DisplayComment(currentMove - 1, commentList[currentMove]);
9834     }
9835
9836     return retVal;
9837 }
9838
9839 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9840 int
9841 ReloadGame(offset)
9842      int offset;
9843 {
9844     int gameNumber = lastLoadGameNumber + offset;
9845     if (lastLoadGameFP == NULL) {
9846         DisplayError(_("No game has been loaded yet"), 0);
9847         return FALSE;
9848     }
9849     if (gameNumber <= 0) {
9850         DisplayError(_("Can't back up any further"), 0);
9851         return FALSE;
9852     }
9853     if (cmailMsgLoaded) {
9854         return CmailLoadGame(lastLoadGameFP, gameNumber,
9855                              lastLoadGameTitle, lastLoadGameUseList);
9856     } else {
9857         return LoadGame(lastLoadGameFP, gameNumber,
9858                         lastLoadGameTitle, lastLoadGameUseList);
9859     }
9860 }
9861
9862
9863
9864 /* Load the nth game from open file f */
9865 int
9866 LoadGame(f, gameNumber, title, useList)
9867      FILE *f;
9868      int gameNumber;
9869      char *title;
9870      int useList;
9871 {
9872     ChessMove cm;
9873     char buf[MSG_SIZ];
9874     int gn = gameNumber;
9875     ListGame *lg = NULL;
9876     int numPGNTags = 0;
9877     int err;
9878     GameMode oldGameMode;
9879     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9880
9881     if (appData.debugMode) 
9882         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9883
9884     if (gameMode == Training )
9885         SetTrainingModeOff();
9886
9887     oldGameMode = gameMode;
9888     if (gameMode != BeginningOfGame) {
9889       Reset(FALSE, TRUE);
9890     }
9891
9892     gameFileFP = f;
9893     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9894         fclose(lastLoadGameFP);
9895     }
9896
9897     if (useList) {
9898         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9899         
9900         if (lg) {
9901             fseek(f, lg->offset, 0);
9902             GameListHighlight(gameNumber);
9903             gn = 1;
9904         }
9905         else {
9906             DisplayError(_("Game number out of range"), 0);
9907             return FALSE;
9908         }
9909     } else {
9910         GameListDestroy();
9911         if (fseek(f, 0, 0) == -1) {
9912             if (f == lastLoadGameFP ?
9913                 gameNumber == lastLoadGameNumber + 1 :
9914                 gameNumber == 1) {
9915                 gn = 1;
9916             } else {
9917                 DisplayError(_("Can't seek on game file"), 0);
9918                 return FALSE;
9919             }
9920         }
9921     }
9922     lastLoadGameFP = f;
9923     lastLoadGameNumber = gameNumber;
9924     strcpy(lastLoadGameTitle, title);
9925     lastLoadGameUseList = useList;
9926
9927     yynewfile(f);
9928
9929     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9930       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9931                 lg->gameInfo.black);
9932             DisplayTitle(buf);
9933     } else if (*title != NULLCHAR) {
9934         if (gameNumber > 1) {
9935             sprintf(buf, "%s %d", title, gameNumber);
9936             DisplayTitle(buf);
9937         } else {
9938             DisplayTitle(title);
9939         }
9940     }
9941
9942     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9943         gameMode = PlayFromGameFile;
9944         ModeHighlight();
9945     }
9946
9947     currentMove = forwardMostMove = backwardMostMove = 0;
9948     CopyBoard(boards[0], initialPosition);
9949     StopClocks();
9950
9951     /*
9952      * Skip the first gn-1 games in the file.
9953      * Also skip over anything that precedes an identifiable 
9954      * start of game marker, to avoid being confused by 
9955      * garbage at the start of the file.  Currently 
9956      * recognized start of game markers are the move number "1",
9957      * the pattern "gnuchess .* game", the pattern
9958      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9959      * A game that starts with one of the latter two patterns
9960      * will also have a move number 1, possibly
9961      * following a position diagram.
9962      * 5-4-02: Let's try being more lenient and allowing a game to
9963      * start with an unnumbered move.  Does that break anything?
9964      */
9965     cm = lastLoadGameStart = (ChessMove) 0;
9966     while (gn > 0) {
9967         yyboardindex = forwardMostMove;
9968         cm = (ChessMove) yylex();
9969         switch (cm) {
9970           case (ChessMove) 0:
9971             if (cmailMsgLoaded) {
9972                 nCmailGames = CMAIL_MAX_GAMES - gn;
9973             } else {
9974                 Reset(TRUE, TRUE);
9975                 DisplayError(_("Game not found in file"), 0);
9976             }
9977             return FALSE;
9978
9979           case GNUChessGame:
9980           case XBoardGame:
9981             gn--;
9982             lastLoadGameStart = cm;
9983             break;
9984             
9985           case MoveNumberOne:
9986             switch (lastLoadGameStart) {
9987               case GNUChessGame:
9988               case XBoardGame:
9989               case PGNTag:
9990                 break;
9991               case MoveNumberOne:
9992               case (ChessMove) 0:
9993                 gn--;           /* count this game */
9994                 lastLoadGameStart = cm;
9995                 break;
9996               default:
9997                 /* impossible */
9998                 break;
9999             }
10000             break;
10001
10002           case PGNTag:
10003             switch (lastLoadGameStart) {
10004               case GNUChessGame:
10005               case PGNTag:
10006               case MoveNumberOne:
10007               case (ChessMove) 0:
10008                 gn--;           /* count this game */
10009                 lastLoadGameStart = cm;
10010                 break;
10011               case XBoardGame:
10012                 lastLoadGameStart = cm; /* game counted already */
10013                 break;
10014               default:
10015                 /* impossible */
10016                 break;
10017             }
10018             if (gn > 0) {
10019                 do {
10020                     yyboardindex = forwardMostMove;
10021                     cm = (ChessMove) yylex();
10022                 } while (cm == PGNTag || cm == Comment);
10023             }
10024             break;
10025
10026           case WhiteWins:
10027           case BlackWins:
10028           case GameIsDrawn:
10029             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10030                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10031                     != CMAIL_OLD_RESULT) {
10032                     nCmailResults ++ ;
10033                     cmailResult[  CMAIL_MAX_GAMES
10034                                 - gn - 1] = CMAIL_OLD_RESULT;
10035                 }
10036             }
10037             break;
10038
10039           case NormalMove:
10040             /* Only a NormalMove can be at the start of a game
10041              * without a position diagram. */
10042             if (lastLoadGameStart == (ChessMove) 0) {
10043               gn--;
10044               lastLoadGameStart = MoveNumberOne;
10045             }
10046             break;
10047
10048           default:
10049             break;
10050         }
10051     }
10052     
10053     if (appData.debugMode)
10054       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10055
10056     if (cm == XBoardGame) {
10057         /* Skip any header junk before position diagram and/or move 1 */
10058         for (;;) {
10059             yyboardindex = forwardMostMove;
10060             cm = (ChessMove) yylex();
10061
10062             if (cm == (ChessMove) 0 ||
10063                 cm == GNUChessGame || cm == XBoardGame) {
10064                 /* Empty game; pretend end-of-file and handle later */
10065                 cm = (ChessMove) 0;
10066                 break;
10067             }
10068
10069             if (cm == MoveNumberOne || cm == PositionDiagram ||
10070                 cm == PGNTag || cm == Comment)
10071               break;
10072         }
10073     } else if (cm == GNUChessGame) {
10074         if (gameInfo.event != NULL) {
10075             free(gameInfo.event);
10076         }
10077         gameInfo.event = StrSave(yy_text);
10078     }   
10079
10080     startedFromSetupPosition = FALSE;
10081     while (cm == PGNTag) {
10082         if (appData.debugMode) 
10083           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10084         err = ParsePGNTag(yy_text, &gameInfo);
10085         if (!err) numPGNTags++;
10086
10087         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10088         if(gameInfo.variant != oldVariant) {
10089             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10090             InitPosition(TRUE);
10091             oldVariant = gameInfo.variant;
10092             if (appData.debugMode) 
10093               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10094         }
10095
10096
10097         if (gameInfo.fen != NULL) {
10098           Board initial_position;
10099           startedFromSetupPosition = TRUE;
10100           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10101             Reset(TRUE, TRUE);
10102             DisplayError(_("Bad FEN position in file"), 0);
10103             return FALSE;
10104           }
10105           CopyBoard(boards[0], initial_position);
10106           if (blackPlaysFirst) {
10107             currentMove = forwardMostMove = backwardMostMove = 1;
10108             CopyBoard(boards[1], initial_position);
10109             strcpy(moveList[0], "");
10110             strcpy(parseList[0], "");
10111             timeRemaining[0][1] = whiteTimeRemaining;
10112             timeRemaining[1][1] = blackTimeRemaining;
10113             if (commentList[0] != NULL) {
10114               commentList[1] = commentList[0];
10115               commentList[0] = NULL;
10116             }
10117           } else {
10118             currentMove = forwardMostMove = backwardMostMove = 0;
10119           }
10120           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10121           {   int i;
10122               initialRulePlies = FENrulePlies;
10123               for( i=0; i< nrCastlingRights; i++ )
10124                   initialRights[i] = initial_position[CASTLING][i];
10125           }
10126           yyboardindex = forwardMostMove;
10127           free(gameInfo.fen);
10128           gameInfo.fen = NULL;
10129         }
10130
10131         yyboardindex = forwardMostMove;
10132         cm = (ChessMove) yylex();
10133
10134         /* Handle comments interspersed among the tags */
10135         while (cm == Comment) {
10136             char *p;
10137             if (appData.debugMode) 
10138               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10139             p = yy_text;
10140             AppendComment(currentMove, p, FALSE);
10141             yyboardindex = forwardMostMove;
10142             cm = (ChessMove) yylex();
10143         }
10144     }
10145
10146     /* don't rely on existence of Event tag since if game was
10147      * pasted from clipboard the Event tag may not exist
10148      */
10149     if (numPGNTags > 0){
10150         char *tags;
10151         if (gameInfo.variant == VariantNormal) {
10152           gameInfo.variant = StringToVariant(gameInfo.event);
10153         }
10154         if (!matchMode) {
10155           if( appData.autoDisplayTags ) {
10156             tags = PGNTags(&gameInfo);
10157             TagsPopUp(tags, CmailMsg());
10158             free(tags);
10159           }
10160         }
10161     } else {
10162         /* Make something up, but don't display it now */
10163         SetGameInfo();
10164         TagsPopDown();
10165     }
10166
10167     if (cm == PositionDiagram) {
10168         int i, j;
10169         char *p;
10170         Board initial_position;
10171
10172         if (appData.debugMode)
10173           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10174
10175         if (!startedFromSetupPosition) {
10176             p = yy_text;
10177             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10178               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10179                 switch (*p) {
10180                   case '[':
10181                   case '-':
10182                   case ' ':
10183                   case '\t':
10184                   case '\n':
10185                   case '\r':
10186                     break;
10187                   default:
10188                     initial_position[i][j++] = CharToPiece(*p);
10189                     break;
10190                 }
10191             while (*p == ' ' || *p == '\t' ||
10192                    *p == '\n' || *p == '\r') p++;
10193         
10194             if (strncmp(p, "black", strlen("black"))==0)
10195               blackPlaysFirst = TRUE;
10196             else
10197               blackPlaysFirst = FALSE;
10198             startedFromSetupPosition = TRUE;
10199         
10200             CopyBoard(boards[0], initial_position);
10201             if (blackPlaysFirst) {
10202                 currentMove = forwardMostMove = backwardMostMove = 1;
10203                 CopyBoard(boards[1], initial_position);
10204                 strcpy(moveList[0], "");
10205                 strcpy(parseList[0], "");
10206                 timeRemaining[0][1] = whiteTimeRemaining;
10207                 timeRemaining[1][1] = blackTimeRemaining;
10208                 if (commentList[0] != NULL) {
10209                     commentList[1] = commentList[0];
10210                     commentList[0] = NULL;
10211                 }
10212             } else {
10213                 currentMove = forwardMostMove = backwardMostMove = 0;
10214             }
10215         }
10216         yyboardindex = forwardMostMove;
10217         cm = (ChessMove) yylex();
10218     }
10219
10220     if (first.pr == NoProc) {
10221         StartChessProgram(&first);
10222     }
10223     InitChessProgram(&first, FALSE);
10224     SendToProgram("force\n", &first);
10225     if (startedFromSetupPosition) {
10226         SendBoard(&first, forwardMostMove);
10227     if (appData.debugMode) {
10228         fprintf(debugFP, "Load Game\n");
10229     }
10230         DisplayBothClocks();
10231     }      
10232
10233     /* [HGM] server: flag to write setup moves in broadcast file as one */
10234     loadFlag = appData.suppressLoadMoves;
10235
10236     while (cm == Comment) {
10237         char *p;
10238         if (appData.debugMode) 
10239           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10240         p = yy_text;
10241         AppendComment(currentMove, p, FALSE);
10242         yyboardindex = forwardMostMove;
10243         cm = (ChessMove) yylex();
10244     }
10245
10246     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10247         cm == WhiteWins || cm == BlackWins ||
10248         cm == GameIsDrawn || cm == GameUnfinished) {
10249         DisplayMessage("", _("No moves in game"));
10250         if (cmailMsgLoaded) {
10251             if (appData.debugMode)
10252               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10253             ClearHighlights();
10254             flipView = FALSE;
10255         }
10256         DrawPosition(FALSE, boards[currentMove]);
10257         DisplayBothClocks();
10258         gameMode = EditGame;
10259         ModeHighlight();
10260         gameFileFP = NULL;
10261         cmailOldMove = 0;
10262         return TRUE;
10263     }
10264
10265     // [HGM] PV info: routine tests if comment empty
10266     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10267         DisplayComment(currentMove - 1, commentList[currentMove]);
10268     }
10269     if (!matchMode && appData.timeDelay != 0) 
10270       DrawPosition(FALSE, boards[currentMove]);
10271
10272     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10273       programStats.ok_to_send = 1;
10274     }
10275
10276     /* if the first token after the PGN tags is a move
10277      * and not move number 1, retrieve it from the parser 
10278      */
10279     if (cm != MoveNumberOne)
10280         LoadGameOneMove(cm);
10281
10282     /* load the remaining moves from the file */
10283     while (LoadGameOneMove((ChessMove)0)) {
10284       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10285       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10286     }
10287
10288     /* rewind to the start of the game */
10289     currentMove = backwardMostMove;
10290
10291     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10292
10293     if (oldGameMode == AnalyzeFile ||
10294         oldGameMode == AnalyzeMode) {
10295       AnalyzeFileEvent();
10296     }
10297
10298     if (matchMode || appData.timeDelay == 0) {
10299       ToEndEvent();
10300       gameMode = EditGame;
10301       ModeHighlight();
10302     } else if (appData.timeDelay > 0) {
10303       AutoPlayGameLoop();
10304     }
10305
10306     if (appData.debugMode) 
10307         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10308
10309     loadFlag = 0; /* [HGM] true game starts */
10310     return TRUE;
10311 }
10312
10313 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10314 int
10315 ReloadPosition(offset)
10316      int offset;
10317 {
10318     int positionNumber = lastLoadPositionNumber + offset;
10319     if (lastLoadPositionFP == NULL) {
10320         DisplayError(_("No position has been loaded yet"), 0);
10321         return FALSE;
10322     }
10323     if (positionNumber <= 0) {
10324         DisplayError(_("Can't back up any further"), 0);
10325         return FALSE;
10326     }
10327     return LoadPosition(lastLoadPositionFP, positionNumber,
10328                         lastLoadPositionTitle);
10329 }
10330
10331 /* Load the nth position from the given file */
10332 int
10333 LoadPositionFromFile(filename, n, title)
10334      char *filename;
10335      int n;
10336      char *title;
10337 {
10338     FILE *f;
10339     char buf[MSG_SIZ];
10340
10341     if (strcmp(filename, "-") == 0) {
10342         return LoadPosition(stdin, n, "stdin");
10343     } else {
10344         f = fopen(filename, "rb");
10345         if (f == NULL) {
10346             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10347             DisplayError(buf, errno);
10348             return FALSE;
10349         } else {
10350             return LoadPosition(f, n, title);
10351         }
10352     }
10353 }
10354
10355 /* Load the nth position from the given open file, and close it */
10356 int
10357 LoadPosition(f, positionNumber, title)
10358      FILE *f;
10359      int positionNumber;
10360      char *title;
10361 {
10362     char *p, line[MSG_SIZ];
10363     Board initial_position;
10364     int i, j, fenMode, pn;
10365     
10366     if (gameMode == Training )
10367         SetTrainingModeOff();
10368
10369     if (gameMode != BeginningOfGame) {
10370         Reset(FALSE, TRUE);
10371     }
10372     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10373         fclose(lastLoadPositionFP);
10374     }
10375     if (positionNumber == 0) positionNumber = 1;
10376     lastLoadPositionFP = f;
10377     lastLoadPositionNumber = positionNumber;
10378     strcpy(lastLoadPositionTitle, title);
10379     if (first.pr == NoProc) {
10380       StartChessProgram(&first);
10381       InitChessProgram(&first, FALSE);
10382     }    
10383     pn = positionNumber;
10384     if (positionNumber < 0) {
10385         /* Negative position number means to seek to that byte offset */
10386         if (fseek(f, -positionNumber, 0) == -1) {
10387             DisplayError(_("Can't seek on position file"), 0);
10388             return FALSE;
10389         };
10390         pn = 1;
10391     } else {
10392         if (fseek(f, 0, 0) == -1) {
10393             if (f == lastLoadPositionFP ?
10394                 positionNumber == lastLoadPositionNumber + 1 :
10395                 positionNumber == 1) {
10396                 pn = 1;
10397             } else {
10398                 DisplayError(_("Can't seek on position file"), 0);
10399                 return FALSE;
10400             }
10401         }
10402     }
10403     /* See if this file is FEN or old-style xboard */
10404     if (fgets(line, MSG_SIZ, f) == NULL) {
10405         DisplayError(_("Position not found in file"), 0);
10406         return FALSE;
10407     }
10408     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10409     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10410
10411     if (pn >= 2) {
10412         if (fenMode || line[0] == '#') pn--;
10413         while (pn > 0) {
10414             /* skip positions before number pn */
10415             if (fgets(line, MSG_SIZ, f) == NULL) {
10416                 Reset(TRUE, TRUE);
10417                 DisplayError(_("Position not found in file"), 0);
10418                 return FALSE;
10419             }
10420             if (fenMode || line[0] == '#') pn--;
10421         }
10422     }
10423
10424     if (fenMode) {
10425         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10426             DisplayError(_("Bad FEN position in file"), 0);
10427             return FALSE;
10428         }
10429     } else {
10430         (void) fgets(line, MSG_SIZ, f);
10431         (void) fgets(line, MSG_SIZ, f);
10432     
10433         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10434             (void) fgets(line, MSG_SIZ, f);
10435             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10436                 if (*p == ' ')
10437                   continue;
10438                 initial_position[i][j++] = CharToPiece(*p);
10439             }
10440         }
10441     
10442         blackPlaysFirst = FALSE;
10443         if (!feof(f)) {
10444             (void) fgets(line, MSG_SIZ, f);
10445             if (strncmp(line, "black", strlen("black"))==0)
10446               blackPlaysFirst = TRUE;
10447         }
10448     }
10449     startedFromSetupPosition = TRUE;
10450     
10451     SendToProgram("force\n", &first);
10452     CopyBoard(boards[0], initial_position);
10453     if (blackPlaysFirst) {
10454         currentMove = forwardMostMove = backwardMostMove = 1;
10455         strcpy(moveList[0], "");
10456         strcpy(parseList[0], "");
10457         CopyBoard(boards[1], initial_position);
10458         DisplayMessage("", _("Black to play"));
10459     } else {
10460         currentMove = forwardMostMove = backwardMostMove = 0;
10461         DisplayMessage("", _("White to play"));
10462     }
10463     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10464     SendBoard(&first, forwardMostMove);
10465     if (appData.debugMode) {
10466 int i, j;
10467   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10468   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10469         fprintf(debugFP, "Load Position\n");
10470     }
10471
10472     if (positionNumber > 1) {
10473         sprintf(line, "%s %d", title, positionNumber);
10474         DisplayTitle(line);
10475     } else {
10476         DisplayTitle(title);
10477     }
10478     gameMode = EditGame;
10479     ModeHighlight();
10480     ResetClocks();
10481     timeRemaining[0][1] = whiteTimeRemaining;
10482     timeRemaining[1][1] = blackTimeRemaining;
10483     DrawPosition(FALSE, boards[currentMove]);
10484    
10485     return TRUE;
10486 }
10487
10488
10489 void
10490 CopyPlayerNameIntoFileName(dest, src)
10491      char **dest, *src;
10492 {
10493     while (*src != NULLCHAR && *src != ',') {
10494         if (*src == ' ') {
10495             *(*dest)++ = '_';
10496             src++;
10497         } else {
10498             *(*dest)++ = *src++;
10499         }
10500     }
10501 }
10502
10503 char *DefaultFileName(ext)
10504      char *ext;
10505 {
10506     static char def[MSG_SIZ];
10507     char *p;
10508
10509     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10510         p = def;
10511         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10512         *p++ = '-';
10513         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10514         *p++ = '.';
10515         strcpy(p, ext);
10516     } else {
10517         def[0] = NULLCHAR;
10518     }
10519     return def;
10520 }
10521
10522 /* Save the current game to the given file */
10523 int
10524 SaveGameToFile(filename, append)
10525      char *filename;
10526      int append;
10527 {
10528     FILE *f;
10529     char buf[MSG_SIZ];
10530
10531     if (strcmp(filename, "-") == 0) {
10532         return SaveGame(stdout, 0, NULL);
10533     } else {
10534         f = fopen(filename, append ? "a" : "w");
10535         if (f == NULL) {
10536             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10537             DisplayError(buf, errno);
10538             return FALSE;
10539         } else {
10540             return SaveGame(f, 0, NULL);
10541         }
10542     }
10543 }
10544
10545 char *
10546 SavePart(str)
10547      char *str;
10548 {
10549     static char buf[MSG_SIZ];
10550     char *p;
10551     
10552     p = strchr(str, ' ');
10553     if (p == NULL) return str;
10554     strncpy(buf, str, p - str);
10555     buf[p - str] = NULLCHAR;
10556     return buf;
10557 }
10558
10559 #define PGN_MAX_LINE 75
10560
10561 #define PGN_SIDE_WHITE  0
10562 #define PGN_SIDE_BLACK  1
10563
10564 /* [AS] */
10565 static int FindFirstMoveOutOfBook( int side )
10566 {
10567     int result = -1;
10568
10569     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10570         int index = backwardMostMove;
10571         int has_book_hit = 0;
10572
10573         if( (index % 2) != side ) {
10574             index++;
10575         }
10576
10577         while( index < forwardMostMove ) {
10578             /* Check to see if engine is in book */
10579             int depth = pvInfoList[index].depth;
10580             int score = pvInfoList[index].score;
10581             int in_book = 0;
10582
10583             if( depth <= 2 ) {
10584                 in_book = 1;
10585             }
10586             else if( score == 0 && depth == 63 ) {
10587                 in_book = 1; /* Zappa */
10588             }
10589             else if( score == 2 && depth == 99 ) {
10590                 in_book = 1; /* Abrok */
10591             }
10592
10593             has_book_hit += in_book;
10594
10595             if( ! in_book ) {
10596                 result = index;
10597
10598                 break;
10599             }
10600
10601             index += 2;
10602         }
10603     }
10604
10605     return result;
10606 }
10607
10608 /* [AS] */
10609 void GetOutOfBookInfo( char * buf )
10610 {
10611     int oob[2];
10612     int i;
10613     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10614
10615     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10616     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10617
10618     *buf = '\0';
10619
10620     if( oob[0] >= 0 || oob[1] >= 0 ) {
10621         for( i=0; i<2; i++ ) {
10622             int idx = oob[i];
10623
10624             if( idx >= 0 ) {
10625                 if( i > 0 && oob[0] >= 0 ) {
10626                     strcat( buf, "   " );
10627                 }
10628
10629                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10630                 sprintf( buf+strlen(buf), "%s%.2f", 
10631                     pvInfoList[idx].score >= 0 ? "+" : "",
10632                     pvInfoList[idx].score / 100.0 );
10633             }
10634         }
10635     }
10636 }
10637
10638 /* Save game in PGN style and close the file */
10639 int
10640 SaveGamePGN(f)
10641      FILE *f;
10642 {
10643     int i, offset, linelen, newblock;
10644     time_t tm;
10645 //    char *movetext;
10646     char numtext[32];
10647     int movelen, numlen, blank;
10648     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10649
10650     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10651     
10652     tm = time((time_t *) NULL);
10653     
10654     PrintPGNTags(f, &gameInfo);
10655     
10656     if (backwardMostMove > 0 || startedFromSetupPosition) {
10657         char *fen = PositionToFEN(backwardMostMove, NULL);
10658         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10659         fprintf(f, "\n{--------------\n");
10660         PrintPosition(f, backwardMostMove);
10661         fprintf(f, "--------------}\n");
10662         free(fen);
10663     }
10664     else {
10665         /* [AS] Out of book annotation */
10666         if( appData.saveOutOfBookInfo ) {
10667             char buf[64];
10668
10669             GetOutOfBookInfo( buf );
10670
10671             if( buf[0] != '\0' ) {
10672                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10673             }
10674         }
10675
10676         fprintf(f, "\n");
10677     }
10678
10679     i = backwardMostMove;
10680     linelen = 0;
10681     newblock = TRUE;
10682
10683     while (i < forwardMostMove) {
10684         /* Print comments preceding this move */
10685         if (commentList[i] != NULL) {
10686             if (linelen > 0) fprintf(f, "\n");
10687             fprintf(f, "%s", commentList[i]);
10688             linelen = 0;
10689             newblock = TRUE;
10690         }
10691
10692         /* Format move number */
10693         if ((i % 2) == 0) {
10694             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10695         } else {
10696             if (newblock) {
10697                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10698             } else {
10699                 numtext[0] = NULLCHAR;
10700             }
10701         }
10702         numlen = strlen(numtext);
10703         newblock = FALSE;
10704
10705         /* Print move number */
10706         blank = linelen > 0 && numlen > 0;
10707         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10708             fprintf(f, "\n");
10709             linelen = 0;
10710             blank = 0;
10711         }
10712         if (blank) {
10713             fprintf(f, " ");
10714             linelen++;
10715         }
10716         fprintf(f, "%s", numtext);
10717         linelen += numlen;
10718
10719         /* Get move */
10720         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10721         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10722
10723         /* Print move */
10724         blank = linelen > 0 && movelen > 0;
10725         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10726             fprintf(f, "\n");
10727             linelen = 0;
10728             blank = 0;
10729         }
10730         if (blank) {
10731             fprintf(f, " ");
10732             linelen++;
10733         }
10734         fprintf(f, "%s", move_buffer);
10735         linelen += movelen;
10736
10737         /* [AS] Add PV info if present */
10738         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10739             /* [HGM] add time */
10740             char buf[MSG_SIZ]; int seconds;
10741
10742             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10743
10744             if( seconds <= 0) buf[0] = 0; else
10745             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10746                 seconds = (seconds + 4)/10; // round to full seconds
10747                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10748                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10749             }
10750
10751             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10752                 pvInfoList[i].score >= 0 ? "+" : "",
10753                 pvInfoList[i].score / 100.0,
10754                 pvInfoList[i].depth,
10755                 buf );
10756
10757             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10758
10759             /* Print score/depth */
10760             blank = linelen > 0 && movelen > 0;
10761             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10762                 fprintf(f, "\n");
10763                 linelen = 0;
10764                 blank = 0;
10765             }
10766             if (blank) {
10767                 fprintf(f, " ");
10768                 linelen++;
10769             }
10770             fprintf(f, "%s", move_buffer);
10771             linelen += movelen;
10772         }
10773
10774         i++;
10775     }
10776     
10777     /* Start a new line */
10778     if (linelen > 0) fprintf(f, "\n");
10779
10780     /* Print comments after last move */
10781     if (commentList[i] != NULL) {
10782         fprintf(f, "%s\n", commentList[i]);
10783     }
10784
10785     /* Print result */
10786     if (gameInfo.resultDetails != NULL &&
10787         gameInfo.resultDetails[0] != NULLCHAR) {
10788         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10789                 PGNResult(gameInfo.result));
10790     } else {
10791         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10792     }
10793
10794     fclose(f);
10795     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10796     return TRUE;
10797 }
10798
10799 /* Save game in old style and close the file */
10800 int
10801 SaveGameOldStyle(f)
10802      FILE *f;
10803 {
10804     int i, offset;
10805     time_t tm;
10806     
10807     tm = time((time_t *) NULL);
10808     
10809     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10810     PrintOpponents(f);
10811     
10812     if (backwardMostMove > 0 || startedFromSetupPosition) {
10813         fprintf(f, "\n[--------------\n");
10814         PrintPosition(f, backwardMostMove);
10815         fprintf(f, "--------------]\n");
10816     } else {
10817         fprintf(f, "\n");
10818     }
10819
10820     i = backwardMostMove;
10821     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10822
10823     while (i < forwardMostMove) {
10824         if (commentList[i] != NULL) {
10825             fprintf(f, "[%s]\n", commentList[i]);
10826         }
10827
10828         if ((i % 2) == 1) {
10829             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10830             i++;
10831         } else {
10832             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10833             i++;
10834             if (commentList[i] != NULL) {
10835                 fprintf(f, "\n");
10836                 continue;
10837             }
10838             if (i >= forwardMostMove) {
10839                 fprintf(f, "\n");
10840                 break;
10841             }
10842             fprintf(f, "%s\n", parseList[i]);
10843             i++;
10844         }
10845     }
10846     
10847     if (commentList[i] != NULL) {
10848         fprintf(f, "[%s]\n", commentList[i]);
10849     }
10850
10851     /* This isn't really the old style, but it's close enough */
10852     if (gameInfo.resultDetails != NULL &&
10853         gameInfo.resultDetails[0] != NULLCHAR) {
10854         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10855                 gameInfo.resultDetails);
10856     } else {
10857         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10858     }
10859
10860     fclose(f);
10861     return TRUE;
10862 }
10863
10864 /* Save the current game to open file f and close the file */
10865 int
10866 SaveGame(f, dummy, dummy2)
10867      FILE *f;
10868      int dummy;
10869      char *dummy2;
10870 {
10871     if (gameMode == EditPosition) EditPositionDone(TRUE);
10872     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10873     if (appData.oldSaveStyle)
10874       return SaveGameOldStyle(f);
10875     else
10876       return SaveGamePGN(f);
10877 }
10878
10879 /* Save the current position to the given file */
10880 int
10881 SavePositionToFile(filename)
10882      char *filename;
10883 {
10884     FILE *f;
10885     char buf[MSG_SIZ];
10886
10887     if (strcmp(filename, "-") == 0) {
10888         return SavePosition(stdout, 0, NULL);
10889     } else {
10890         f = fopen(filename, "a");
10891         if (f == NULL) {
10892             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10893             DisplayError(buf, errno);
10894             return FALSE;
10895         } else {
10896             SavePosition(f, 0, NULL);
10897             return TRUE;
10898         }
10899     }
10900 }
10901
10902 /* Save the current position to the given open file and close the file */
10903 int
10904 SavePosition(f, dummy, dummy2)
10905      FILE *f;
10906      int dummy;
10907      char *dummy2;
10908 {
10909     time_t tm;
10910     char *fen;
10911     
10912     if (gameMode == EditPosition) EditPositionDone(TRUE);
10913     if (appData.oldSaveStyle) {
10914         tm = time((time_t *) NULL);
10915     
10916         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10917         PrintOpponents(f);
10918         fprintf(f, "[--------------\n");
10919         PrintPosition(f, currentMove);
10920         fprintf(f, "--------------]\n");
10921     } else {
10922         fen = PositionToFEN(currentMove, NULL);
10923         fprintf(f, "%s\n", fen);
10924         free(fen);
10925     }
10926     fclose(f);
10927     return TRUE;
10928 }
10929
10930 void
10931 ReloadCmailMsgEvent(unregister)
10932      int unregister;
10933 {
10934 #if !WIN32
10935     static char *inFilename = NULL;
10936     static char *outFilename;
10937     int i;
10938     struct stat inbuf, outbuf;
10939     int status;
10940     
10941     /* Any registered moves are unregistered if unregister is set, */
10942     /* i.e. invoked by the signal handler */
10943     if (unregister) {
10944         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10945             cmailMoveRegistered[i] = FALSE;
10946             if (cmailCommentList[i] != NULL) {
10947                 free(cmailCommentList[i]);
10948                 cmailCommentList[i] = NULL;
10949             }
10950         }
10951         nCmailMovesRegistered = 0;
10952     }
10953
10954     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10955         cmailResult[i] = CMAIL_NOT_RESULT;
10956     }
10957     nCmailResults = 0;
10958
10959     if (inFilename == NULL) {
10960         /* Because the filenames are static they only get malloced once  */
10961         /* and they never get freed                                      */
10962         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10963         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10964
10965         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10966         sprintf(outFilename, "%s.out", appData.cmailGameName);
10967     }
10968     
10969     status = stat(outFilename, &outbuf);
10970     if (status < 0) {
10971         cmailMailedMove = FALSE;
10972     } else {
10973         status = stat(inFilename, &inbuf);
10974         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10975     }
10976     
10977     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10978        counts the games, notes how each one terminated, etc.
10979        
10980        It would be nice to remove this kludge and instead gather all
10981        the information while building the game list.  (And to keep it
10982        in the game list nodes instead of having a bunch of fixed-size
10983        parallel arrays.)  Note this will require getting each game's
10984        termination from the PGN tags, as the game list builder does
10985        not process the game moves.  --mann
10986        */
10987     cmailMsgLoaded = TRUE;
10988     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10989     
10990     /* Load first game in the file or popup game menu */
10991     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10992
10993 #endif /* !WIN32 */
10994     return;
10995 }
10996
10997 int
10998 RegisterMove()
10999 {
11000     FILE *f;
11001     char string[MSG_SIZ];
11002
11003     if (   cmailMailedMove
11004         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11005         return TRUE;            /* Allow free viewing  */
11006     }
11007
11008     /* Unregister move to ensure that we don't leave RegisterMove        */
11009     /* with the move registered when the conditions for registering no   */
11010     /* longer hold                                                       */
11011     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11012         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11013         nCmailMovesRegistered --;
11014
11015         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11016           {
11017               free(cmailCommentList[lastLoadGameNumber - 1]);
11018               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11019           }
11020     }
11021
11022     if (cmailOldMove == -1) {
11023         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11024         return FALSE;
11025     }
11026
11027     if (currentMove > cmailOldMove + 1) {
11028         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11029         return FALSE;
11030     }
11031
11032     if (currentMove < cmailOldMove) {
11033         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11034         return FALSE;
11035     }
11036
11037     if (forwardMostMove > currentMove) {
11038         /* Silently truncate extra moves */
11039         TruncateGame();
11040     }
11041
11042     if (   (currentMove == cmailOldMove + 1)
11043         || (   (currentMove == cmailOldMove)
11044             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11045                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11046         if (gameInfo.result != GameUnfinished) {
11047             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11048         }
11049
11050         if (commentList[currentMove] != NULL) {
11051             cmailCommentList[lastLoadGameNumber - 1]
11052               = StrSave(commentList[currentMove]);
11053         }
11054         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11055
11056         if (appData.debugMode)
11057           fprintf(debugFP, "Saving %s for game %d\n",
11058                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11059
11060         sprintf(string,
11061                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11062         
11063         f = fopen(string, "w");
11064         if (appData.oldSaveStyle) {
11065             SaveGameOldStyle(f); /* also closes the file */
11066             
11067             sprintf(string, "%s.pos.out", appData.cmailGameName);
11068             f = fopen(string, "w");
11069             SavePosition(f, 0, NULL); /* also closes the file */
11070         } else {
11071             fprintf(f, "{--------------\n");
11072             PrintPosition(f, currentMove);
11073             fprintf(f, "--------------}\n\n");
11074             
11075             SaveGame(f, 0, NULL); /* also closes the file*/
11076         }
11077         
11078         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11079         nCmailMovesRegistered ++;
11080     } else if (nCmailGames == 1) {
11081         DisplayError(_("You have not made a move yet"), 0);
11082         return FALSE;
11083     }
11084
11085     return TRUE;
11086 }
11087
11088 void
11089 MailMoveEvent()
11090 {
11091 #if !WIN32
11092     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11093     FILE *commandOutput;
11094     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11095     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11096     int nBuffers;
11097     int i;
11098     int archived;
11099     char *arcDir;
11100
11101     if (! cmailMsgLoaded) {
11102         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11103         return;
11104     }
11105
11106     if (nCmailGames == nCmailResults) {
11107         DisplayError(_("No unfinished games"), 0);
11108         return;
11109     }
11110
11111 #if CMAIL_PROHIBIT_REMAIL
11112     if (cmailMailedMove) {
11113         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);
11114         DisplayError(msg, 0);
11115         return;
11116     }
11117 #endif
11118
11119     if (! (cmailMailedMove || RegisterMove())) return;
11120     
11121     if (   cmailMailedMove
11122         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11123         sprintf(string, partCommandString,
11124                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11125         commandOutput = popen(string, "r");
11126
11127         if (commandOutput == NULL) {
11128             DisplayError(_("Failed to invoke cmail"), 0);
11129         } else {
11130             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11131                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11132             }
11133             if (nBuffers > 1) {
11134                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11135                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11136                 nBytes = MSG_SIZ - 1;
11137             } else {
11138                 (void) memcpy(msg, buffer, nBytes);
11139             }
11140             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11141
11142             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11143                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11144
11145                 archived = TRUE;
11146                 for (i = 0; i < nCmailGames; i ++) {
11147                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11148                         archived = FALSE;
11149                     }
11150                 }
11151                 if (   archived
11152                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11153                         != NULL)) {
11154                     sprintf(buffer, "%s/%s.%s.archive",
11155                             arcDir,
11156                             appData.cmailGameName,
11157                             gameInfo.date);
11158                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11159                     cmailMsgLoaded = FALSE;
11160                 }
11161             }
11162
11163             DisplayInformation(msg);
11164             pclose(commandOutput);
11165         }
11166     } else {
11167         if ((*cmailMsg) != '\0') {
11168             DisplayInformation(cmailMsg);
11169         }
11170     }
11171
11172     return;
11173 #endif /* !WIN32 */
11174 }
11175
11176 char *
11177 CmailMsg()
11178 {
11179 #if WIN32
11180     return NULL;
11181 #else
11182     int  prependComma = 0;
11183     char number[5];
11184     char string[MSG_SIZ];       /* Space for game-list */
11185     int  i;
11186     
11187     if (!cmailMsgLoaded) return "";
11188
11189     if (cmailMailedMove) {
11190         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11191     } else {
11192         /* Create a list of games left */
11193         sprintf(string, "[");
11194         for (i = 0; i < nCmailGames; i ++) {
11195             if (! (   cmailMoveRegistered[i]
11196                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11197                 if (prependComma) {
11198                     sprintf(number, ",%d", i + 1);
11199                 } else {
11200                     sprintf(number, "%d", i + 1);
11201                     prependComma = 1;
11202                 }
11203                 
11204                 strcat(string, number);
11205             }
11206         }
11207         strcat(string, "]");
11208
11209         if (nCmailMovesRegistered + nCmailResults == 0) {
11210             switch (nCmailGames) {
11211               case 1:
11212                 sprintf(cmailMsg,
11213                         _("Still need to make move for game\n"));
11214                 break;
11215                 
11216               case 2:
11217                 sprintf(cmailMsg,
11218                         _("Still need to make moves for both games\n"));
11219                 break;
11220                 
11221               default:
11222                 sprintf(cmailMsg,
11223                         _("Still need to make moves for all %d games\n"),
11224                         nCmailGames);
11225                 break;
11226             }
11227         } else {
11228             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11229               case 1:
11230                 sprintf(cmailMsg,
11231                         _("Still need to make a move for game %s\n"),
11232                         string);
11233                 break;
11234                 
11235               case 0:
11236                 if (nCmailResults == nCmailGames) {
11237                     sprintf(cmailMsg, _("No unfinished games\n"));
11238                 } else {
11239                     sprintf(cmailMsg, _("Ready to send mail\n"));
11240                 }
11241                 break;
11242                 
11243               default:
11244                 sprintf(cmailMsg,
11245                         _("Still need to make moves for games %s\n"),
11246                         string);
11247             }
11248         }
11249     }
11250     return cmailMsg;
11251 #endif /* WIN32 */
11252 }
11253
11254 void
11255 ResetGameEvent()
11256 {
11257     if (gameMode == Training)
11258       SetTrainingModeOff();
11259
11260     Reset(TRUE, TRUE);
11261     cmailMsgLoaded = FALSE;
11262     if (appData.icsActive) {
11263       SendToICS(ics_prefix);
11264       SendToICS("refresh\n");
11265     }
11266 }
11267
11268 void
11269 ExitEvent(status)
11270      int status;
11271 {
11272     exiting++;
11273     if (exiting > 2) {
11274       /* Give up on clean exit */
11275       exit(status);
11276     }
11277     if (exiting > 1) {
11278       /* Keep trying for clean exit */
11279       return;
11280     }
11281
11282     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11283
11284     if (telnetISR != NULL) {
11285       RemoveInputSource(telnetISR);
11286     }
11287     if (icsPR != NoProc) {
11288       DestroyChildProcess(icsPR, TRUE);
11289     }
11290
11291     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11292     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11293
11294     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11295     /* make sure this other one finishes before killing it!                  */
11296     if(endingGame) { int count = 0;
11297         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11298         while(endingGame && count++ < 10) DoSleep(1);
11299         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11300     }
11301
11302     /* Kill off chess programs */
11303     if (first.pr != NoProc) {
11304         ExitAnalyzeMode();
11305         
11306         DoSleep( appData.delayBeforeQuit );
11307         SendToProgram("quit\n", &first);
11308         DoSleep( appData.delayAfterQuit );
11309         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11310     }
11311     if (second.pr != NoProc) {
11312         DoSleep( appData.delayBeforeQuit );
11313         SendToProgram("quit\n", &second);
11314         DoSleep( appData.delayAfterQuit );
11315         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11316     }
11317     if (first.isr != NULL) {
11318         RemoveInputSource(first.isr);
11319     }
11320     if (second.isr != NULL) {
11321         RemoveInputSource(second.isr);
11322     }
11323
11324     ShutDownFrontEnd();
11325     exit(status);
11326 }
11327
11328 void
11329 PauseEvent()
11330 {
11331     if (appData.debugMode)
11332         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11333     if (pausing) {
11334         pausing = FALSE;
11335         ModeHighlight();
11336         if (gameMode == MachinePlaysWhite ||
11337             gameMode == MachinePlaysBlack) {
11338             StartClocks();
11339         } else {
11340             DisplayBothClocks();
11341         }
11342         if (gameMode == PlayFromGameFile) {
11343             if (appData.timeDelay >= 0) 
11344                 AutoPlayGameLoop();
11345         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11346             Reset(FALSE, TRUE);
11347             SendToICS(ics_prefix);
11348             SendToICS("refresh\n");
11349         } else if (currentMove < forwardMostMove) {
11350             ForwardInner(forwardMostMove);
11351         }
11352         pauseExamInvalid = FALSE;
11353     } else {
11354         switch (gameMode) {
11355           default:
11356             return;
11357           case IcsExamining:
11358             pauseExamForwardMostMove = forwardMostMove;
11359             pauseExamInvalid = FALSE;
11360             /* fall through */
11361           case IcsObserving:
11362           case IcsPlayingWhite:
11363           case IcsPlayingBlack:
11364             pausing = TRUE;
11365             ModeHighlight();
11366             return;
11367           case PlayFromGameFile:
11368             (void) StopLoadGameTimer();
11369             pausing = TRUE;
11370             ModeHighlight();
11371             break;
11372           case BeginningOfGame:
11373             if (appData.icsActive) return;
11374             /* else fall through */
11375           case MachinePlaysWhite:
11376           case MachinePlaysBlack:
11377           case TwoMachinesPlay:
11378             if (forwardMostMove == 0)
11379               return;           /* don't pause if no one has moved */
11380             if ((gameMode == MachinePlaysWhite &&
11381                  !WhiteOnMove(forwardMostMove)) ||
11382                 (gameMode == MachinePlaysBlack &&
11383                  WhiteOnMove(forwardMostMove))) {
11384                 StopClocks();
11385             }
11386             pausing = TRUE;
11387             ModeHighlight();
11388             break;
11389         }
11390     }
11391 }
11392
11393 void
11394 EditCommentEvent()
11395 {
11396     char title[MSG_SIZ];
11397
11398     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11399         strcpy(title, _("Edit comment"));
11400     } else {
11401         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11402                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11403                 parseList[currentMove - 1]);
11404     }
11405
11406     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11407 }
11408
11409
11410 void
11411 EditTagsEvent()
11412 {
11413     char *tags = PGNTags(&gameInfo);
11414     EditTagsPopUp(tags);
11415     free(tags);
11416 }
11417
11418 void
11419 AnalyzeModeEvent()
11420 {
11421     if (appData.noChessProgram || gameMode == AnalyzeMode)
11422       return;
11423
11424     if (gameMode != AnalyzeFile) {
11425         if (!appData.icsEngineAnalyze) {
11426                EditGameEvent();
11427                if (gameMode != EditGame) return;
11428         }
11429         ResurrectChessProgram();
11430         SendToProgram("analyze\n", &first);
11431         first.analyzing = TRUE;
11432         /*first.maybeThinking = TRUE;*/
11433         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11434         EngineOutputPopUp();
11435     }
11436     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11437     pausing = FALSE;
11438     ModeHighlight();
11439     SetGameInfo();
11440
11441     StartAnalysisClock();
11442     GetTimeMark(&lastNodeCountTime);
11443     lastNodeCount = 0;
11444 }
11445
11446 void
11447 AnalyzeFileEvent()
11448 {
11449     if (appData.noChessProgram || gameMode == AnalyzeFile)
11450       return;
11451
11452     if (gameMode != AnalyzeMode) {
11453         EditGameEvent();
11454         if (gameMode != EditGame) return;
11455         ResurrectChessProgram();
11456         SendToProgram("analyze\n", &first);
11457         first.analyzing = TRUE;
11458         /*first.maybeThinking = TRUE;*/
11459         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11460         EngineOutputPopUp();
11461     }
11462     gameMode = AnalyzeFile;
11463     pausing = FALSE;
11464     ModeHighlight();
11465     SetGameInfo();
11466
11467     StartAnalysisClock();
11468     GetTimeMark(&lastNodeCountTime);
11469     lastNodeCount = 0;
11470 }
11471
11472 void
11473 MachineWhiteEvent()
11474 {
11475     char buf[MSG_SIZ];
11476     char *bookHit = NULL;
11477
11478     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11479       return;
11480
11481
11482     if (gameMode == PlayFromGameFile || 
11483         gameMode == TwoMachinesPlay  || 
11484         gameMode == Training         || 
11485         gameMode == AnalyzeMode      || 
11486         gameMode == EndOfGame)
11487         EditGameEvent();
11488
11489     if (gameMode == EditPosition) 
11490         EditPositionDone(TRUE);
11491
11492     if (!WhiteOnMove(currentMove)) {
11493         DisplayError(_("It is not White's turn"), 0);
11494         return;
11495     }
11496   
11497     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11498       ExitAnalyzeMode();
11499
11500     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11501         gameMode == AnalyzeFile)
11502         TruncateGame();
11503
11504     ResurrectChessProgram();    /* in case it isn't running */
11505     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11506         gameMode = MachinePlaysWhite;
11507         ResetClocks();
11508     } else
11509     gameMode = MachinePlaysWhite;
11510     pausing = FALSE;
11511     ModeHighlight();
11512     SetGameInfo();
11513     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11514     DisplayTitle(buf);
11515     if (first.sendName) {
11516       sprintf(buf, "name %s\n", gameInfo.black);
11517       SendToProgram(buf, &first);
11518     }
11519     if (first.sendTime) {
11520       if (first.useColors) {
11521         SendToProgram("black\n", &first); /*gnu kludge*/
11522       }
11523       SendTimeRemaining(&first, TRUE);
11524     }
11525     if (first.useColors) {
11526       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11527     }
11528     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11529     SetMachineThinkingEnables();
11530     first.maybeThinking = TRUE;
11531     StartClocks();
11532     firstMove = FALSE;
11533
11534     if (appData.autoFlipView && !flipView) {
11535       flipView = !flipView;
11536       DrawPosition(FALSE, NULL);
11537       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11538     }
11539
11540     if(bookHit) { // [HGM] book: simulate book reply
11541         static char bookMove[MSG_SIZ]; // a bit generous?
11542
11543         programStats.nodes = programStats.depth = programStats.time = 
11544         programStats.score = programStats.got_only_move = 0;
11545         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11546
11547         strcpy(bookMove, "move ");
11548         strcat(bookMove, bookHit);
11549         HandleMachineMove(bookMove, &first);
11550     }
11551 }
11552
11553 void
11554 MachineBlackEvent()
11555 {
11556     char buf[MSG_SIZ];
11557    char *bookHit = NULL;
11558
11559     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11560         return;
11561
11562
11563     if (gameMode == PlayFromGameFile || 
11564         gameMode == TwoMachinesPlay  || 
11565         gameMode == Training         || 
11566         gameMode == AnalyzeMode      || 
11567         gameMode == EndOfGame)
11568         EditGameEvent();
11569
11570     if (gameMode == EditPosition) 
11571         EditPositionDone(TRUE);
11572
11573     if (WhiteOnMove(currentMove)) {
11574         DisplayError(_("It is not Black's turn"), 0);
11575         return;
11576     }
11577     
11578     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11579       ExitAnalyzeMode();
11580
11581     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11582         gameMode == AnalyzeFile)
11583         TruncateGame();
11584
11585     ResurrectChessProgram();    /* in case it isn't running */
11586     gameMode = MachinePlaysBlack;
11587     pausing = FALSE;
11588     ModeHighlight();
11589     SetGameInfo();
11590     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11591     DisplayTitle(buf);
11592     if (first.sendName) {
11593       sprintf(buf, "name %s\n", gameInfo.white);
11594       SendToProgram(buf, &first);
11595     }
11596     if (first.sendTime) {
11597       if (first.useColors) {
11598         SendToProgram("white\n", &first); /*gnu kludge*/
11599       }
11600       SendTimeRemaining(&first, FALSE);
11601     }
11602     if (first.useColors) {
11603       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11604     }
11605     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11606     SetMachineThinkingEnables();
11607     first.maybeThinking = TRUE;
11608     StartClocks();
11609
11610     if (appData.autoFlipView && flipView) {
11611       flipView = !flipView;
11612       DrawPosition(FALSE, NULL);
11613       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11614     }
11615     if(bookHit) { // [HGM] book: simulate book reply
11616         static char bookMove[MSG_SIZ]; // a bit generous?
11617
11618         programStats.nodes = programStats.depth = programStats.time = 
11619         programStats.score = programStats.got_only_move = 0;
11620         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11621
11622         strcpy(bookMove, "move ");
11623         strcat(bookMove, bookHit);
11624         HandleMachineMove(bookMove, &first);
11625     }
11626 }
11627
11628
11629 void
11630 DisplayTwoMachinesTitle()
11631 {
11632     char buf[MSG_SIZ];
11633     if (appData.matchGames > 0) {
11634         if (first.twoMachinesColor[0] == 'w') {
11635             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11636                     gameInfo.white, gameInfo.black,
11637                     first.matchWins, second.matchWins,
11638                     matchGame - 1 - (first.matchWins + second.matchWins));
11639         } else {
11640             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11641                     gameInfo.white, gameInfo.black,
11642                     second.matchWins, first.matchWins,
11643                     matchGame - 1 - (first.matchWins + second.matchWins));
11644         }
11645     } else {
11646         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11647     }
11648     DisplayTitle(buf);
11649 }
11650
11651 void
11652 TwoMachinesEvent P((void))
11653 {
11654     int i;
11655     char buf[MSG_SIZ];
11656     ChessProgramState *onmove;
11657     char *bookHit = NULL;
11658     
11659     if (appData.noChessProgram) return;
11660
11661     switch (gameMode) {
11662       case TwoMachinesPlay:
11663         return;
11664       case MachinePlaysWhite:
11665       case MachinePlaysBlack:
11666         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11667             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11668             return;
11669         }
11670         /* fall through */
11671       case BeginningOfGame:
11672       case PlayFromGameFile:
11673       case EndOfGame:
11674         EditGameEvent();
11675         if (gameMode != EditGame) return;
11676         break;
11677       case EditPosition:
11678         EditPositionDone(TRUE);
11679         break;
11680       case AnalyzeMode:
11681       case AnalyzeFile:
11682         ExitAnalyzeMode();
11683         break;
11684       case EditGame:
11685       default:
11686         break;
11687     }
11688
11689 //    forwardMostMove = currentMove;
11690     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11691     ResurrectChessProgram();    /* in case first program isn't running */
11692
11693     if (second.pr == NULL) {
11694         StartChessProgram(&second);
11695         if (second.protocolVersion == 1) {
11696           TwoMachinesEventIfReady();
11697         } else {
11698           /* kludge: allow timeout for initial "feature" command */
11699           FreezeUI();
11700           DisplayMessage("", _("Starting second chess program"));
11701           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11702         }
11703         return;
11704     }
11705     DisplayMessage("", "");
11706     InitChessProgram(&second, FALSE);
11707     SendToProgram("force\n", &second);
11708     if (startedFromSetupPosition) {
11709         SendBoard(&second, backwardMostMove);
11710     if (appData.debugMode) {
11711         fprintf(debugFP, "Two Machines\n");
11712     }
11713     }
11714     for (i = backwardMostMove; i < forwardMostMove; i++) {
11715         SendMoveToProgram(i, &second);
11716     }
11717
11718     gameMode = TwoMachinesPlay;
11719     pausing = FALSE;
11720     ModeHighlight();
11721     SetGameInfo();
11722     DisplayTwoMachinesTitle();
11723     firstMove = TRUE;
11724     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11725         onmove = &first;
11726     } else {
11727         onmove = &second;
11728     }
11729
11730     SendToProgram(first.computerString, &first);
11731     if (first.sendName) {
11732       sprintf(buf, "name %s\n", second.tidy);
11733       SendToProgram(buf, &first);
11734     }
11735     SendToProgram(second.computerString, &second);
11736     if (second.sendName) {
11737       sprintf(buf, "name %s\n", first.tidy);
11738       SendToProgram(buf, &second);
11739     }
11740
11741     ResetClocks();
11742     if (!first.sendTime || !second.sendTime) {
11743         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11744         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11745     }
11746     if (onmove->sendTime) {
11747       if (onmove->useColors) {
11748         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11749       }
11750       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11751     }
11752     if (onmove->useColors) {
11753       SendToProgram(onmove->twoMachinesColor, onmove);
11754     }
11755     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11756 //    SendToProgram("go\n", onmove);
11757     onmove->maybeThinking = TRUE;
11758     SetMachineThinkingEnables();
11759
11760     StartClocks();
11761
11762     if(bookHit) { // [HGM] book: simulate book reply
11763         static char bookMove[MSG_SIZ]; // a bit generous?
11764
11765         programStats.nodes = programStats.depth = programStats.time = 
11766         programStats.score = programStats.got_only_move = 0;
11767         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11768
11769         strcpy(bookMove, "move ");
11770         strcat(bookMove, bookHit);
11771         savedMessage = bookMove; // args for deferred call
11772         savedState = onmove;
11773         ScheduleDelayedEvent(DeferredBookMove, 1);
11774     }
11775 }
11776
11777 void
11778 TrainingEvent()
11779 {
11780     if (gameMode == Training) {
11781       SetTrainingModeOff();
11782       gameMode = PlayFromGameFile;
11783       DisplayMessage("", _("Training mode off"));
11784     } else {
11785       gameMode = Training;
11786       animateTraining = appData.animate;
11787
11788       /* make sure we are not already at the end of the game */
11789       if (currentMove < forwardMostMove) {
11790         SetTrainingModeOn();
11791         DisplayMessage("", _("Training mode on"));
11792       } else {
11793         gameMode = PlayFromGameFile;
11794         DisplayError(_("Already at end of game"), 0);
11795       }
11796     }
11797     ModeHighlight();
11798 }
11799
11800 void
11801 IcsClientEvent()
11802 {
11803     if (!appData.icsActive) return;
11804     switch (gameMode) {
11805       case IcsPlayingWhite:
11806       case IcsPlayingBlack:
11807       case IcsObserving:
11808       case IcsIdle:
11809       case BeginningOfGame:
11810       case IcsExamining:
11811         return;
11812
11813       case EditGame:
11814         break;
11815
11816       case EditPosition:
11817         EditPositionDone(TRUE);
11818         break;
11819
11820       case AnalyzeMode:
11821       case AnalyzeFile:
11822         ExitAnalyzeMode();
11823         break;
11824         
11825       default:
11826         EditGameEvent();
11827         break;
11828     }
11829
11830     gameMode = IcsIdle;
11831     ModeHighlight();
11832     return;
11833 }
11834
11835
11836 void
11837 EditGameEvent()
11838 {
11839     int i;
11840
11841     switch (gameMode) {
11842       case Training:
11843         SetTrainingModeOff();
11844         break;
11845       case MachinePlaysWhite:
11846       case MachinePlaysBlack:
11847       case BeginningOfGame:
11848         SendToProgram("force\n", &first);
11849         SetUserThinkingEnables();
11850         break;
11851       case PlayFromGameFile:
11852         (void) StopLoadGameTimer();
11853         if (gameFileFP != NULL) {
11854             gameFileFP = NULL;
11855         }
11856         break;
11857       case EditPosition:
11858         EditPositionDone(TRUE);
11859         break;
11860       case AnalyzeMode:
11861       case AnalyzeFile:
11862         ExitAnalyzeMode();
11863         SendToProgram("force\n", &first);
11864         break;
11865       case TwoMachinesPlay:
11866         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11867         ResurrectChessProgram();
11868         SetUserThinkingEnables();
11869         break;
11870       case EndOfGame:
11871         ResurrectChessProgram();
11872         break;
11873       case IcsPlayingBlack:
11874       case IcsPlayingWhite:
11875         DisplayError(_("Warning: You are still playing a game"), 0);
11876         break;
11877       case IcsObserving:
11878         DisplayError(_("Warning: You are still observing a game"), 0);
11879         break;
11880       case IcsExamining:
11881         DisplayError(_("Warning: You are still examining a game"), 0);
11882         break;
11883       case IcsIdle:
11884         break;
11885       case EditGame:
11886       default:
11887         return;
11888     }
11889     
11890     pausing = FALSE;
11891     StopClocks();
11892     first.offeredDraw = second.offeredDraw = 0;
11893
11894     if (gameMode == PlayFromGameFile) {
11895         whiteTimeRemaining = timeRemaining[0][currentMove];
11896         blackTimeRemaining = timeRemaining[1][currentMove];
11897         DisplayTitle("");
11898     }
11899
11900     if (gameMode == MachinePlaysWhite ||
11901         gameMode == MachinePlaysBlack ||
11902         gameMode == TwoMachinesPlay ||
11903         gameMode == EndOfGame) {
11904         i = forwardMostMove;
11905         while (i > currentMove) {
11906             SendToProgram("undo\n", &first);
11907             i--;
11908         }
11909         whiteTimeRemaining = timeRemaining[0][currentMove];
11910         blackTimeRemaining = timeRemaining[1][currentMove];
11911         DisplayBothClocks();
11912         if (whiteFlag || blackFlag) {
11913             whiteFlag = blackFlag = 0;
11914         }
11915         DisplayTitle("");
11916     }           
11917     
11918     gameMode = EditGame;
11919     ModeHighlight();
11920     SetGameInfo();
11921 }
11922
11923
11924 void
11925 EditPositionEvent()
11926 {
11927     if (gameMode == EditPosition) {
11928         EditGameEvent();
11929         return;
11930     }
11931     
11932     EditGameEvent();
11933     if (gameMode != EditGame) return;
11934     
11935     gameMode = EditPosition;
11936     ModeHighlight();
11937     SetGameInfo();
11938     if (currentMove > 0)
11939       CopyBoard(boards[0], boards[currentMove]);
11940     
11941     blackPlaysFirst = !WhiteOnMove(currentMove);
11942     ResetClocks();
11943     currentMove = forwardMostMove = backwardMostMove = 0;
11944     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11945     DisplayMove(-1);
11946 }
11947
11948 void
11949 ExitAnalyzeMode()
11950 {
11951     /* [DM] icsEngineAnalyze - possible call from other functions */
11952     if (appData.icsEngineAnalyze) {
11953         appData.icsEngineAnalyze = FALSE;
11954
11955         DisplayMessage("",_("Close ICS engine analyze..."));
11956     }
11957     if (first.analysisSupport && first.analyzing) {
11958       SendToProgram("exit\n", &first);
11959       first.analyzing = FALSE;
11960     }
11961     thinkOutput[0] = NULLCHAR;
11962 }
11963
11964 void
11965 EditPositionDone(Boolean fakeRights)
11966 {
11967     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11968
11969     startedFromSetupPosition = TRUE;
11970     InitChessProgram(&first, FALSE);
11971     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11972       boards[0][EP_STATUS] = EP_NONE;
11973       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11974     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11975         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11976         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11977       } else boards[0][CASTLING][2] = NoRights;
11978     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11979         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11980         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11981       } else boards[0][CASTLING][5] = NoRights;
11982     }
11983     SendToProgram("force\n", &first);
11984     if (blackPlaysFirst) {
11985         strcpy(moveList[0], "");
11986         strcpy(parseList[0], "");
11987         currentMove = forwardMostMove = backwardMostMove = 1;
11988         CopyBoard(boards[1], boards[0]);
11989     } else {
11990         currentMove = forwardMostMove = backwardMostMove = 0;
11991     }
11992     SendBoard(&first, forwardMostMove);
11993     if (appData.debugMode) {
11994         fprintf(debugFP, "EditPosDone\n");
11995     }
11996     DisplayTitle("");
11997     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11998     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11999     gameMode = EditGame;
12000     ModeHighlight();
12001     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12002     ClearHighlights(); /* [AS] */
12003 }
12004
12005 /* Pause for `ms' milliseconds */
12006 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12007 void
12008 TimeDelay(ms)
12009      long ms;
12010 {
12011     TimeMark m1, m2;
12012
12013     GetTimeMark(&m1);
12014     do {
12015         GetTimeMark(&m2);
12016     } while (SubtractTimeMarks(&m2, &m1) < ms);
12017 }
12018
12019 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12020 void
12021 SendMultiLineToICS(buf)
12022      char *buf;
12023 {
12024     char temp[MSG_SIZ+1], *p;
12025     int len;
12026
12027     len = strlen(buf);
12028     if (len > MSG_SIZ)
12029       len = MSG_SIZ;
12030   
12031     strncpy(temp, buf, len);
12032     temp[len] = 0;
12033
12034     p = temp;
12035     while (*p) {
12036         if (*p == '\n' || *p == '\r')
12037           *p = ' ';
12038         ++p;
12039     }
12040
12041     strcat(temp, "\n");
12042     SendToICS(temp);
12043     SendToPlayer(temp, strlen(temp));
12044 }
12045
12046 void
12047 SetWhiteToPlayEvent()
12048 {
12049     if (gameMode == EditPosition) {
12050         blackPlaysFirst = FALSE;
12051         DisplayBothClocks();    /* works because currentMove is 0 */
12052     } else if (gameMode == IcsExamining) {
12053         SendToICS(ics_prefix);
12054         SendToICS("tomove white\n");
12055     }
12056 }
12057
12058 void
12059 SetBlackToPlayEvent()
12060 {
12061     if (gameMode == EditPosition) {
12062         blackPlaysFirst = TRUE;
12063         currentMove = 1;        /* kludge */
12064         DisplayBothClocks();
12065         currentMove = 0;
12066     } else if (gameMode == IcsExamining) {
12067         SendToICS(ics_prefix);
12068         SendToICS("tomove black\n");
12069     }
12070 }
12071
12072 void
12073 EditPositionMenuEvent(selection, x, y)
12074      ChessSquare selection;
12075      int x, y;
12076 {
12077     char buf[MSG_SIZ];
12078     ChessSquare piece = boards[0][y][x];
12079
12080     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12081
12082     switch (selection) {
12083       case ClearBoard:
12084         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12085             SendToICS(ics_prefix);
12086             SendToICS("bsetup clear\n");
12087         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12088             SendToICS(ics_prefix);
12089             SendToICS("clearboard\n");
12090         } else {
12091             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12092                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12093                 for (y = 0; y < BOARD_HEIGHT; y++) {
12094                     if (gameMode == IcsExamining) {
12095                         if (boards[currentMove][y][x] != EmptySquare) {
12096                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12097                                     AAA + x, ONE + y);
12098                             SendToICS(buf);
12099                         }
12100                     } else {
12101                         boards[0][y][x] = p;
12102                     }
12103                 }
12104             }
12105         }
12106         if (gameMode == EditPosition) {
12107             DrawPosition(FALSE, boards[0]);
12108         }
12109         break;
12110
12111       case WhitePlay:
12112         SetWhiteToPlayEvent();
12113         break;
12114
12115       case BlackPlay:
12116         SetBlackToPlayEvent();
12117         break;
12118
12119       case EmptySquare:
12120         if (gameMode == IcsExamining) {
12121             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12122             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12123             SendToICS(buf);
12124         } else {
12125             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12126                 if(x == BOARD_LEFT-2) {
12127                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12128                     boards[0][y][1] = 0;
12129                 } else
12130                 if(x == BOARD_RGHT+1) {
12131                     if(y >= gameInfo.holdingsSize) break;
12132                     boards[0][y][BOARD_WIDTH-2] = 0;
12133                 } else break;
12134             }
12135             boards[0][y][x] = EmptySquare;
12136             DrawPosition(FALSE, boards[0]);
12137         }
12138         break;
12139
12140       case PromotePiece:
12141         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12142            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12143             selection = (ChessSquare) (PROMOTED piece);
12144         } else if(piece == EmptySquare) selection = WhiteSilver;
12145         else selection = (ChessSquare)((int)piece - 1);
12146         goto defaultlabel;
12147
12148       case DemotePiece:
12149         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12150            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12151             selection = (ChessSquare) (DEMOTED piece);
12152         } else if(piece == EmptySquare) selection = BlackSilver;
12153         else selection = (ChessSquare)((int)piece + 1);       
12154         goto defaultlabel;
12155
12156       case WhiteQueen:
12157       case BlackQueen:
12158         if(gameInfo.variant == VariantShatranj ||
12159            gameInfo.variant == VariantXiangqi  ||
12160            gameInfo.variant == VariantCourier  ||
12161            gameInfo.variant == VariantMakruk     )
12162             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12163         goto defaultlabel;
12164
12165       case WhiteKing:
12166       case BlackKing:
12167         if(gameInfo.variant == VariantXiangqi)
12168             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12169         if(gameInfo.variant == VariantKnightmate)
12170             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12171       default:
12172         defaultlabel:
12173         if (gameMode == IcsExamining) {
12174             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12175             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12176                     PieceToChar(selection), AAA + x, ONE + y);
12177             SendToICS(buf);
12178         } else {
12179             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12180                 int n;
12181                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12182                     n = PieceToNumber(selection - BlackPawn);
12183                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12184                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12185                     boards[0][BOARD_HEIGHT-1-n][1]++;
12186                 } else
12187                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12188                     n = PieceToNumber(selection);
12189                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12190                     boards[0][n][BOARD_WIDTH-1] = selection;
12191                     boards[0][n][BOARD_WIDTH-2]++;
12192                 }
12193             } else
12194             boards[0][y][x] = selection;
12195             DrawPosition(TRUE, boards[0]);
12196         }
12197         break;
12198     }
12199 }
12200
12201
12202 void
12203 DropMenuEvent(selection, x, y)
12204      ChessSquare selection;
12205      int x, y;
12206 {
12207     ChessMove moveType;
12208
12209     switch (gameMode) {
12210       case IcsPlayingWhite:
12211       case MachinePlaysBlack:
12212         if (!WhiteOnMove(currentMove)) {
12213             DisplayMoveError(_("It is Black's turn"));
12214             return;
12215         }
12216         moveType = WhiteDrop;
12217         break;
12218       case IcsPlayingBlack:
12219       case MachinePlaysWhite:
12220         if (WhiteOnMove(currentMove)) {
12221             DisplayMoveError(_("It is White's turn"));
12222             return;
12223         }
12224         moveType = BlackDrop;
12225         break;
12226       case EditGame:
12227         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12228         break;
12229       default:
12230         return;
12231     }
12232
12233     if (moveType == BlackDrop && selection < BlackPawn) {
12234       selection = (ChessSquare) ((int) selection
12235                                  + (int) BlackPawn - (int) WhitePawn);
12236     }
12237     if (boards[currentMove][y][x] != EmptySquare) {
12238         DisplayMoveError(_("That square is occupied"));
12239         return;
12240     }
12241
12242     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12243 }
12244
12245 void
12246 AcceptEvent()
12247 {
12248     /* Accept a pending offer of any kind from opponent */
12249     
12250     if (appData.icsActive) {
12251         SendToICS(ics_prefix);
12252         SendToICS("accept\n");
12253     } else if (cmailMsgLoaded) {
12254         if (currentMove == cmailOldMove &&
12255             commentList[cmailOldMove] != NULL &&
12256             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12257                    "Black offers a draw" : "White offers a draw")) {
12258             TruncateGame();
12259             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12260             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12261         } else {
12262             DisplayError(_("There is no pending offer on this move"), 0);
12263             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12264         }
12265     } else {
12266         /* Not used for offers from chess program */
12267     }
12268 }
12269
12270 void
12271 DeclineEvent()
12272 {
12273     /* Decline a pending offer of any kind from opponent */
12274     
12275     if (appData.icsActive) {
12276         SendToICS(ics_prefix);
12277         SendToICS("decline\n");
12278     } else if (cmailMsgLoaded) {
12279         if (currentMove == cmailOldMove &&
12280             commentList[cmailOldMove] != NULL &&
12281             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12282                    "Black offers a draw" : "White offers a draw")) {
12283 #ifdef NOTDEF
12284             AppendComment(cmailOldMove, "Draw declined", TRUE);
12285             DisplayComment(cmailOldMove - 1, "Draw declined");
12286 #endif /*NOTDEF*/
12287         } else {
12288             DisplayError(_("There is no pending offer on this move"), 0);
12289         }
12290     } else {
12291         /* Not used for offers from chess program */
12292     }
12293 }
12294
12295 void
12296 RematchEvent()
12297 {
12298     /* Issue ICS rematch command */
12299     if (appData.icsActive) {
12300         SendToICS(ics_prefix);
12301         SendToICS("rematch\n");
12302     }
12303 }
12304
12305 void
12306 CallFlagEvent()
12307 {
12308     /* Call your opponent's flag (claim a win on time) */
12309     if (appData.icsActive) {
12310         SendToICS(ics_prefix);
12311         SendToICS("flag\n");
12312     } else {
12313         switch (gameMode) {
12314           default:
12315             return;
12316           case MachinePlaysWhite:
12317             if (whiteFlag) {
12318                 if (blackFlag)
12319                   GameEnds(GameIsDrawn, "Both players ran out of time",
12320                            GE_PLAYER);
12321                 else
12322                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12323             } else {
12324                 DisplayError(_("Your opponent is not out of time"), 0);
12325             }
12326             break;
12327           case MachinePlaysBlack:
12328             if (blackFlag) {
12329                 if (whiteFlag)
12330                   GameEnds(GameIsDrawn, "Both players ran out of time",
12331                            GE_PLAYER);
12332                 else
12333                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12334             } else {
12335                 DisplayError(_("Your opponent is not out of time"), 0);
12336             }
12337             break;
12338         }
12339     }
12340 }
12341
12342 void
12343 DrawEvent()
12344 {
12345     /* Offer draw or accept pending draw offer from opponent */
12346     
12347     if (appData.icsActive) {
12348         /* Note: tournament rules require draw offers to be
12349            made after you make your move but before you punch
12350            your clock.  Currently ICS doesn't let you do that;
12351            instead, you immediately punch your clock after making
12352            a move, but you can offer a draw at any time. */
12353         
12354         SendToICS(ics_prefix);
12355         SendToICS("draw\n");
12356         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12357     } else if (cmailMsgLoaded) {
12358         if (currentMove == cmailOldMove &&
12359             commentList[cmailOldMove] != NULL &&
12360             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12361                    "Black offers a draw" : "White offers a draw")) {
12362             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12363             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12364         } else if (currentMove == cmailOldMove + 1) {
12365             char *offer = WhiteOnMove(cmailOldMove) ?
12366               "White offers a draw" : "Black offers a draw";
12367             AppendComment(currentMove, offer, TRUE);
12368             DisplayComment(currentMove - 1, offer);
12369             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12370         } else {
12371             DisplayError(_("You must make your move before offering a draw"), 0);
12372             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12373         }
12374     } else if (first.offeredDraw) {
12375         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12376     } else {
12377         if (first.sendDrawOffers) {
12378             SendToProgram("draw\n", &first);
12379             userOfferedDraw = TRUE;
12380         }
12381     }
12382 }
12383
12384 void
12385 AdjournEvent()
12386 {
12387     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12388     
12389     if (appData.icsActive) {
12390         SendToICS(ics_prefix);
12391         SendToICS("adjourn\n");
12392     } else {
12393         /* Currently GNU Chess doesn't offer or accept Adjourns */
12394     }
12395 }
12396
12397
12398 void
12399 AbortEvent()
12400 {
12401     /* Offer Abort or accept pending Abort offer from opponent */
12402     
12403     if (appData.icsActive) {
12404         SendToICS(ics_prefix);
12405         SendToICS("abort\n");
12406     } else {
12407         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12408     }
12409 }
12410
12411 void
12412 ResignEvent()
12413 {
12414     /* Resign.  You can do this even if it's not your turn. */
12415     
12416     if (appData.icsActive) {
12417         SendToICS(ics_prefix);
12418         SendToICS("resign\n");
12419     } else {
12420         switch (gameMode) {
12421           case MachinePlaysWhite:
12422             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12423             break;
12424           case MachinePlaysBlack:
12425             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12426             break;
12427           case EditGame:
12428             if (cmailMsgLoaded) {
12429                 TruncateGame();
12430                 if (WhiteOnMove(cmailOldMove)) {
12431                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12432                 } else {
12433                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12434                 }
12435                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12436             }
12437             break;
12438           default:
12439             break;
12440         }
12441     }
12442 }
12443
12444
12445 void
12446 StopObservingEvent()
12447 {
12448     /* Stop observing current games */
12449     SendToICS(ics_prefix);
12450     SendToICS("unobserve\n");
12451 }
12452
12453 void
12454 StopExaminingEvent()
12455 {
12456     /* Stop observing current game */
12457     SendToICS(ics_prefix);
12458     SendToICS("unexamine\n");
12459 }
12460
12461 void
12462 ForwardInner(target)
12463      int target;
12464 {
12465     int limit;
12466
12467     if (appData.debugMode)
12468         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12469                 target, currentMove, forwardMostMove);
12470
12471     if (gameMode == EditPosition)
12472       return;
12473
12474     if (gameMode == PlayFromGameFile && !pausing)
12475       PauseEvent();
12476     
12477     if (gameMode == IcsExamining && pausing)
12478       limit = pauseExamForwardMostMove;
12479     else
12480       limit = forwardMostMove;
12481     
12482     if (target > limit) target = limit;
12483
12484     if (target > 0 && moveList[target - 1][0]) {
12485         int fromX, fromY, toX, toY;
12486         toX = moveList[target - 1][2] - AAA;
12487         toY = moveList[target - 1][3] - ONE;
12488         if (moveList[target - 1][1] == '@') {
12489             if (appData.highlightLastMove) {
12490                 SetHighlights(-1, -1, toX, toY);
12491             }
12492         } else {
12493             fromX = moveList[target - 1][0] - AAA;
12494             fromY = moveList[target - 1][1] - ONE;
12495             if (target == currentMove + 1) {
12496                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12497             }
12498             if (appData.highlightLastMove) {
12499                 SetHighlights(fromX, fromY, toX, toY);
12500             }
12501         }
12502     }
12503     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12504         gameMode == Training || gameMode == PlayFromGameFile || 
12505         gameMode == AnalyzeFile) {
12506         while (currentMove < target) {
12507             SendMoveToProgram(currentMove++, &first);
12508         }
12509     } else {
12510         currentMove = target;
12511     }
12512     
12513     if (gameMode == EditGame || gameMode == EndOfGame) {
12514         whiteTimeRemaining = timeRemaining[0][currentMove];
12515         blackTimeRemaining = timeRemaining[1][currentMove];
12516     }
12517     DisplayBothClocks();
12518     DisplayMove(currentMove - 1);
12519     DrawPosition(FALSE, boards[currentMove]);
12520     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12521     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12522         DisplayComment(currentMove - 1, commentList[currentMove]);
12523     }
12524 }
12525
12526
12527 void
12528 ForwardEvent()
12529 {
12530     if (gameMode == IcsExamining && !pausing) {
12531         SendToICS(ics_prefix);
12532         SendToICS("forward\n");
12533     } else {
12534         ForwardInner(currentMove + 1);
12535     }
12536 }
12537
12538 void
12539 ToEndEvent()
12540 {
12541     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12542         /* to optimze, we temporarily turn off analysis mode while we feed
12543          * the remaining moves to the engine. Otherwise we get analysis output
12544          * after each move.
12545          */ 
12546         if (first.analysisSupport) {
12547           SendToProgram("exit\nforce\n", &first);
12548           first.analyzing = FALSE;
12549         }
12550     }
12551         
12552     if (gameMode == IcsExamining && !pausing) {
12553         SendToICS(ics_prefix);
12554         SendToICS("forward 999999\n");
12555     } else {
12556         ForwardInner(forwardMostMove);
12557     }
12558
12559     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12560         /* we have fed all the moves, so reactivate analysis mode */
12561         SendToProgram("analyze\n", &first);
12562         first.analyzing = TRUE;
12563         /*first.maybeThinking = TRUE;*/
12564         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12565     }
12566 }
12567
12568 void
12569 BackwardInner(target)
12570      int target;
12571 {
12572     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12573
12574     if (appData.debugMode)
12575         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12576                 target, currentMove, forwardMostMove);
12577
12578     if (gameMode == EditPosition) return;
12579     if (currentMove <= backwardMostMove) {
12580         ClearHighlights();
12581         DrawPosition(full_redraw, boards[currentMove]);
12582         return;
12583     }
12584     if (gameMode == PlayFromGameFile && !pausing)
12585       PauseEvent();
12586     
12587     if (moveList[target][0]) {
12588         int fromX, fromY, toX, toY;
12589         toX = moveList[target][2] - AAA;
12590         toY = moveList[target][3] - ONE;
12591         if (moveList[target][1] == '@') {
12592             if (appData.highlightLastMove) {
12593                 SetHighlights(-1, -1, toX, toY);
12594             }
12595         } else {
12596             fromX = moveList[target][0] - AAA;
12597             fromY = moveList[target][1] - ONE;
12598             if (target == currentMove - 1) {
12599                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12600             }
12601             if (appData.highlightLastMove) {
12602                 SetHighlights(fromX, fromY, toX, toY);
12603             }
12604         }
12605     }
12606     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12607         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12608         while (currentMove > target) {
12609             SendToProgram("undo\n", &first);
12610             currentMove--;
12611         }
12612     } else {
12613         currentMove = target;
12614     }
12615     
12616     if (gameMode == EditGame || gameMode == EndOfGame) {
12617         whiteTimeRemaining = timeRemaining[0][currentMove];
12618         blackTimeRemaining = timeRemaining[1][currentMove];
12619     }
12620     DisplayBothClocks();
12621     DisplayMove(currentMove - 1);
12622     DrawPosition(full_redraw, boards[currentMove]);
12623     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12624     // [HGM] PV info: routine tests if comment empty
12625     DisplayComment(currentMove - 1, commentList[currentMove]);
12626 }
12627
12628 void
12629 BackwardEvent()
12630 {
12631     if (gameMode == IcsExamining && !pausing) {
12632         SendToICS(ics_prefix);
12633         SendToICS("backward\n");
12634     } else {
12635         BackwardInner(currentMove - 1);
12636     }
12637 }
12638
12639 void
12640 ToStartEvent()
12641 {
12642     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12643         /* to optimize, we temporarily turn off analysis mode while we undo
12644          * all the moves. Otherwise we get analysis output after each undo.
12645          */ 
12646         if (first.analysisSupport) {
12647           SendToProgram("exit\nforce\n", &first);
12648           first.analyzing = FALSE;
12649         }
12650     }
12651
12652     if (gameMode == IcsExamining && !pausing) {
12653         SendToICS(ics_prefix);
12654         SendToICS("backward 999999\n");
12655     } else {
12656         BackwardInner(backwardMostMove);
12657     }
12658
12659     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12660         /* we have fed all the moves, so reactivate analysis mode */
12661         SendToProgram("analyze\n", &first);
12662         first.analyzing = TRUE;
12663         /*first.maybeThinking = TRUE;*/
12664         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12665     }
12666 }
12667
12668 void
12669 ToNrEvent(int to)
12670 {
12671   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12672   if (to >= forwardMostMove) to = forwardMostMove;
12673   if (to <= backwardMostMove) to = backwardMostMove;
12674   if (to < currentMove) {
12675     BackwardInner(to);
12676   } else {
12677     ForwardInner(to);
12678   }
12679 }
12680
12681 void
12682 RevertEvent(Boolean annotate)
12683 {
12684     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12685         return;
12686     }
12687     if (gameMode != IcsExamining) {
12688         DisplayError(_("You are not examining a game"), 0);
12689         return;
12690     }
12691     if (pausing) {
12692         DisplayError(_("You can't revert while pausing"), 0);
12693         return;
12694     }
12695     SendToICS(ics_prefix);
12696     SendToICS("revert\n");
12697 }
12698
12699 void
12700 RetractMoveEvent()
12701 {
12702     switch (gameMode) {
12703       case MachinePlaysWhite:
12704       case MachinePlaysBlack:
12705         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12706             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12707             return;
12708         }
12709         if (forwardMostMove < 2) return;
12710         currentMove = forwardMostMove = forwardMostMove - 2;
12711         whiteTimeRemaining = timeRemaining[0][currentMove];
12712         blackTimeRemaining = timeRemaining[1][currentMove];
12713         DisplayBothClocks();
12714         DisplayMove(currentMove - 1);
12715         ClearHighlights();/*!! could figure this out*/
12716         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12717         SendToProgram("remove\n", &first);
12718         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12719         break;
12720
12721       case BeginningOfGame:
12722       default:
12723         break;
12724
12725       case IcsPlayingWhite:
12726       case IcsPlayingBlack:
12727         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12728             SendToICS(ics_prefix);
12729             SendToICS("takeback 2\n");
12730         } else {
12731             SendToICS(ics_prefix);
12732             SendToICS("takeback 1\n");
12733         }
12734         break;
12735     }
12736 }
12737
12738 void
12739 MoveNowEvent()
12740 {
12741     ChessProgramState *cps;
12742
12743     switch (gameMode) {
12744       case MachinePlaysWhite:
12745         if (!WhiteOnMove(forwardMostMove)) {
12746             DisplayError(_("It is your turn"), 0);
12747             return;
12748         }
12749         cps = &first;
12750         break;
12751       case MachinePlaysBlack:
12752         if (WhiteOnMove(forwardMostMove)) {
12753             DisplayError(_("It is your turn"), 0);
12754             return;
12755         }
12756         cps = &first;
12757         break;
12758       case TwoMachinesPlay:
12759         if (WhiteOnMove(forwardMostMove) ==
12760             (first.twoMachinesColor[0] == 'w')) {
12761             cps = &first;
12762         } else {
12763             cps = &second;
12764         }
12765         break;
12766       case BeginningOfGame:
12767       default:
12768         return;
12769     }
12770     SendToProgram("?\n", cps);
12771 }
12772
12773 void
12774 TruncateGameEvent()
12775 {
12776     EditGameEvent();
12777     if (gameMode != EditGame) return;
12778     TruncateGame();
12779 }
12780
12781 void
12782 TruncateGame()
12783 {
12784     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12785     if (forwardMostMove > currentMove) {
12786         if (gameInfo.resultDetails != NULL) {
12787             free(gameInfo.resultDetails);
12788             gameInfo.resultDetails = NULL;
12789             gameInfo.result = GameUnfinished;
12790         }
12791         forwardMostMove = currentMove;
12792         HistorySet(parseList, backwardMostMove, forwardMostMove,
12793                    currentMove-1);
12794     }
12795 }
12796
12797 void
12798 HintEvent()
12799 {
12800     if (appData.noChessProgram) return;
12801     switch (gameMode) {
12802       case MachinePlaysWhite:
12803         if (WhiteOnMove(forwardMostMove)) {
12804             DisplayError(_("Wait until your turn"), 0);
12805             return;
12806         }
12807         break;
12808       case BeginningOfGame:
12809       case MachinePlaysBlack:
12810         if (!WhiteOnMove(forwardMostMove)) {
12811             DisplayError(_("Wait until your turn"), 0);
12812             return;
12813         }
12814         break;
12815       default:
12816         DisplayError(_("No hint available"), 0);
12817         return;
12818     }
12819     SendToProgram("hint\n", &first);
12820     hintRequested = TRUE;
12821 }
12822
12823 void
12824 BookEvent()
12825 {
12826     if (appData.noChessProgram) return;
12827     switch (gameMode) {
12828       case MachinePlaysWhite:
12829         if (WhiteOnMove(forwardMostMove)) {
12830             DisplayError(_("Wait until your turn"), 0);
12831             return;
12832         }
12833         break;
12834       case BeginningOfGame:
12835       case MachinePlaysBlack:
12836         if (!WhiteOnMove(forwardMostMove)) {
12837             DisplayError(_("Wait until your turn"), 0);
12838             return;
12839         }
12840         break;
12841       case EditPosition:
12842         EditPositionDone(TRUE);
12843         break;
12844       case TwoMachinesPlay:
12845         return;
12846       default:
12847         break;
12848     }
12849     SendToProgram("bk\n", &first);
12850     bookOutput[0] = NULLCHAR;
12851     bookRequested = TRUE;
12852 }
12853
12854 void
12855 AboutGameEvent()
12856 {
12857     char *tags = PGNTags(&gameInfo);
12858     TagsPopUp(tags, CmailMsg());
12859     free(tags);
12860 }
12861
12862 /* end button procedures */
12863
12864 void
12865 PrintPosition(fp, move)
12866      FILE *fp;
12867      int move;
12868 {
12869     int i, j;
12870     
12871     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12872         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12873             char c = PieceToChar(boards[move][i][j]);
12874             fputc(c == 'x' ? '.' : c, fp);
12875             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12876         }
12877     }
12878     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12879       fprintf(fp, "white to play\n");
12880     else
12881       fprintf(fp, "black to play\n");
12882 }
12883
12884 void
12885 PrintOpponents(fp)
12886      FILE *fp;
12887 {
12888     if (gameInfo.white != NULL) {
12889         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12890     } else {
12891         fprintf(fp, "\n");
12892     }
12893 }
12894
12895 /* Find last component of program's own name, using some heuristics */
12896 void
12897 TidyProgramName(prog, host, buf)
12898      char *prog, *host, buf[MSG_SIZ];
12899 {
12900     char *p, *q;
12901     int local = (strcmp(host, "localhost") == 0);
12902     while (!local && (p = strchr(prog, ';')) != NULL) {
12903         p++;
12904         while (*p == ' ') p++;
12905         prog = p;
12906     }
12907     if (*prog == '"' || *prog == '\'') {
12908         q = strchr(prog + 1, *prog);
12909     } else {
12910         q = strchr(prog, ' ');
12911     }
12912     if (q == NULL) q = prog + strlen(prog);
12913     p = q;
12914     while (p >= prog && *p != '/' && *p != '\\') p--;
12915     p++;
12916     if(p == prog && *p == '"') p++;
12917     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12918     memcpy(buf, p, q - p);
12919     buf[q - p] = NULLCHAR;
12920     if (!local) {
12921         strcat(buf, "@");
12922         strcat(buf, host);
12923     }
12924 }
12925
12926 char *
12927 TimeControlTagValue()
12928 {
12929     char buf[MSG_SIZ];
12930     if (!appData.clockMode) {
12931         strcpy(buf, "-");
12932     } else if (movesPerSession > 0) {
12933         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12934     } else if (timeIncrement == 0) {
12935         sprintf(buf, "%ld", timeControl/1000);
12936     } else {
12937         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12938     }
12939     return StrSave(buf);
12940 }
12941
12942 void
12943 SetGameInfo()
12944 {
12945     /* This routine is used only for certain modes */
12946     VariantClass v = gameInfo.variant;
12947     ChessMove r = GameUnfinished;
12948     char *p = NULL;
12949
12950     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12951         r = gameInfo.result; 
12952         p = gameInfo.resultDetails; 
12953         gameInfo.resultDetails = NULL;
12954     }
12955     ClearGameInfo(&gameInfo);
12956     gameInfo.variant = v;
12957
12958     switch (gameMode) {
12959       case MachinePlaysWhite:
12960         gameInfo.event = StrSave( appData.pgnEventHeader );
12961         gameInfo.site = StrSave(HostName());
12962         gameInfo.date = PGNDate();
12963         gameInfo.round = StrSave("-");
12964         gameInfo.white = StrSave(first.tidy);
12965         gameInfo.black = StrSave(UserName());
12966         gameInfo.timeControl = TimeControlTagValue();
12967         break;
12968
12969       case MachinePlaysBlack:
12970         gameInfo.event = StrSave( appData.pgnEventHeader );
12971         gameInfo.site = StrSave(HostName());
12972         gameInfo.date = PGNDate();
12973         gameInfo.round = StrSave("-");
12974         gameInfo.white = StrSave(UserName());
12975         gameInfo.black = StrSave(first.tidy);
12976         gameInfo.timeControl = TimeControlTagValue();
12977         break;
12978
12979       case TwoMachinesPlay:
12980         gameInfo.event = StrSave( appData.pgnEventHeader );
12981         gameInfo.site = StrSave(HostName());
12982         gameInfo.date = PGNDate();
12983         if (matchGame > 0) {
12984             char buf[MSG_SIZ];
12985             sprintf(buf, "%d", matchGame);
12986             gameInfo.round = StrSave(buf);
12987         } else {
12988             gameInfo.round = StrSave("-");
12989         }
12990         if (first.twoMachinesColor[0] == 'w') {
12991             gameInfo.white = StrSave(first.tidy);
12992             gameInfo.black = StrSave(second.tidy);
12993         } else {
12994             gameInfo.white = StrSave(second.tidy);
12995             gameInfo.black = StrSave(first.tidy);
12996         }
12997         gameInfo.timeControl = TimeControlTagValue();
12998         break;
12999
13000       case EditGame:
13001         gameInfo.event = StrSave("Edited game");
13002         gameInfo.site = StrSave(HostName());
13003         gameInfo.date = PGNDate();
13004         gameInfo.round = StrSave("-");
13005         gameInfo.white = StrSave("-");
13006         gameInfo.black = StrSave("-");
13007         gameInfo.result = r;
13008         gameInfo.resultDetails = p;
13009         break;
13010
13011       case EditPosition:
13012         gameInfo.event = StrSave("Edited position");
13013         gameInfo.site = StrSave(HostName());
13014         gameInfo.date = PGNDate();
13015         gameInfo.round = StrSave("-");
13016         gameInfo.white = StrSave("-");
13017         gameInfo.black = StrSave("-");
13018         break;
13019
13020       case IcsPlayingWhite:
13021       case IcsPlayingBlack:
13022       case IcsObserving:
13023       case IcsExamining:
13024         break;
13025
13026       case PlayFromGameFile:
13027         gameInfo.event = StrSave("Game from non-PGN file");
13028         gameInfo.site = StrSave(HostName());
13029         gameInfo.date = PGNDate();
13030         gameInfo.round = StrSave("-");
13031         gameInfo.white = StrSave("?");
13032         gameInfo.black = StrSave("?");
13033         break;
13034
13035       default:
13036         break;
13037     }
13038 }
13039
13040 void
13041 ReplaceComment(index, text)
13042      int index;
13043      char *text;
13044 {
13045     int len;
13046
13047     while (*text == '\n') text++;
13048     len = strlen(text);
13049     while (len > 0 && text[len - 1] == '\n') len--;
13050
13051     if (commentList[index] != NULL)
13052       free(commentList[index]);
13053
13054     if (len == 0) {
13055         commentList[index] = NULL;
13056         return;
13057     }
13058   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13059       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13060       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13061     commentList[index] = (char *) malloc(len + 2);
13062     strncpy(commentList[index], text, len);
13063     commentList[index][len] = '\n';
13064     commentList[index][len + 1] = NULLCHAR;
13065   } else { 
13066     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13067     char *p;
13068     commentList[index] = (char *) malloc(len + 6);
13069     strcpy(commentList[index], "{\n");
13070     strncpy(commentList[index]+2, text, len);
13071     commentList[index][len+2] = NULLCHAR;
13072     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13073     strcat(commentList[index], "\n}\n");
13074   }
13075 }
13076
13077 void
13078 CrushCRs(text)
13079      char *text;
13080 {
13081   char *p = text;
13082   char *q = text;
13083   char ch;
13084
13085   do {
13086     ch = *p++;
13087     if (ch == '\r') continue;
13088     *q++ = ch;
13089   } while (ch != '\0');
13090 }
13091
13092 void
13093 AppendComment(index, text, addBraces)
13094      int index;
13095      char *text;
13096      Boolean addBraces; // [HGM] braces: tells if we should add {}
13097 {
13098     int oldlen, len;
13099     char *old;
13100
13101 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13102     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13103
13104     CrushCRs(text);
13105     while (*text == '\n') text++;
13106     len = strlen(text);
13107     while (len > 0 && text[len - 1] == '\n') len--;
13108
13109     if (len == 0) return;
13110
13111     if (commentList[index] != NULL) {
13112         old = commentList[index];
13113         oldlen = strlen(old);
13114         while(commentList[index][oldlen-1] ==  '\n')
13115           commentList[index][--oldlen] = NULLCHAR;
13116         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13117         strcpy(commentList[index], old);
13118         free(old);
13119         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13120         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13121           if(addBraces) addBraces = FALSE; else { text++; len--; }
13122           while (*text == '\n') { text++; len--; }
13123           commentList[index][--oldlen] = NULLCHAR;
13124       }
13125         if(addBraces) strcat(commentList[index], "\n{\n");
13126         else          strcat(commentList[index], "\n");
13127         strcat(commentList[index], text);
13128         if(addBraces) strcat(commentList[index], "\n}\n");
13129         else          strcat(commentList[index], "\n");
13130     } else {
13131         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13132         if(addBraces)
13133              strcpy(commentList[index], "{\n");
13134         else commentList[index][0] = NULLCHAR;
13135         strcat(commentList[index], text);
13136         strcat(commentList[index], "\n");
13137         if(addBraces) strcat(commentList[index], "}\n");
13138     }
13139 }
13140
13141 static char * FindStr( char * text, char * sub_text )
13142 {
13143     char * result = strstr( text, sub_text );
13144
13145     if( result != NULL ) {
13146         result += strlen( sub_text );
13147     }
13148
13149     return result;
13150 }
13151
13152 /* [AS] Try to extract PV info from PGN comment */
13153 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13154 char *GetInfoFromComment( int index, char * text )
13155 {
13156     char * sep = text;
13157
13158     if( text != NULL && index > 0 ) {
13159         int score = 0;
13160         int depth = 0;
13161         int time = -1, sec = 0, deci;
13162         char * s_eval = FindStr( text, "[%eval " );
13163         char * s_emt = FindStr( text, "[%emt " );
13164
13165         if( s_eval != NULL || s_emt != NULL ) {
13166             /* New style */
13167             char delim;
13168
13169             if( s_eval != NULL ) {
13170                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13171                     return text;
13172                 }
13173
13174                 if( delim != ']' ) {
13175                     return text;
13176                 }
13177             }
13178
13179             if( s_emt != NULL ) {
13180             }
13181                 return text;
13182         }
13183         else {
13184             /* We expect something like: [+|-]nnn.nn/dd */
13185             int score_lo = 0;
13186
13187             if(*text != '{') return text; // [HGM] braces: must be normal comment
13188
13189             sep = strchr( text, '/' );
13190             if( sep == NULL || sep < (text+4) ) {
13191                 return text;
13192             }
13193
13194             time = -1; sec = -1; deci = -1;
13195             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13196                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13197                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13198                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13199                 return text;
13200             }
13201
13202             if( score_lo < 0 || score_lo >= 100 ) {
13203                 return text;
13204             }
13205
13206             if(sec >= 0) time = 600*time + 10*sec; else
13207             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13208
13209             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13210
13211             /* [HGM] PV time: now locate end of PV info */
13212             while( *++sep >= '0' && *sep <= '9'); // strip depth
13213             if(time >= 0)
13214             while( *++sep >= '0' && *sep <= '9'); // strip time
13215             if(sec >= 0)
13216             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13217             if(deci >= 0)
13218             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13219             while(*sep == ' ') sep++;
13220         }
13221
13222         if( depth <= 0 ) {
13223             return text;
13224         }
13225
13226         if( time < 0 ) {
13227             time = -1;
13228         }
13229
13230         pvInfoList[index-1].depth = depth;
13231         pvInfoList[index-1].score = score;
13232         pvInfoList[index-1].time  = 10*time; // centi-sec
13233         if(*sep == '}') *sep = 0; else *--sep = '{';
13234     }
13235     return sep;
13236 }
13237
13238 void
13239 SendToProgram(message, cps)
13240      char *message;
13241      ChessProgramState *cps;
13242 {
13243     int count, outCount, error;
13244     char buf[MSG_SIZ];
13245
13246     if (cps->pr == NULL) return;
13247     Attention(cps);
13248     
13249     if (appData.debugMode) {
13250         TimeMark now;
13251         GetTimeMark(&now);
13252         fprintf(debugFP, "%ld >%-6s: %s", 
13253                 SubtractTimeMarks(&now, &programStartTime),
13254                 cps->which, message);
13255     }
13256     
13257     count = strlen(message);
13258     outCount = OutputToProcess(cps->pr, message, count, &error);
13259     if (outCount < count && !exiting 
13260                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13261         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13262         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13263             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13264                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13265                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13266             } else {
13267                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13268             }
13269             gameInfo.resultDetails = StrSave(buf);
13270         }
13271         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13272     }
13273 }
13274
13275 void
13276 ReceiveFromProgram(isr, closure, message, count, error)
13277      InputSourceRef isr;
13278      VOIDSTAR closure;
13279      char *message;
13280      int count;
13281      int error;
13282 {
13283     char *end_str;
13284     char buf[MSG_SIZ];
13285     ChessProgramState *cps = (ChessProgramState *)closure;
13286
13287     if (isr != cps->isr) return; /* Killed intentionally */
13288     if (count <= 0) {
13289         if (count == 0) {
13290             sprintf(buf,
13291                     _("Error: %s chess program (%s) exited unexpectedly"),
13292                     cps->which, cps->program);
13293         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13294                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13295                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13296                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13297                 } else {
13298                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13299                 }
13300                 gameInfo.resultDetails = StrSave(buf);
13301             }
13302             RemoveInputSource(cps->isr);
13303             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13304         } else {
13305             sprintf(buf,
13306                     _("Error reading from %s chess program (%s)"),
13307                     cps->which, cps->program);
13308             RemoveInputSource(cps->isr);
13309
13310             /* [AS] Program is misbehaving badly... kill it */
13311             if( count == -2 ) {
13312                 DestroyChildProcess( cps->pr, 9 );
13313                 cps->pr = NoProc;
13314             }
13315
13316             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13317         }
13318         return;
13319     }
13320     
13321     if ((end_str = strchr(message, '\r')) != NULL)
13322       *end_str = NULLCHAR;
13323     if ((end_str = strchr(message, '\n')) != NULL)
13324       *end_str = NULLCHAR;
13325     
13326     if (appData.debugMode) {
13327         TimeMark now; int print = 1;
13328         char *quote = ""; char c; int i;
13329
13330         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13331                 char start = message[0];
13332                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13333                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13334                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13335                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13336                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13337                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13338                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13339                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13340                         { quote = "# "; print = (appData.engineComments == 2); }
13341                 message[0] = start; // restore original message
13342         }
13343         if(print) {
13344                 GetTimeMark(&now);
13345                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13346                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13347                         quote,
13348                         message);
13349         }
13350     }
13351
13352     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13353     if (appData.icsEngineAnalyze) {
13354         if (strstr(message, "whisper") != NULL ||
13355              strstr(message, "kibitz") != NULL || 
13356             strstr(message, "tellics") != NULL) return;
13357     }
13358
13359     HandleMachineMove(message, cps);
13360 }
13361
13362
13363 void
13364 SendTimeControl(cps, mps, tc, inc, sd, st)
13365      ChessProgramState *cps;
13366      int mps, inc, sd, st;
13367      long tc;
13368 {
13369     char buf[MSG_SIZ];
13370     int seconds;
13371
13372     if( timeControl_2 > 0 ) {
13373         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13374             tc = timeControl_2;
13375         }
13376     }
13377     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13378     inc /= cps->timeOdds;
13379     st  /= cps->timeOdds;
13380
13381     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13382
13383     if (st > 0) {
13384       /* Set exact time per move, normally using st command */
13385       if (cps->stKludge) {
13386         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13387         seconds = st % 60;
13388         if (seconds == 0) {
13389           sprintf(buf, "level 1 %d\n", st/60);
13390         } else {
13391           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13392         }
13393       } else {
13394         sprintf(buf, "st %d\n", st);
13395       }
13396     } else {
13397       /* Set conventional or incremental time control, using level command */
13398       if (seconds == 0) {
13399         /* Note old gnuchess bug -- minutes:seconds used to not work.
13400            Fixed in later versions, but still avoid :seconds
13401            when seconds is 0. */
13402         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13403       } else {
13404         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13405                 seconds, inc/1000);
13406       }
13407     }
13408     SendToProgram(buf, cps);
13409
13410     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13411     /* Orthogonally, limit search to given depth */
13412     if (sd > 0) {
13413       if (cps->sdKludge) {
13414         sprintf(buf, "depth\n%d\n", sd);
13415       } else {
13416         sprintf(buf, "sd %d\n", sd);
13417       }
13418       SendToProgram(buf, cps);
13419     }
13420
13421     if(cps->nps > 0) { /* [HGM] nps */
13422         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13423         else {
13424                 sprintf(buf, "nps %d\n", cps->nps);
13425               SendToProgram(buf, cps);
13426         }
13427     }
13428 }
13429
13430 ChessProgramState *WhitePlayer()
13431 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13432 {
13433     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13434        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13435         return &second;
13436     return &first;
13437 }
13438
13439 void
13440 SendTimeRemaining(cps, machineWhite)
13441      ChessProgramState *cps;
13442      int /*boolean*/ machineWhite;
13443 {
13444     char message[MSG_SIZ];
13445     long time, otime;
13446
13447     /* Note: this routine must be called when the clocks are stopped
13448        or when they have *just* been set or switched; otherwise
13449        it will be off by the time since the current tick started.
13450     */
13451     if (machineWhite) {
13452         time = whiteTimeRemaining / 10;
13453         otime = blackTimeRemaining / 10;
13454     } else {
13455         time = blackTimeRemaining / 10;
13456         otime = whiteTimeRemaining / 10;
13457     }
13458     /* [HGM] translate opponent's time by time-odds factor */
13459     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13460     if (appData.debugMode) {
13461         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13462     }
13463
13464     if (time <= 0) time = 1;
13465     if (otime <= 0) otime = 1;
13466     
13467     sprintf(message, "time %ld\n", time);
13468     SendToProgram(message, cps);
13469
13470     sprintf(message, "otim %ld\n", otime);
13471     SendToProgram(message, cps);
13472 }
13473
13474 int
13475 BoolFeature(p, name, loc, cps)
13476      char **p;
13477      char *name;
13478      int *loc;
13479      ChessProgramState *cps;
13480 {
13481   char buf[MSG_SIZ];
13482   int len = strlen(name);
13483   int val;
13484   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13485     (*p) += len + 1;
13486     sscanf(*p, "%d", &val);
13487     *loc = (val != 0);
13488     while (**p && **p != ' ') (*p)++;
13489     sprintf(buf, "accepted %s\n", name);
13490     SendToProgram(buf, cps);
13491     return TRUE;
13492   }
13493   return FALSE;
13494 }
13495
13496 int
13497 IntFeature(p, name, loc, cps)
13498      char **p;
13499      char *name;
13500      int *loc;
13501      ChessProgramState *cps;
13502 {
13503   char buf[MSG_SIZ];
13504   int len = strlen(name);
13505   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13506     (*p) += len + 1;
13507     sscanf(*p, "%d", loc);
13508     while (**p && **p != ' ') (*p)++;
13509     sprintf(buf, "accepted %s\n", name);
13510     SendToProgram(buf, cps);
13511     return TRUE;
13512   }
13513   return FALSE;
13514 }
13515
13516 int
13517 StringFeature(p, name, loc, cps)
13518      char **p;
13519      char *name;
13520      char loc[];
13521      ChessProgramState *cps;
13522 {
13523   char buf[MSG_SIZ];
13524   int len = strlen(name);
13525   if (strncmp((*p), name, len) == 0
13526       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13527     (*p) += len + 2;
13528     sscanf(*p, "%[^\"]", loc);
13529     while (**p && **p != '\"') (*p)++;
13530     if (**p == '\"') (*p)++;
13531     sprintf(buf, "accepted %s\n", name);
13532     SendToProgram(buf, cps);
13533     return TRUE;
13534   }
13535   return FALSE;
13536 }
13537
13538 int 
13539 ParseOption(Option *opt, ChessProgramState *cps)
13540 // [HGM] options: process the string that defines an engine option, and determine
13541 // name, type, default value, and allowed value range
13542 {
13543         char *p, *q, buf[MSG_SIZ];
13544         int n, min = (-1)<<31, max = 1<<31, def;
13545
13546         if(p = strstr(opt->name, " -spin ")) {
13547             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13548             if(max < min) max = min; // enforce consistency
13549             if(def < min) def = min;
13550             if(def > max) def = max;
13551             opt->value = def;
13552             opt->min = min;
13553             opt->max = max;
13554             opt->type = Spin;
13555         } else if((p = strstr(opt->name, " -slider "))) {
13556             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13557             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13558             if(max < min) max = min; // enforce consistency
13559             if(def < min) def = min;
13560             if(def > max) def = max;
13561             opt->value = def;
13562             opt->min = min;
13563             opt->max = max;
13564             opt->type = Spin; // Slider;
13565         } else if((p = strstr(opt->name, " -string "))) {
13566             opt->textValue = p+9;
13567             opt->type = TextBox;
13568         } else if((p = strstr(opt->name, " -file "))) {
13569             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13570             opt->textValue = p+7;
13571             opt->type = TextBox; // FileName;
13572         } else if((p = strstr(opt->name, " -path "))) {
13573             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13574             opt->textValue = p+7;
13575             opt->type = TextBox; // PathName;
13576         } else if(p = strstr(opt->name, " -check ")) {
13577             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13578             opt->value = (def != 0);
13579             opt->type = CheckBox;
13580         } else if(p = strstr(opt->name, " -combo ")) {
13581             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13582             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13583             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13584             opt->value = n = 0;
13585             while(q = StrStr(q, " /// ")) {
13586                 n++; *q = 0;    // count choices, and null-terminate each of them
13587                 q += 5;
13588                 if(*q == '*') { // remember default, which is marked with * prefix
13589                     q++;
13590                     opt->value = n;
13591                 }
13592                 cps->comboList[cps->comboCnt++] = q;
13593             }
13594             cps->comboList[cps->comboCnt++] = NULL;
13595             opt->max = n + 1;
13596             opt->type = ComboBox;
13597         } else if(p = strstr(opt->name, " -button")) {
13598             opt->type = Button;
13599         } else if(p = strstr(opt->name, " -save")) {
13600             opt->type = SaveButton;
13601         } else return FALSE;
13602         *p = 0; // terminate option name
13603         // now look if the command-line options define a setting for this engine option.
13604         if(cps->optionSettings && cps->optionSettings[0])
13605             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13606         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13607                 sprintf(buf, "option %s", p);
13608                 if(p = strstr(buf, ",")) *p = 0;
13609                 strcat(buf, "\n");
13610                 SendToProgram(buf, cps);
13611         }
13612         return TRUE;
13613 }
13614
13615 void
13616 FeatureDone(cps, val)
13617      ChessProgramState* cps;
13618      int val;
13619 {
13620   DelayedEventCallback cb = GetDelayedEvent();
13621   if ((cb == InitBackEnd3 && cps == &first) ||
13622       (cb == TwoMachinesEventIfReady && cps == &second)) {
13623     CancelDelayedEvent();
13624     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13625   }
13626   cps->initDone = val;
13627 }
13628
13629 /* Parse feature command from engine */
13630 void
13631 ParseFeatures(args, cps)
13632      char* args;
13633      ChessProgramState *cps;  
13634 {
13635   char *p = args;
13636   char *q;
13637   int val;
13638   char buf[MSG_SIZ];
13639
13640   for (;;) {
13641     while (*p == ' ') p++;
13642     if (*p == NULLCHAR) return;
13643
13644     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13645     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13646     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13647     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13648     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13649     if (BoolFeature(&p, "reuse", &val, cps)) {
13650       /* Engine can disable reuse, but can't enable it if user said no */
13651       if (!val) cps->reuse = FALSE;
13652       continue;
13653     }
13654     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13655     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13656       if (gameMode == TwoMachinesPlay) {
13657         DisplayTwoMachinesTitle();
13658       } else {
13659         DisplayTitle("");
13660       }
13661       continue;
13662     }
13663     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13664     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13665     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13666     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13667     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13668     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13669     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13670     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13671     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13672     if (IntFeature(&p, "done", &val, cps)) {
13673       FeatureDone(cps, val);
13674       continue;
13675     }
13676     /* Added by Tord: */
13677     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13678     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13679     /* End of additions by Tord */
13680
13681     /* [HGM] added features: */
13682     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13683     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13684     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13685     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13686     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13687     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13688     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13689         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13690             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13691             SendToProgram(buf, cps);
13692             continue;
13693         }
13694         if(cps->nrOptions >= MAX_OPTIONS) {
13695             cps->nrOptions--;
13696             sprintf(buf, "%s engine has too many options\n", cps->which);
13697             DisplayError(buf, 0);
13698         }
13699         continue;
13700     }
13701     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13702     /* End of additions by HGM */
13703
13704     /* unknown feature: complain and skip */
13705     q = p;
13706     while (*q && *q != '=') q++;
13707     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13708     SendToProgram(buf, cps);
13709     p = q;
13710     if (*p == '=') {
13711       p++;
13712       if (*p == '\"') {
13713         p++;
13714         while (*p && *p != '\"') p++;
13715         if (*p == '\"') p++;
13716       } else {
13717         while (*p && *p != ' ') p++;
13718       }
13719     }
13720   }
13721
13722 }
13723
13724 void
13725 PeriodicUpdatesEvent(newState)
13726      int newState;
13727 {
13728     if (newState == appData.periodicUpdates)
13729       return;
13730
13731     appData.periodicUpdates=newState;
13732
13733     /* Display type changes, so update it now */
13734 //    DisplayAnalysis();
13735
13736     /* Get the ball rolling again... */
13737     if (newState) {
13738         AnalysisPeriodicEvent(1);
13739         StartAnalysisClock();
13740     }
13741 }
13742
13743 void
13744 PonderNextMoveEvent(newState)
13745      int newState;
13746 {
13747     if (newState == appData.ponderNextMove) return;
13748     if (gameMode == EditPosition) EditPositionDone(TRUE);
13749     if (newState) {
13750         SendToProgram("hard\n", &first);
13751         if (gameMode == TwoMachinesPlay) {
13752             SendToProgram("hard\n", &second);
13753         }
13754     } else {
13755         SendToProgram("easy\n", &first);
13756         thinkOutput[0] = NULLCHAR;
13757         if (gameMode == TwoMachinesPlay) {
13758             SendToProgram("easy\n", &second);
13759         }
13760     }
13761     appData.ponderNextMove = newState;
13762 }
13763
13764 void
13765 NewSettingEvent(option, command, value)
13766      char *command;
13767      int option, value;
13768 {
13769     char buf[MSG_SIZ];
13770
13771     if (gameMode == EditPosition) EditPositionDone(TRUE);
13772     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13773     SendToProgram(buf, &first);
13774     if (gameMode == TwoMachinesPlay) {
13775         SendToProgram(buf, &second);
13776     }
13777 }
13778
13779 void
13780 ShowThinkingEvent()
13781 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13782 {
13783     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13784     int newState = appData.showThinking
13785         // [HGM] thinking: other features now need thinking output as well
13786         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13787     
13788     if (oldState == newState) return;
13789     oldState = newState;
13790     if (gameMode == EditPosition) EditPositionDone(TRUE);
13791     if (oldState) {
13792         SendToProgram("post\n", &first);
13793         if (gameMode == TwoMachinesPlay) {
13794             SendToProgram("post\n", &second);
13795         }
13796     } else {
13797         SendToProgram("nopost\n", &first);
13798         thinkOutput[0] = NULLCHAR;
13799         if (gameMode == TwoMachinesPlay) {
13800             SendToProgram("nopost\n", &second);
13801         }
13802     }
13803 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13804 }
13805
13806 void
13807 AskQuestionEvent(title, question, replyPrefix, which)
13808      char *title; char *question; char *replyPrefix; char *which;
13809 {
13810   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13811   if (pr == NoProc) return;
13812   AskQuestion(title, question, replyPrefix, pr);
13813 }
13814
13815 void
13816 DisplayMove(moveNumber)
13817      int moveNumber;
13818 {
13819     char message[MSG_SIZ];
13820     char res[MSG_SIZ];
13821     char cpThinkOutput[MSG_SIZ];
13822
13823     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13824     
13825     if (moveNumber == forwardMostMove - 1 || 
13826         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13827
13828         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13829
13830         if (strchr(cpThinkOutput, '\n')) {
13831             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13832         }
13833     } else {
13834         *cpThinkOutput = NULLCHAR;
13835     }
13836
13837     /* [AS] Hide thinking from human user */
13838     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13839         *cpThinkOutput = NULLCHAR;
13840         if( thinkOutput[0] != NULLCHAR ) {
13841             int i;
13842
13843             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13844                 cpThinkOutput[i] = '.';
13845             }
13846             cpThinkOutput[i] = NULLCHAR;
13847             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13848         }
13849     }
13850
13851     if (moveNumber == forwardMostMove - 1 &&
13852         gameInfo.resultDetails != NULL) {
13853         if (gameInfo.resultDetails[0] == NULLCHAR) {
13854             sprintf(res, " %s", PGNResult(gameInfo.result));
13855         } else {
13856             sprintf(res, " {%s} %s",
13857                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13858         }
13859     } else {
13860         res[0] = NULLCHAR;
13861     }
13862
13863     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13864         DisplayMessage(res, cpThinkOutput);
13865     } else {
13866         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13867                 WhiteOnMove(moveNumber) ? " " : ".. ",
13868                 parseList[moveNumber], res);
13869         DisplayMessage(message, cpThinkOutput);
13870     }
13871 }
13872
13873 void
13874 DisplayComment(moveNumber, text)
13875      int moveNumber;
13876      char *text;
13877 {
13878     char title[MSG_SIZ];
13879     char buf[8000]; // comment can be long!
13880     int score, depth;
13881     
13882     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13883       strcpy(title, "Comment");
13884     } else {
13885       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13886               WhiteOnMove(moveNumber) ? " " : ".. ",
13887               parseList[moveNumber]);
13888     }
13889     // [HGM] PV info: display PV info together with (or as) comment
13890     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13891       if(text == NULL) text = "";                                           
13892       score = pvInfoList[moveNumber].score;
13893       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13894               depth, (pvInfoList[moveNumber].time+50)/100, text);
13895       text = buf;
13896     }
13897     if (text != NULL && (appData.autoDisplayComment || commentUp))
13898         CommentPopUp(title, text);
13899 }
13900
13901 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13902  * might be busy thinking or pondering.  It can be omitted if your
13903  * gnuchess is configured to stop thinking immediately on any user
13904  * input.  However, that gnuchess feature depends on the FIONREAD
13905  * ioctl, which does not work properly on some flavors of Unix.
13906  */
13907 void
13908 Attention(cps)
13909      ChessProgramState *cps;
13910 {
13911 #if ATTENTION
13912     if (!cps->useSigint) return;
13913     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13914     switch (gameMode) {
13915       case MachinePlaysWhite:
13916       case MachinePlaysBlack:
13917       case TwoMachinesPlay:
13918       case IcsPlayingWhite:
13919       case IcsPlayingBlack:
13920       case AnalyzeMode:
13921       case AnalyzeFile:
13922         /* Skip if we know it isn't thinking */
13923         if (!cps->maybeThinking) return;
13924         if (appData.debugMode)
13925           fprintf(debugFP, "Interrupting %s\n", cps->which);
13926         InterruptChildProcess(cps->pr);
13927         cps->maybeThinking = FALSE;
13928         break;
13929       default:
13930         break;
13931     }
13932 #endif /*ATTENTION*/
13933 }
13934
13935 int
13936 CheckFlags()
13937 {
13938     if (whiteTimeRemaining <= 0) {
13939         if (!whiteFlag) {
13940             whiteFlag = TRUE;
13941             if (appData.icsActive) {
13942                 if (appData.autoCallFlag &&
13943                     gameMode == IcsPlayingBlack && !blackFlag) {
13944                   SendToICS(ics_prefix);
13945                   SendToICS("flag\n");
13946                 }
13947             } else {
13948                 if (blackFlag) {
13949                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13950                 } else {
13951                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13952                     if (appData.autoCallFlag) {
13953                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13954                         return TRUE;
13955                     }
13956                 }
13957             }
13958         }
13959     }
13960     if (blackTimeRemaining <= 0) {
13961         if (!blackFlag) {
13962             blackFlag = TRUE;
13963             if (appData.icsActive) {
13964                 if (appData.autoCallFlag &&
13965                     gameMode == IcsPlayingWhite && !whiteFlag) {
13966                   SendToICS(ics_prefix);
13967                   SendToICS("flag\n");
13968                 }
13969             } else {
13970                 if (whiteFlag) {
13971                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13972                 } else {
13973                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13974                     if (appData.autoCallFlag) {
13975                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13976                         return TRUE;
13977                     }
13978                 }
13979             }
13980         }
13981     }
13982     return FALSE;
13983 }
13984
13985 void
13986 CheckTimeControl()
13987 {
13988     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13989         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13990
13991     /*
13992      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13993      */
13994     if ( !WhiteOnMove(forwardMostMove) )
13995         /* White made time control */
13996         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13997         /* [HGM] time odds: correct new time quota for time odds! */
13998                                             / WhitePlayer()->timeOdds;
13999       else
14000         /* Black made time control */
14001         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14002                                             / WhitePlayer()->other->timeOdds;
14003 }
14004
14005 void
14006 DisplayBothClocks()
14007 {
14008     int wom = gameMode == EditPosition ?
14009       !blackPlaysFirst : WhiteOnMove(currentMove);
14010     DisplayWhiteClock(whiteTimeRemaining, wom);
14011     DisplayBlackClock(blackTimeRemaining, !wom);
14012 }
14013
14014
14015 /* Timekeeping seems to be a portability nightmare.  I think everyone
14016    has ftime(), but I'm really not sure, so I'm including some ifdefs
14017    to use other calls if you don't.  Clocks will be less accurate if
14018    you have neither ftime nor gettimeofday.
14019 */
14020
14021 /* VS 2008 requires the #include outside of the function */
14022 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14023 #include <sys/timeb.h>
14024 #endif
14025
14026 /* Get the current time as a TimeMark */
14027 void
14028 GetTimeMark(tm)
14029      TimeMark *tm;
14030 {
14031 #if HAVE_GETTIMEOFDAY
14032
14033     struct timeval timeVal;
14034     struct timezone timeZone;
14035
14036     gettimeofday(&timeVal, &timeZone);
14037     tm->sec = (long) timeVal.tv_sec; 
14038     tm->ms = (int) (timeVal.tv_usec / 1000L);
14039
14040 #else /*!HAVE_GETTIMEOFDAY*/
14041 #if HAVE_FTIME
14042
14043 // include <sys/timeb.h> / moved to just above start of function
14044     struct timeb timeB;
14045
14046     ftime(&timeB);
14047     tm->sec = (long) timeB.time;
14048     tm->ms = (int) timeB.millitm;
14049
14050 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14051     tm->sec = (long) time(NULL);
14052     tm->ms = 0;
14053 #endif
14054 #endif
14055 }
14056
14057 /* Return the difference in milliseconds between two
14058    time marks.  We assume the difference will fit in a long!
14059 */
14060 long
14061 SubtractTimeMarks(tm2, tm1)
14062      TimeMark *tm2, *tm1;
14063 {
14064     return 1000L*(tm2->sec - tm1->sec) +
14065            (long) (tm2->ms - tm1->ms);
14066 }
14067
14068
14069 /*
14070  * Code to manage the game clocks.
14071  *
14072  * In tournament play, black starts the clock and then white makes a move.
14073  * We give the human user a slight advantage if he is playing white---the
14074  * clocks don't run until he makes his first move, so it takes zero time.
14075  * Also, we don't account for network lag, so we could get out of sync
14076  * with GNU Chess's clock -- but then, referees are always right.  
14077  */
14078
14079 static TimeMark tickStartTM;
14080 static long intendedTickLength;
14081
14082 long
14083 NextTickLength(timeRemaining)
14084      long timeRemaining;
14085 {
14086     long nominalTickLength, nextTickLength;
14087
14088     if (timeRemaining > 0L && timeRemaining <= 10000L)
14089       nominalTickLength = 100L;
14090     else
14091       nominalTickLength = 1000L;
14092     nextTickLength = timeRemaining % nominalTickLength;
14093     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14094
14095     return nextTickLength;
14096 }
14097
14098 /* Adjust clock one minute up or down */
14099 void
14100 AdjustClock(Boolean which, int dir)
14101 {
14102     if(which) blackTimeRemaining += 60000*dir;
14103     else      whiteTimeRemaining += 60000*dir;
14104     DisplayBothClocks();
14105 }
14106
14107 /* Stop clocks and reset to a fresh time control */
14108 void
14109 ResetClocks() 
14110 {
14111     (void) StopClockTimer();
14112     if (appData.icsActive) {
14113         whiteTimeRemaining = blackTimeRemaining = 0;
14114     } else if (searchTime) {
14115         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14116         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14117     } else { /* [HGM] correct new time quote for time odds */
14118         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14119         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14120     }
14121     if (whiteFlag || blackFlag) {
14122         DisplayTitle("");
14123         whiteFlag = blackFlag = FALSE;
14124     }
14125     DisplayBothClocks();
14126 }
14127
14128 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14129
14130 /* Decrement running clock by amount of time that has passed */
14131 void
14132 DecrementClocks()
14133 {
14134     long timeRemaining;
14135     long lastTickLength, fudge;
14136     TimeMark now;
14137
14138     if (!appData.clockMode) return;
14139     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14140         
14141     GetTimeMark(&now);
14142
14143     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14144
14145     /* Fudge if we woke up a little too soon */
14146     fudge = intendedTickLength - lastTickLength;
14147     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14148
14149     if (WhiteOnMove(forwardMostMove)) {
14150         if(whiteNPS >= 0) lastTickLength = 0;
14151         timeRemaining = whiteTimeRemaining -= lastTickLength;
14152         DisplayWhiteClock(whiteTimeRemaining - fudge,
14153                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14154     } else {
14155         if(blackNPS >= 0) lastTickLength = 0;
14156         timeRemaining = blackTimeRemaining -= lastTickLength;
14157         DisplayBlackClock(blackTimeRemaining - fudge,
14158                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14159     }
14160
14161     if (CheckFlags()) return;
14162         
14163     tickStartTM = now;
14164     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14165     StartClockTimer(intendedTickLength);
14166
14167     /* if the time remaining has fallen below the alarm threshold, sound the
14168      * alarm. if the alarm has sounded and (due to a takeback or time control
14169      * with increment) the time remaining has increased to a level above the
14170      * threshold, reset the alarm so it can sound again. 
14171      */
14172     
14173     if (appData.icsActive && appData.icsAlarm) {
14174
14175         /* make sure we are dealing with the user's clock */
14176         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14177                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14178            )) return;
14179
14180         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14181             alarmSounded = FALSE;
14182         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14183             PlayAlarmSound();
14184             alarmSounded = TRUE;
14185         }
14186     }
14187 }
14188
14189
14190 /* A player has just moved, so stop the previously running
14191    clock and (if in clock mode) start the other one.
14192    We redisplay both clocks in case we're in ICS mode, because
14193    ICS gives us an update to both clocks after every move.
14194    Note that this routine is called *after* forwardMostMove
14195    is updated, so the last fractional tick must be subtracted
14196    from the color that is *not* on move now.
14197 */
14198 void
14199 SwitchClocks(int newMoveNr)
14200 {
14201     long lastTickLength;
14202     TimeMark now;
14203     int flagged = FALSE;
14204
14205     GetTimeMark(&now);
14206
14207     if (StopClockTimer() && appData.clockMode) {
14208         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14209         if (!WhiteOnMove(forwardMostMove)) {
14210             if(blackNPS >= 0) lastTickLength = 0;
14211             blackTimeRemaining -= lastTickLength;
14212            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14213 //         if(pvInfoList[forwardMostMove-1].time == -1)
14214                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14215                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14216         } else {
14217            if(whiteNPS >= 0) lastTickLength = 0;
14218            whiteTimeRemaining -= lastTickLength;
14219            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14220 //         if(pvInfoList[forwardMostMove-1].time == -1)
14221                  pvInfoList[forwardMostMove-1].time = 
14222                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14223         }
14224         flagged = CheckFlags();
14225     }
14226     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14227     CheckTimeControl();
14228
14229     if (flagged || !appData.clockMode) return;
14230
14231     switch (gameMode) {
14232       case MachinePlaysBlack:
14233       case MachinePlaysWhite:
14234       case BeginningOfGame:
14235         if (pausing) return;
14236         break;
14237
14238       case EditGame:
14239       case PlayFromGameFile:
14240       case IcsExamining:
14241         return;
14242
14243       default:
14244         break;
14245     }
14246
14247     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14248         if(WhiteOnMove(forwardMostMove))
14249              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14250         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14251     }
14252
14253     tickStartTM = now;
14254     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14255       whiteTimeRemaining : blackTimeRemaining);
14256     StartClockTimer(intendedTickLength);
14257 }
14258         
14259
14260 /* Stop both clocks */
14261 void
14262 StopClocks()
14263 {       
14264     long lastTickLength;
14265     TimeMark now;
14266
14267     if (!StopClockTimer()) return;
14268     if (!appData.clockMode) return;
14269
14270     GetTimeMark(&now);
14271
14272     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14273     if (WhiteOnMove(forwardMostMove)) {
14274         if(whiteNPS >= 0) lastTickLength = 0;
14275         whiteTimeRemaining -= lastTickLength;
14276         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14277     } else {
14278         if(blackNPS >= 0) lastTickLength = 0;
14279         blackTimeRemaining -= lastTickLength;
14280         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14281     }
14282     CheckFlags();
14283 }
14284         
14285 /* Start clock of player on move.  Time may have been reset, so
14286    if clock is already running, stop and restart it. */
14287 void
14288 StartClocks()
14289 {
14290     (void) StopClockTimer(); /* in case it was running already */
14291     DisplayBothClocks();
14292     if (CheckFlags()) return;
14293
14294     if (!appData.clockMode) return;
14295     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14296
14297     GetTimeMark(&tickStartTM);
14298     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14299       whiteTimeRemaining : blackTimeRemaining);
14300
14301    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14302     whiteNPS = blackNPS = -1; 
14303     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14304        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14305         whiteNPS = first.nps;
14306     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14307        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14308         blackNPS = first.nps;
14309     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14310         whiteNPS = second.nps;
14311     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14312         blackNPS = second.nps;
14313     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14314
14315     StartClockTimer(intendedTickLength);
14316 }
14317
14318 char *
14319 TimeString(ms)
14320      long ms;
14321 {
14322     long second, minute, hour, day;
14323     char *sign = "";
14324     static char buf[32];
14325     
14326     if (ms > 0 && ms <= 9900) {
14327       /* convert milliseconds to tenths, rounding up */
14328       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14329
14330       sprintf(buf, " %03.1f ", tenths/10.0);
14331       return buf;
14332     }
14333
14334     /* convert milliseconds to seconds, rounding up */
14335     /* use floating point to avoid strangeness of integer division
14336        with negative dividends on many machines */
14337     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14338
14339     if (second < 0) {
14340         sign = "-";
14341         second = -second;
14342     }
14343     
14344     day = second / (60 * 60 * 24);
14345     second = second % (60 * 60 * 24);
14346     hour = second / (60 * 60);
14347     second = second % (60 * 60);
14348     minute = second / 60;
14349     second = second % 60;
14350     
14351     if (day > 0)
14352       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14353               sign, day, hour, minute, second);
14354     else if (hour > 0)
14355       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14356     else
14357       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14358     
14359     return buf;
14360 }
14361
14362
14363 /*
14364  * This is necessary because some C libraries aren't ANSI C compliant yet.
14365  */
14366 char *
14367 StrStr(string, match)
14368      char *string, *match;
14369 {
14370     int i, length;
14371     
14372     length = strlen(match);
14373     
14374     for (i = strlen(string) - length; i >= 0; i--, string++)
14375       if (!strncmp(match, string, length))
14376         return string;
14377     
14378     return NULL;
14379 }
14380
14381 char *
14382 StrCaseStr(string, match)
14383      char *string, *match;
14384 {
14385     int i, j, length;
14386     
14387     length = strlen(match);
14388     
14389     for (i = strlen(string) - length; i >= 0; i--, string++) {
14390         for (j = 0; j < length; j++) {
14391             if (ToLower(match[j]) != ToLower(string[j]))
14392               break;
14393         }
14394         if (j == length) return string;
14395     }
14396
14397     return NULL;
14398 }
14399
14400 #ifndef _amigados
14401 int
14402 StrCaseCmp(s1, s2)
14403      char *s1, *s2;
14404 {
14405     char c1, c2;
14406     
14407     for (;;) {
14408         c1 = ToLower(*s1++);
14409         c2 = ToLower(*s2++);
14410         if (c1 > c2) return 1;
14411         if (c1 < c2) return -1;
14412         if (c1 == NULLCHAR) return 0;
14413     }
14414 }
14415
14416
14417 int
14418 ToLower(c)
14419      int c;
14420 {
14421     return isupper(c) ? tolower(c) : c;
14422 }
14423
14424
14425 int
14426 ToUpper(c)
14427      int c;
14428 {
14429     return islower(c) ? toupper(c) : c;
14430 }
14431 #endif /* !_amigados    */
14432
14433 char *
14434 StrSave(s)
14435      char *s;
14436 {
14437     char *ret;
14438
14439     if ((ret = (char *) malloc(strlen(s) + 1))) {
14440         strcpy(ret, s);
14441     }
14442     return ret;
14443 }
14444
14445 char *
14446 StrSavePtr(s, savePtr)
14447      char *s, **savePtr;
14448 {
14449     if (*savePtr) {
14450         free(*savePtr);
14451     }
14452     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14453         strcpy(*savePtr, s);
14454     }
14455     return(*savePtr);
14456 }
14457
14458 char *
14459 PGNDate()
14460 {
14461     time_t clock;
14462     struct tm *tm;
14463     char buf[MSG_SIZ];
14464
14465     clock = time((time_t *)NULL);
14466     tm = localtime(&clock);
14467     sprintf(buf, "%04d.%02d.%02d",
14468             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14469     return StrSave(buf);
14470 }
14471
14472
14473 char *
14474 PositionToFEN(move, overrideCastling)
14475      int move;
14476      char *overrideCastling;
14477 {
14478     int i, j, fromX, fromY, toX, toY;
14479     int whiteToPlay;
14480     char buf[128];
14481     char *p, *q;
14482     int emptycount;
14483     ChessSquare piece;
14484
14485     whiteToPlay = (gameMode == EditPosition) ?
14486       !blackPlaysFirst : (move % 2 == 0);
14487     p = buf;
14488
14489     /* Piece placement data */
14490     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14491         emptycount = 0;
14492         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14493             if (boards[move][i][j] == EmptySquare) {
14494                 emptycount++;
14495             } else { ChessSquare piece = boards[move][i][j];
14496                 if (emptycount > 0) {
14497                     if(emptycount<10) /* [HGM] can be >= 10 */
14498                         *p++ = '0' + emptycount;
14499                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14500                     emptycount = 0;
14501                 }
14502                 if(PieceToChar(piece) == '+') {
14503                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14504                     *p++ = '+';
14505                     piece = (ChessSquare)(DEMOTED piece);
14506                 } 
14507                 *p++ = PieceToChar(piece);
14508                 if(p[-1] == '~') {
14509                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14510                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14511                     *p++ = '~';
14512                 }
14513             }
14514         }
14515         if (emptycount > 0) {
14516             if(emptycount<10) /* [HGM] can be >= 10 */
14517                 *p++ = '0' + emptycount;
14518             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14519             emptycount = 0;
14520         }
14521         *p++ = '/';
14522     }
14523     *(p - 1) = ' ';
14524
14525     /* [HGM] print Crazyhouse or Shogi holdings */
14526     if( gameInfo.holdingsWidth ) {
14527         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14528         q = p;
14529         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14530             piece = boards[move][i][BOARD_WIDTH-1];
14531             if( piece != EmptySquare )
14532               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14533                   *p++ = PieceToChar(piece);
14534         }
14535         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14536             piece = boards[move][BOARD_HEIGHT-i-1][0];
14537             if( piece != EmptySquare )
14538               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14539                   *p++ = PieceToChar(piece);
14540         }
14541
14542         if( q == p ) *p++ = '-';
14543         *p++ = ']';
14544         *p++ = ' ';
14545     }
14546
14547     /* Active color */
14548     *p++ = whiteToPlay ? 'w' : 'b';
14549     *p++ = ' ';
14550
14551   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14552     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14553   } else {
14554   if(nrCastlingRights) {
14555      q = p;
14556      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14557        /* [HGM] write directly from rights */
14558            if(boards[move][CASTLING][2] != NoRights &&
14559               boards[move][CASTLING][0] != NoRights   )
14560                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14561            if(boards[move][CASTLING][2] != NoRights &&
14562               boards[move][CASTLING][1] != NoRights   )
14563                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14564            if(boards[move][CASTLING][5] != NoRights &&
14565               boards[move][CASTLING][3] != NoRights   )
14566                 *p++ = boards[move][CASTLING][3] + AAA;
14567            if(boards[move][CASTLING][5] != NoRights &&
14568               boards[move][CASTLING][4] != NoRights   )
14569                 *p++ = boards[move][CASTLING][4] + AAA;
14570      } else {
14571
14572         /* [HGM] write true castling rights */
14573         if( nrCastlingRights == 6 ) {
14574             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14575                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14576             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14577                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14578             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14579                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14580             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14581                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14582         }
14583      }
14584      if (q == p) *p++ = '-'; /* No castling rights */
14585      *p++ = ' ';
14586   }
14587
14588   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14589      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14590     /* En passant target square */
14591     if (move > backwardMostMove) {
14592         fromX = moveList[move - 1][0] - AAA;
14593         fromY = moveList[move - 1][1] - ONE;
14594         toX = moveList[move - 1][2] - AAA;
14595         toY = moveList[move - 1][3] - ONE;
14596         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14597             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14598             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14599             fromX == toX) {
14600             /* 2-square pawn move just happened */
14601             *p++ = toX + AAA;
14602             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14603         } else {
14604             *p++ = '-';
14605         }
14606     } else if(move == backwardMostMove) {
14607         // [HGM] perhaps we should always do it like this, and forget the above?
14608         if((signed char)boards[move][EP_STATUS] >= 0) {
14609             *p++ = boards[move][EP_STATUS] + AAA;
14610             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14611         } else {
14612             *p++ = '-';
14613         }
14614     } else {
14615         *p++ = '-';
14616     }
14617     *p++ = ' ';
14618   }
14619   }
14620
14621     /* [HGM] find reversible plies */
14622     {   int i = 0, j=move;
14623
14624         if (appData.debugMode) { int k;
14625             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14626             for(k=backwardMostMove; k<=forwardMostMove; k++)
14627                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14628
14629         }
14630
14631         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14632         if( j == backwardMostMove ) i += initialRulePlies;
14633         sprintf(p, "%d ", i);
14634         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14635     }
14636     /* Fullmove number */
14637     sprintf(p, "%d", (move / 2) + 1);
14638     
14639     return StrSave(buf);
14640 }
14641
14642 Boolean
14643 ParseFEN(board, blackPlaysFirst, fen)
14644     Board board;
14645      int *blackPlaysFirst;
14646      char *fen;
14647 {
14648     int i, j;
14649     char *p;
14650     int emptycount;
14651     ChessSquare piece;
14652
14653     p = fen;
14654
14655     /* [HGM] by default clear Crazyhouse holdings, if present */
14656     if(gameInfo.holdingsWidth) {
14657        for(i=0; i<BOARD_HEIGHT; i++) {
14658            board[i][0]             = EmptySquare; /* black holdings */
14659            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14660            board[i][1]             = (ChessSquare) 0; /* black counts */
14661            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14662        }
14663     }
14664
14665     /* Piece placement data */
14666     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14667         j = 0;
14668         for (;;) {
14669             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14670                 if (*p == '/') p++;
14671                 emptycount = gameInfo.boardWidth - j;
14672                 while (emptycount--)
14673                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14674                 break;
14675 #if(BOARD_FILES >= 10)
14676             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14677                 p++; emptycount=10;
14678                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14679                 while (emptycount--)
14680                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14681 #endif
14682             } else if (isdigit(*p)) {
14683                 emptycount = *p++ - '0';
14684                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14685                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14686                 while (emptycount--)
14687                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14688             } else if (*p == '+' || isalpha(*p)) {
14689                 if (j >= gameInfo.boardWidth) return FALSE;
14690                 if(*p=='+') {
14691                     piece = CharToPiece(*++p);
14692                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14693                     piece = (ChessSquare) (PROMOTED piece ); p++;
14694                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14695                 } else piece = CharToPiece(*p++);
14696
14697                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14698                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14699                     piece = (ChessSquare) (PROMOTED piece);
14700                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14701                     p++;
14702                 }
14703                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14704             } else {
14705                 return FALSE;
14706             }
14707         }
14708     }
14709     while (*p == '/' || *p == ' ') p++;
14710
14711     /* [HGM] look for Crazyhouse holdings here */
14712     while(*p==' ') p++;
14713     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14714         if(*p == '[') p++;
14715         if(*p == '-' ) *p++; /* empty holdings */ else {
14716             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14717             /* if we would allow FEN reading to set board size, we would   */
14718             /* have to add holdings and shift the board read so far here   */
14719             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14720                 *p++;
14721                 if((int) piece >= (int) BlackPawn ) {
14722                     i = (int)piece - (int)BlackPawn;
14723                     i = PieceToNumber((ChessSquare)i);
14724                     if( i >= gameInfo.holdingsSize ) return FALSE;
14725                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14726                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14727                 } else {
14728                     i = (int)piece - (int)WhitePawn;
14729                     i = PieceToNumber((ChessSquare)i);
14730                     if( i >= gameInfo.holdingsSize ) return FALSE;
14731                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14732                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14733                 }
14734             }
14735         }
14736         if(*p == ']') *p++;
14737     }
14738
14739     while(*p == ' ') p++;
14740
14741     /* Active color */
14742     switch (*p++) {
14743       case 'w':
14744         *blackPlaysFirst = FALSE;
14745         break;
14746       case 'b': 
14747         *blackPlaysFirst = TRUE;
14748         break;
14749       default:
14750         return FALSE;
14751     }
14752
14753     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14754     /* return the extra info in global variiables             */
14755
14756     /* set defaults in case FEN is incomplete */
14757     board[EP_STATUS] = EP_UNKNOWN;
14758     for(i=0; i<nrCastlingRights; i++ ) {
14759         board[CASTLING][i] =
14760             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14761     }   /* assume possible unless obviously impossible */
14762     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14763     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14764     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14765                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14766     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14767     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14768     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14769                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14770     FENrulePlies = 0;
14771
14772     while(*p==' ') p++;
14773     if(nrCastlingRights) {
14774       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14775           /* castling indicator present, so default becomes no castlings */
14776           for(i=0; i<nrCastlingRights; i++ ) {
14777                  board[CASTLING][i] = NoRights;
14778           }
14779       }
14780       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14781              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14782              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14783              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14784         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14785
14786         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14787             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14788             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14789         }
14790         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14791             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14792         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14793                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14794         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14795                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14796         switch(c) {
14797           case'K':
14798               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14799               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14800               board[CASTLING][2] = whiteKingFile;
14801               break;
14802           case'Q':
14803               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14804               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14805               board[CASTLING][2] = whiteKingFile;
14806               break;
14807           case'k':
14808               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14809               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14810               board[CASTLING][5] = blackKingFile;
14811               break;
14812           case'q':
14813               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14814               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14815               board[CASTLING][5] = blackKingFile;
14816           case '-':
14817               break;
14818           default: /* FRC castlings */
14819               if(c >= 'a') { /* black rights */
14820                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14821                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14822                   if(i == BOARD_RGHT) break;
14823                   board[CASTLING][5] = i;
14824                   c -= AAA;
14825                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14826                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14827                   if(c > i)
14828                       board[CASTLING][3] = c;
14829                   else
14830                       board[CASTLING][4] = c;
14831               } else { /* white rights */
14832                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14833                     if(board[0][i] == WhiteKing) break;
14834                   if(i == BOARD_RGHT) break;
14835                   board[CASTLING][2] = i;
14836                   c -= AAA - 'a' + 'A';
14837                   if(board[0][c] >= WhiteKing) break;
14838                   if(c > i)
14839                       board[CASTLING][0] = c;
14840                   else
14841                       board[CASTLING][1] = c;
14842               }
14843         }
14844       }
14845       for(i=0; i<nrCastlingRights; i++)
14846         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14847     if (appData.debugMode) {
14848         fprintf(debugFP, "FEN castling rights:");
14849         for(i=0; i<nrCastlingRights; i++)
14850         fprintf(debugFP, " %d", board[CASTLING][i]);
14851         fprintf(debugFP, "\n");
14852     }
14853
14854       while(*p==' ') p++;
14855     }
14856
14857     /* read e.p. field in games that know e.p. capture */
14858     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14859        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14860       if(*p=='-') {
14861         p++; board[EP_STATUS] = EP_NONE;
14862       } else {
14863          char c = *p++ - AAA;
14864
14865          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14866          if(*p >= '0' && *p <='9') *p++;
14867          board[EP_STATUS] = c;
14868       }
14869     }
14870
14871
14872     if(sscanf(p, "%d", &i) == 1) {
14873         FENrulePlies = i; /* 50-move ply counter */
14874         /* (The move number is still ignored)    */
14875     }
14876
14877     return TRUE;
14878 }
14879       
14880 void
14881 EditPositionPasteFEN(char *fen)
14882 {
14883   if (fen != NULL) {
14884     Board initial_position;
14885
14886     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14887       DisplayError(_("Bad FEN position in clipboard"), 0);
14888       return ;
14889     } else {
14890       int savedBlackPlaysFirst = blackPlaysFirst;
14891       EditPositionEvent();
14892       blackPlaysFirst = savedBlackPlaysFirst;
14893       CopyBoard(boards[0], initial_position);
14894       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14895       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14896       DisplayBothClocks();
14897       DrawPosition(FALSE, boards[currentMove]);
14898     }
14899   }
14900 }
14901
14902 static char cseq[12] = "\\   ";
14903
14904 Boolean set_cont_sequence(char *new_seq)
14905 {
14906     int len;
14907     Boolean ret;
14908
14909     // handle bad attempts to set the sequence
14910         if (!new_seq)
14911                 return 0; // acceptable error - no debug
14912
14913     len = strlen(new_seq);
14914     ret = (len > 0) && (len < sizeof(cseq));
14915     if (ret)
14916         strcpy(cseq, new_seq);
14917     else if (appData.debugMode)
14918         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14919     return ret;
14920 }
14921
14922 /*
14923     reformat a source message so words don't cross the width boundary.  internal
14924     newlines are not removed.  returns the wrapped size (no null character unless
14925     included in source message).  If dest is NULL, only calculate the size required
14926     for the dest buffer.  lp argument indicats line position upon entry, and it's
14927     passed back upon exit.
14928 */
14929 int wrap(char *dest, char *src, int count, int width, int *lp)
14930 {
14931     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14932
14933     cseq_len = strlen(cseq);
14934     old_line = line = *lp;
14935     ansi = len = clen = 0;
14936
14937     for (i=0; i < count; i++)
14938     {
14939         if (src[i] == '\033')
14940             ansi = 1;
14941
14942         // if we hit the width, back up
14943         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14944         {
14945             // store i & len in case the word is too long
14946             old_i = i, old_len = len;
14947
14948             // find the end of the last word
14949             while (i && src[i] != ' ' && src[i] != '\n')
14950             {
14951                 i--;
14952                 len--;
14953             }
14954
14955             // word too long?  restore i & len before splitting it
14956             if ((old_i-i+clen) >= width)
14957             {
14958                 i = old_i;
14959                 len = old_len;
14960             }
14961
14962             // extra space?
14963             if (i && src[i-1] == ' ')
14964                 len--;
14965
14966             if (src[i] != ' ' && src[i] != '\n')
14967             {
14968                 i--;
14969                 if (len)
14970                     len--;
14971             }
14972
14973             // now append the newline and continuation sequence
14974             if (dest)
14975                 dest[len] = '\n';
14976             len++;
14977             if (dest)
14978                 strncpy(dest+len, cseq, cseq_len);
14979             len += cseq_len;
14980             line = cseq_len;
14981             clen = cseq_len;
14982             continue;
14983         }
14984
14985         if (dest)
14986             dest[len] = src[i];
14987         len++;
14988         if (!ansi)
14989             line++;
14990         if (src[i] == '\n')
14991             line = 0;
14992         if (src[i] == 'm')
14993             ansi = 0;
14994     }
14995     if (dest && appData.debugMode)
14996     {
14997         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14998             count, width, line, len, *lp);
14999         show_bytes(debugFP, src, count);
15000         fprintf(debugFP, "\ndest: ");
15001         show_bytes(debugFP, dest, len);
15002         fprintf(debugFP, "\n");
15003     }
15004     *lp = dest ? line : old_line;
15005
15006     return len;
15007 }
15008
15009 // [HGM] vari: routines for shelving variations
15010
15011 void 
15012 PushTail(int firstMove, int lastMove)
15013 {
15014         int i, j, nrMoves = lastMove - firstMove;
15015
15016         if(appData.icsActive) { // only in local mode
15017                 forwardMostMove = currentMove; // mimic old ICS behavior
15018                 return;
15019         }
15020         if(storedGames >= MAX_VARIATIONS-1) return;
15021
15022         // push current tail of game on stack
15023         savedResult[storedGames] = gameInfo.result;
15024         savedDetails[storedGames] = gameInfo.resultDetails;
15025         gameInfo.resultDetails = NULL;
15026         savedFirst[storedGames] = firstMove;
15027         savedLast [storedGames] = lastMove;
15028         savedFramePtr[storedGames] = framePtr;
15029         framePtr -= nrMoves; // reserve space for the boards
15030         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15031             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15032             for(j=0; j<MOVE_LEN; j++)
15033                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15034             for(j=0; j<2*MOVE_LEN; j++)
15035                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15036             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15037             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15038             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15039             pvInfoList[firstMove+i-1].depth = 0;
15040             commentList[framePtr+i] = commentList[firstMove+i];
15041             commentList[firstMove+i] = NULL;
15042         }
15043
15044         storedGames++;
15045         forwardMostMove = firstMove; // truncate game so we can start variation
15046         if(storedGames == 1) GreyRevert(FALSE);
15047 }
15048
15049 Boolean
15050 PopTail(Boolean annotate)
15051 {
15052         int i, j, nrMoves;
15053         char buf[8000], moveBuf[20];
15054
15055         if(appData.icsActive) return FALSE; // only in local mode
15056         if(!storedGames) return FALSE; // sanity
15057         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15058
15059         storedGames--;
15060         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15061         nrMoves = savedLast[storedGames] - currentMove;
15062         if(annotate) {
15063                 int cnt = 10;
15064                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15065                 else strcpy(buf, "(");
15066                 for(i=currentMove; i<forwardMostMove; i++) {
15067                         if(WhiteOnMove(i))
15068                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15069                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15070                         strcat(buf, moveBuf);
15071                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15072                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15073                 }
15074                 strcat(buf, ")");
15075         }
15076         for(i=1; i<=nrMoves; i++) { // copy last variation back
15077             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15078             for(j=0; j<MOVE_LEN; j++)
15079                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15080             for(j=0; j<2*MOVE_LEN; j++)
15081                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15082             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15083             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15084             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15085             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15086             commentList[currentMove+i] = commentList[framePtr+i];
15087             commentList[framePtr+i] = NULL;
15088         }
15089         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15090         framePtr = savedFramePtr[storedGames];
15091         gameInfo.result = savedResult[storedGames];
15092         if(gameInfo.resultDetails != NULL) {
15093             free(gameInfo.resultDetails);
15094       }
15095         gameInfo.resultDetails = savedDetails[storedGames];
15096         forwardMostMove = currentMove + nrMoves;
15097         if(storedGames == 0) GreyRevert(TRUE);
15098         return TRUE;
15099 }
15100
15101 void 
15102 CleanupTail()
15103 {       // remove all shelved variations
15104         int i;
15105         for(i=0; i<storedGames; i++) {
15106             if(savedDetails[i])
15107                 free(savedDetails[i]);
15108             savedDetails[i] = NULL;
15109         }
15110         for(i=framePtr; i<MAX_MOVES; i++) {
15111                 if(commentList[i]) free(commentList[i]);
15112                 commentList[i] = NULL;
15113         }
15114         framePtr = MAX_MOVES-1;
15115         storedGames = 0;
15116 }
15117
15118 void
15119 LoadVariation(int index, char *text)
15120 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15121         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15122         int level = 0, move;
15123
15124         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15125         // first find outermost bracketing variation
15126         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15127             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15128                 if(*p == '{') wait = '}'; else
15129                 if(*p == '[') wait = ']'; else
15130                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15131                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15132             }
15133             if(*p == wait) wait = NULLCHAR; // closing ]} found
15134             p++;
15135         }
15136         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15137         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15138         end[1] = NULLCHAR; // clip off comment beyond variation
15139         ToNrEvent(currentMove-1);
15140         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15141         // kludge: use ParsePV() to append variation to game
15142         move = currentMove;
15143         ParsePV(start, TRUE);
15144         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15145         ClearPremoveHighlights();
15146         CommentPopDown();
15147         ToNrEvent(currentMove+1);
15148 }