Fix bug in sending cores command to engine
[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 int partnerHighlight[2];
247 Boolean partnerBoardValid = 0;
248 char partnerStatus[MSG_SIZ];
249 Boolean partnerUp;
250 Boolean originalFlip;
251 Boolean twoBoards = 0;
252 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
253 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
254 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
255 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
256 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
257 int opponentKibitzes;
258 int lastSavedGame; /* [HGM] save: ID of game */
259 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
260 extern int chatCount;
261 int chattingPartner;
262 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
263
264 /* States for ics_getting_history */
265 #define H_FALSE 0
266 #define H_REQUESTED 1
267 #define H_GOT_REQ_HEADER 2
268 #define H_GOT_UNREQ_HEADER 3
269 #define H_GETTING_MOVES 4
270 #define H_GOT_UNWANTED_HEADER 5
271
272 /* whosays values for GameEnds */
273 #define GE_ICS 0
274 #define GE_ENGINE 1
275 #define GE_PLAYER 2
276 #define GE_FILE 3
277 #define GE_XBOARD 4
278 #define GE_ENGINE1 5
279 #define GE_ENGINE2 6
280
281 /* Maximum number of games in a cmail message */
282 #define CMAIL_MAX_GAMES 20
283
284 /* Different types of move when calling RegisterMove */
285 #define CMAIL_MOVE   0
286 #define CMAIL_RESIGN 1
287 #define CMAIL_DRAW   2
288 #define CMAIL_ACCEPT 3
289
290 /* Different types of result to remember for each game */
291 #define CMAIL_NOT_RESULT 0
292 #define CMAIL_OLD_RESULT 1
293 #define CMAIL_NEW_RESULT 2
294
295 /* Telnet protocol constants */
296 #define TN_WILL 0373
297 #define TN_WONT 0374
298 #define TN_DO   0375
299 #define TN_DONT 0376
300 #define TN_IAC  0377
301 #define TN_ECHO 0001
302 #define TN_SGA  0003
303 #define TN_PORT 23
304
305 /* [AS] */
306 static char * safeStrCpy( char * dst, const char * src, size_t count )
307 {
308     assert( dst != NULL );
309     assert( src != NULL );
310     assert( count > 0 );
311
312     strncpy( dst, src, count );
313     dst[ count-1 ] = '\0';
314     return dst;
315 }
316
317 /* Some compiler can't cast u64 to double
318  * This function do the job for us:
319
320  * We use the highest bit for cast, this only
321  * works if the highest bit is not
322  * in use (This should not happen)
323  *
324  * We used this for all compiler
325  */
326 double
327 u64ToDouble(u64 value)
328 {
329   double r;
330   u64 tmp = value & u64Const(0x7fffffffffffffff);
331   r = (double)(s64)tmp;
332   if (value & u64Const(0x8000000000000000))
333        r +=  9.2233720368547758080e18; /* 2^63 */
334  return r;
335 }
336
337 /* Fake up flags for now, as we aren't keeping track of castling
338    availability yet. [HGM] Change of logic: the flag now only
339    indicates the type of castlings allowed by the rule of the game.
340    The actual rights themselves are maintained in the array
341    castlingRights, as part of the game history, and are not probed
342    by this function.
343  */
344 int
345 PosFlags(index)
346 {
347   int flags = F_ALL_CASTLE_OK;
348   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
349   switch (gameInfo.variant) {
350   case VariantSuicide:
351     flags &= ~F_ALL_CASTLE_OK;
352   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
353     flags |= F_IGNORE_CHECK;
354   case VariantLosers:
355     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
356     break;
357   case VariantAtomic:
358     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359     break;
360   case VariantKriegspiel:
361     flags |= F_KRIEGSPIEL_CAPTURE;
362     break;
363   case VariantCapaRandom: 
364   case VariantFischeRandom:
365     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
366   case VariantNoCastle:
367   case VariantShatranj:
368   case VariantCourier:
369   case VariantMakruk:
370     flags &= ~F_ALL_CASTLE_OK;
371     break;
372   default:
373     break;
374   }
375   return flags;
376 }
377
378 FILE *gameFileFP, *debugFP;
379
380 /* 
381     [AS] Note: sometimes, the sscanf() function is used to parse the input
382     into a fixed-size buffer. Because of this, we must be prepared to
383     receive strings as long as the size of the input buffer, which is currently
384     set to 4K for Windows and 8K for the rest.
385     So, we must either allocate sufficiently large buffers here, or
386     reduce the size of the input buffer in the input reading part.
387 */
388
389 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
390 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
391 char thinkOutput1[MSG_SIZ*10];
392
393 ChessProgramState first, second;
394
395 /* premove variables */
396 int premoveToX = 0;
397 int premoveToY = 0;
398 int premoveFromX = 0;
399 int premoveFromY = 0;
400 int premovePromoChar = 0;
401 int gotPremove = 0;
402 Boolean alarmSounded;
403 /* end premove variables */
404
405 char *ics_prefix = "$";
406 int ics_type = ICS_GENERIC;
407
408 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
409 int pauseExamForwardMostMove = 0;
410 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
411 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
412 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
413 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
414 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
415 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
416 int whiteFlag = FALSE, blackFlag = FALSE;
417 int userOfferedDraw = FALSE;
418 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
419 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
420 int cmailMoveType[CMAIL_MAX_GAMES];
421 long ics_clock_paused = 0;
422 ProcRef icsPR = NoProc, cmailPR = NoProc;
423 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
424 GameMode gameMode = BeginningOfGame;
425 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
426 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
427 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
428 int hiddenThinkOutputState = 0; /* [AS] */
429 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
430 int adjudicateLossPlies = 6;
431 char white_holding[64], black_holding[64];
432 TimeMark lastNodeCountTime;
433 long lastNodeCount=0;
434 int have_sent_ICS_logon = 0;
435 int movesPerSession;
436 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
437 long timeControl_2; /* [AS] Allow separate time controls */
438 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
439 long timeRemaining[2][MAX_MOVES];
440 int matchGame = 0;
441 TimeMark programStartTime;
442 char ics_handle[MSG_SIZ];
443 int have_set_title = 0;
444
445 /* animateTraining preserves the state of appData.animate
446  * when Training mode is activated. This allows the
447  * response to be animated when appData.animate == TRUE and
448  * appData.animateDragging == TRUE.
449  */
450 Boolean animateTraining;
451
452 GameInfo gameInfo;
453
454 AppData appData;
455
456 Board boards[MAX_MOVES];
457 /* [HGM] Following 7 needed for accurate legality tests: */
458 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
459 signed char  initialRights[BOARD_FILES];
460 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
461 int   initialRulePlies, FENrulePlies;
462 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
463 int loadFlag = 0; 
464 int shuffleOpenings;
465 int mute; // mute all sounds
466
467 // [HGM] vari: next 12 to save and restore variations
468 #define MAX_VARIATIONS 10
469 int framePtr = MAX_MOVES-1; // points to free stack entry
470 int storedGames = 0;
471 int savedFirst[MAX_VARIATIONS];
472 int savedLast[MAX_VARIATIONS];
473 int savedFramePtr[MAX_VARIATIONS];
474 char *savedDetails[MAX_VARIATIONS];
475 ChessMove savedResult[MAX_VARIATIONS];
476
477 void PushTail P((int firstMove, int lastMove));
478 Boolean PopTail P((Boolean annotate));
479 void CleanupTail P((void));
480
481 ChessSquare  FIDEArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackBishop, BlackKnight, BlackRook }
486 };
487
488 ChessSquare twoKingsArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
490         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
491     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
492         BlackKing, BlackKing, BlackKnight, BlackRook }
493 };
494
495 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
496     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
497         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
498     { BlackRook, BlackMan, BlackBishop, BlackQueen,
499         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
500 };
501
502 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
503     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
504         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
505     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
506         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
507 };
508
509 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
511         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
513         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
514 };
515
516 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
517     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
518         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackMan, BlackFerz,
520         BlackKing, BlackMan, BlackKnight, BlackRook }
521 };
522
523
524 #if (BOARD_FILES>=10)
525 ChessSquare ShogiArray[2][BOARD_FILES] = {
526     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
527         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
528     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
529         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
530 };
531
532 ChessSquare XiangqiArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
534         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
536         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare CapablancaArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
541         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
543         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
544 };
545
546 ChessSquare GreatArray[2][BOARD_FILES] = {
547     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
548         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
549     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
550         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
551 };
552
553 ChessSquare JanusArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
555         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
556     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
557         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
558 };
559
560 #ifdef GOTHIC
561 ChessSquare GothicArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
563         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
565         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
566 };
567 #else // !GOTHIC
568 #define GothicArray CapablancaArray
569 #endif // !GOTHIC
570
571 #ifdef FALCON
572 ChessSquare FalconArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
574         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
576         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
577 };
578 #else // !FALCON
579 #define FalconArray CapablancaArray
580 #endif // !FALCON
581
582 #else // !(BOARD_FILES>=10)
583 #define XiangqiPosition FIDEArray
584 #define CapablancaArray FIDEArray
585 #define GothicArray FIDEArray
586 #define GreatArray FIDEArray
587 #endif // !(BOARD_FILES>=10)
588
589 #if (BOARD_FILES>=12)
590 ChessSquare CourierArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
592         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
593     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
594         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
595 };
596 #else // !(BOARD_FILES>=12)
597 #define CourierArray CapablancaArray
598 #endif // !(BOARD_FILES>=12)
599
600
601 Board initialPosition;
602
603
604 /* Convert str to a rating. Checks for special cases of "----",
605
606    "++++", etc. Also strips ()'s */
607 int
608 string_to_rating(str)
609   char *str;
610 {
611   while(*str && !isdigit(*str)) ++str;
612   if (!*str)
613     return 0;   /* One of the special "no rating" cases */
614   else
615     return atoi(str);
616 }
617
618 void
619 ClearProgramStats()
620 {
621     /* Init programStats */
622     programStats.movelist[0] = 0;
623     programStats.depth = 0;
624     programStats.nr_moves = 0;
625     programStats.moves_left = 0;
626     programStats.nodes = 0;
627     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
628     programStats.score = 0;
629     programStats.got_only_move = 0;
630     programStats.got_fail = 0;
631     programStats.line_is_book = 0;
632 }
633
634 void
635 InitBackEnd1()
636 {
637     int matched, min, sec;
638
639     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
640
641     GetTimeMark(&programStartTime);
642     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
643
644     ClearProgramStats();
645     programStats.ok_to_send = 1;
646     programStats.seen_stat = 0;
647
648     /*
649      * Initialize game list
650      */
651     ListNew(&gameList);
652
653
654     /*
655      * Internet chess server status
656      */
657     if (appData.icsActive) {
658         appData.matchMode = FALSE;
659         appData.matchGames = 0;
660 #if ZIPPY       
661         appData.noChessProgram = !appData.zippyPlay;
662 #else
663         appData.zippyPlay = FALSE;
664         appData.zippyTalk = FALSE;
665         appData.noChessProgram = TRUE;
666 #endif
667         if (*appData.icsHelper != NULLCHAR) {
668             appData.useTelnet = TRUE;
669             appData.telnetProgram = appData.icsHelper;
670         }
671     } else {
672         appData.zippyTalk = appData.zippyPlay = FALSE;
673     }
674
675     /* [AS] Initialize pv info list [HGM] and game state */
676     {
677         int i, j;
678
679         for( i=0; i<=framePtr; i++ ) {
680             pvInfoList[i].depth = -1;
681             boards[i][EP_STATUS] = EP_NONE;
682             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
683         }
684     }
685
686     /*
687      * Parse timeControl resource
688      */
689     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
690                           appData.movesPerSession)) {
691         char buf[MSG_SIZ];
692         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
693         DisplayFatalError(buf, 0, 2);
694     }
695
696     /*
697      * Parse searchTime resource
698      */
699     if (*appData.searchTime != NULLCHAR) {
700         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
701         if (matched == 1) {
702             searchTime = min * 60;
703         } else if (matched == 2) {
704             searchTime = min * 60 + sec;
705         } else {
706             char buf[MSG_SIZ];
707             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
708             DisplayFatalError(buf, 0, 2);
709         }
710     }
711
712     /* [AS] Adjudication threshold */
713     adjudicateLossThreshold = appData.adjudicateLossThreshold;
714     
715     first.which = "first";
716     second.which = "second";
717     first.maybeThinking = second.maybeThinking = FALSE;
718     first.pr = second.pr = NoProc;
719     first.isr = second.isr = NULL;
720     first.sendTime = second.sendTime = 2;
721     first.sendDrawOffers = 1;
722     if (appData.firstPlaysBlack) {
723         first.twoMachinesColor = "black\n";
724         second.twoMachinesColor = "white\n";
725     } else {
726         first.twoMachinesColor = "white\n";
727         second.twoMachinesColor = "black\n";
728     }
729     first.program = appData.firstChessProgram;
730     second.program = appData.secondChessProgram;
731     first.host = appData.firstHost;
732     second.host = appData.secondHost;
733     first.dir = appData.firstDirectory;
734     second.dir = appData.secondDirectory;
735     first.other = &second;
736     second.other = &first;
737     first.initString = appData.initString;
738     second.initString = appData.secondInitString;
739     first.computerString = appData.firstComputerString;
740     second.computerString = appData.secondComputerString;
741     first.useSigint = second.useSigint = TRUE;
742     first.useSigterm = second.useSigterm = TRUE;
743     first.reuse = appData.reuseFirst;
744     second.reuse = appData.reuseSecond;
745     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
746     second.nps = appData.secondNPS;
747     first.useSetboard = second.useSetboard = FALSE;
748     first.useSAN = second.useSAN = FALSE;
749     first.usePing = second.usePing = FALSE;
750     first.lastPing = second.lastPing = 0;
751     first.lastPong = second.lastPong = 0;
752     first.usePlayother = second.usePlayother = FALSE;
753     first.useColors = second.useColors = TRUE;
754     first.useUsermove = second.useUsermove = FALSE;
755     first.sendICS = second.sendICS = FALSE;
756     first.sendName = second.sendName = appData.icsActive;
757     first.sdKludge = second.sdKludge = FALSE;
758     first.stKludge = second.stKludge = FALSE;
759     TidyProgramName(first.program, first.host, first.tidy);
760     TidyProgramName(second.program, second.host, second.tidy);
761     first.matchWins = second.matchWins = 0;
762     strcpy(first.variants, appData.variant);
763     strcpy(second.variants, appData.variant);
764     first.analysisSupport = second.analysisSupport = 2; /* detect */
765     first.analyzing = second.analyzing = FALSE;
766     first.initDone = second.initDone = FALSE;
767
768     /* New features added by Tord: */
769     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
770     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
771     /* End of new features added by Tord. */
772     first.fenOverride  = appData.fenOverride1;
773     second.fenOverride = appData.fenOverride2;
774
775     /* [HGM] time odds: set factor for each machine */
776     first.timeOdds  = appData.firstTimeOdds;
777     second.timeOdds = appData.secondTimeOdds;
778     { float norm = 1;
779         if(appData.timeOddsMode) {
780             norm = first.timeOdds;
781             if(norm > second.timeOdds) norm = second.timeOdds;
782         }
783         first.timeOdds /= norm;
784         second.timeOdds /= norm;
785     }
786
787     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
788     first.accumulateTC = appData.firstAccumulateTC;
789     second.accumulateTC = appData.secondAccumulateTC;
790     first.maxNrOfSessions = second.maxNrOfSessions = 1;
791
792     /* [HGM] debug */
793     first.debug = second.debug = FALSE;
794     first.supportsNPS = second.supportsNPS = UNKNOWN;
795
796     /* [HGM] options */
797     first.optionSettings  = appData.firstOptions;
798     second.optionSettings = appData.secondOptions;
799
800     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
801     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
802     first.isUCI = appData.firstIsUCI; /* [AS] */
803     second.isUCI = appData.secondIsUCI; /* [AS] */
804     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
805     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
806
807     if (appData.firstProtocolVersion > PROTOVER ||
808         appData.firstProtocolVersion < 1) {
809       char buf[MSG_SIZ];
810       sprintf(buf, _("protocol version %d not supported"),
811               appData.firstProtocolVersion);
812       DisplayFatalError(buf, 0, 2);
813     } else {
814       first.protocolVersion = appData.firstProtocolVersion;
815     }
816
817     if (appData.secondProtocolVersion > PROTOVER ||
818         appData.secondProtocolVersion < 1) {
819       char buf[MSG_SIZ];
820       sprintf(buf, _("protocol version %d not supported"),
821               appData.secondProtocolVersion);
822       DisplayFatalError(buf, 0, 2);
823     } else {
824       second.protocolVersion = appData.secondProtocolVersion;
825     }
826
827     if (appData.icsActive) {
828         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
829 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
830     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
831         appData.clockMode = FALSE;
832         first.sendTime = second.sendTime = 0;
833     }
834     
835 #if ZIPPY
836     /* Override some settings from environment variables, for backward
837        compatibility.  Unfortunately it's not feasible to have the env
838        vars just set defaults, at least in xboard.  Ugh.
839     */
840     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
841       ZippyInit();
842     }
843 #endif
844     
845     if (appData.noChessProgram) {
846         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
847         sprintf(programVersion, "%s", PACKAGE_STRING);
848     } else {
849       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
850       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
851       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
852     }
853
854     if (!appData.icsActive) {
855       char buf[MSG_SIZ];
856       /* Check for variants that are supported only in ICS mode,
857          or not at all.  Some that are accepted here nevertheless
858          have bugs; see comments below.
859       */
860       VariantClass variant = StringToVariant(appData.variant);
861       switch (variant) {
862       case VariantBughouse:     /* need four players and two boards */
863       case VariantKriegspiel:   /* need to hide pieces and move details */
864       /* case VariantFischeRandom: (Fabien: moved below) */
865         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
866         DisplayFatalError(buf, 0, 2);
867         return;
868
869       case VariantUnknown:
870       case VariantLoadable:
871       case Variant29:
872       case Variant30:
873       case Variant31:
874       case Variant32:
875       case Variant33:
876       case Variant34:
877       case Variant35:
878       case Variant36:
879       default:
880         sprintf(buf, _("Unknown variant name %s"), appData.variant);
881         DisplayFatalError(buf, 0, 2);
882         return;
883
884       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
885       case VariantFairy:      /* [HGM] TestLegality definitely off! */
886       case VariantGothic:     /* [HGM] should work */
887       case VariantCapablanca: /* [HGM] should work */
888       case VariantCourier:    /* [HGM] initial forced moves not implemented */
889       case VariantShogi:      /* [HGM] drops not tested for legality */
890       case VariantKnightmate: /* [HGM] should work */
891       case VariantCylinder:   /* [HGM] untested */
892       case VariantFalcon:     /* [HGM] untested */
893       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
894                                  offboard interposition not understood */
895       case VariantNormal:     /* definitely works! */
896       case VariantWildCastle: /* pieces not automatically shuffled */
897       case VariantNoCastle:   /* pieces not automatically shuffled */
898       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
899       case VariantLosers:     /* should work except for win condition,
900                                  and doesn't know captures are mandatory */
901       case VariantSuicide:    /* should work except for win condition,
902                                  and doesn't know captures are mandatory */
903       case VariantGiveaway:   /* should work except for win condition,
904                                  and doesn't know captures are mandatory */
905       case VariantTwoKings:   /* should work */
906       case VariantAtomic:     /* should work except for win condition */
907       case Variant3Check:     /* should work except for win condition */
908       case VariantShatranj:   /* should work except for all win conditions */
909       case VariantMakruk:     /* should work except for daw countdown */
910       case VariantBerolina:   /* might work if TestLegality is off */
911       case VariantCapaRandom: /* should work */
912       case VariantJanus:      /* should work */
913       case VariantSuper:      /* experimental */
914       case VariantGreat:      /* experimental, requires legality testing to be off */
915         break;
916       }
917     }
918
919     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
920     InitEngineUCI( installDir, &second );
921 }
922
923 int NextIntegerFromString( char ** str, long * value )
924 {
925     int result = -1;
926     char * s = *str;
927
928     while( *s == ' ' || *s == '\t' ) {
929         s++;
930     }
931
932     *value = 0;
933
934     if( *s >= '0' && *s <= '9' ) {
935         while( *s >= '0' && *s <= '9' ) {
936             *value = *value * 10 + (*s - '0');
937             s++;
938         }
939
940         result = 0;
941     }
942
943     *str = s;
944
945     return result;
946 }
947
948 int NextTimeControlFromString( char ** str, long * value )
949 {
950     long temp;
951     int result = NextIntegerFromString( str, &temp );
952
953     if( result == 0 ) {
954         *value = temp * 60; /* Minutes */
955         if( **str == ':' ) {
956             (*str)++;
957             result = NextIntegerFromString( str, &temp );
958             *value += temp; /* Seconds */
959         }
960     }
961
962     return result;
963 }
964
965 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
966 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
967     int result = -1; long temp, temp2;
968
969     if(**str != '+') return -1; // old params remain in force!
970     (*str)++;
971     if( NextTimeControlFromString( str, &temp ) ) return -1;
972
973     if(**str != '/') {
974         /* time only: incremental or sudden-death time control */
975         if(**str == '+') { /* increment follows; read it */
976             (*str)++;
977             if(result = NextIntegerFromString( str, &temp2)) return -1;
978             *inc = temp2 * 1000;
979         } else *inc = 0;
980         *moves = 0; *tc = temp * 1000; 
981         return 0;
982     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
983
984     (*str)++; /* classical time control */
985     result = NextTimeControlFromString( str, &temp2);
986     if(result == 0) {
987         *moves = temp/60;
988         *tc    = temp2 * 1000;
989         *inc   = 0;
990     }
991     return result;
992 }
993
994 int GetTimeQuota(int movenr)
995 {   /* [HGM] get time to add from the multi-session time-control string */
996     int moves=1; /* kludge to force reading of first session */
997     long time, increment;
998     char *s = fullTimeControlString;
999
1000     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1001     do {
1002         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1003         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1004         if(movenr == -1) return time;    /* last move before new session     */
1005         if(!moves) return increment;     /* current session is incremental   */
1006         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1007     } while(movenr >= -1);               /* try again for next session       */
1008
1009     return 0; // no new time quota on this move
1010 }
1011
1012 int
1013 ParseTimeControl(tc, ti, mps)
1014      char *tc;
1015      int ti;
1016      int mps;
1017 {
1018   long tc1;
1019   long tc2;
1020   char buf[MSG_SIZ];
1021   
1022   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1023   if(ti > 0) {
1024     if(mps)
1025       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1026     else sprintf(buf, "+%s+%d", tc, ti);
1027   } else {
1028     if(mps)
1029              sprintf(buf, "+%d/%s", mps, tc);
1030     else sprintf(buf, "+%s", tc);
1031   }
1032   fullTimeControlString = StrSave(buf);
1033   
1034   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1035     return FALSE;
1036   }
1037   
1038   if( *tc == '/' ) {
1039     /* Parse second time control */
1040     tc++;
1041     
1042     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043       return FALSE;
1044     }
1045     
1046     if( tc2 == 0 ) {
1047       return FALSE;
1048     }
1049     
1050     timeControl_2 = tc2 * 1000;
1051   }
1052   else {
1053     timeControl_2 = 0;
1054   }
1055   
1056   if( tc1 == 0 ) {
1057     return FALSE;
1058   }
1059   
1060   timeControl = tc1 * 1000;
1061   
1062   if (ti >= 0) {
1063     timeIncrement = ti * 1000;  /* convert to ms */
1064     movesPerSession = 0;
1065   } else {
1066     timeIncrement = 0;
1067     movesPerSession = mps;
1068   }
1069   return TRUE;
1070 }
1071
1072 void
1073 InitBackEnd2()
1074 {
1075     if (appData.debugMode) {
1076         fprintf(debugFP, "%s\n", programVersion);
1077     }
1078
1079     set_cont_sequence(appData.wrapContSeq);
1080     if (appData.matchGames > 0) {
1081         appData.matchMode = TRUE;
1082     } else if (appData.matchMode) {
1083         appData.matchGames = 1;
1084     }
1085     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1086         appData.matchGames = appData.sameColorGames;
1087     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1088         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1089         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1090     }
1091     Reset(TRUE, FALSE);
1092     if (appData.noChessProgram || first.protocolVersion == 1) {
1093       InitBackEnd3();
1094     } else {
1095       /* kludge: allow timeout for initial "feature" commands */
1096       FreezeUI();
1097       DisplayMessage("", _("Starting chess program"));
1098       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1099     }
1100 }
1101
1102 void
1103 InitBackEnd3 P((void))
1104 {
1105     GameMode initialMode;
1106     char buf[MSG_SIZ];
1107     int err;
1108
1109     InitChessProgram(&first, startedFromSetupPosition);
1110
1111     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1112         free(programVersion);
1113         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1114         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1115     }
1116
1117     if (appData.icsActive) {
1118 #ifdef WIN32
1119         /* [DM] Make a console window if needed [HGM] merged ifs */
1120         ConsoleCreate(); 
1121 #endif
1122         err = establish();
1123         if (err != 0) {
1124             if (*appData.icsCommPort != NULLCHAR) {
1125                 sprintf(buf, _("Could not open comm port %s"),  
1126                         appData.icsCommPort);
1127             } else {
1128                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1129                         appData.icsHost, appData.icsPort);
1130             }
1131             DisplayFatalError(buf, err, 1);
1132             return;
1133         }
1134         SetICSMode();
1135         telnetISR =
1136           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137         fromUserISR =
1138           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1139         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1140             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1141     } else if (appData.noChessProgram) {
1142         SetNCPMode();
1143     } else {
1144         SetGNUMode();
1145     }
1146
1147     if (*appData.cmailGameName != NULLCHAR) {
1148         SetCmailMode();
1149         OpenLoopback(&cmailPR);
1150         cmailISR =
1151           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152     }
1153     
1154     ThawUI();
1155     DisplayMessage("", "");
1156     if (StrCaseCmp(appData.initialMode, "") == 0) {
1157       initialMode = BeginningOfGame;
1158     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159       initialMode = TwoMachinesPlay;
1160     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161       initialMode = AnalyzeFile; 
1162     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163       initialMode = AnalyzeMode;
1164     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165       initialMode = MachinePlaysWhite;
1166     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167       initialMode = MachinePlaysBlack;
1168     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169       initialMode = EditGame;
1170     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171       initialMode = EditPosition;
1172     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173       initialMode = Training;
1174     } else {
1175       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176       DisplayFatalError(buf, 0, 2);
1177       return;
1178     }
1179
1180     if (appData.matchMode) {
1181         /* Set up machine vs. machine match */
1182         if (appData.noChessProgram) {
1183             DisplayFatalError(_("Can't have a match with no chess programs"),
1184                               0, 2);
1185             return;
1186         }
1187         matchMode = TRUE;
1188         matchGame = 1;
1189         if (*appData.loadGameFile != NULLCHAR) {
1190             int index = appData.loadGameIndex; // [HGM] autoinc
1191             if(index<0) lastIndex = index = 1;
1192             if (!LoadGameFromFile(appData.loadGameFile,
1193                                   index,
1194                                   appData.loadGameFile, FALSE)) {
1195                 DisplayFatalError(_("Bad game file"), 0, 1);
1196                 return;
1197             }
1198         } else if (*appData.loadPositionFile != NULLCHAR) {
1199             int index = appData.loadPositionIndex; // [HGM] autoinc
1200             if(index<0) lastIndex = index = 1;
1201             if (!LoadPositionFromFile(appData.loadPositionFile,
1202                                       index,
1203                                       appData.loadPositionFile)) {
1204                 DisplayFatalError(_("Bad position file"), 0, 1);
1205                 return;
1206             }
1207         }
1208         TwoMachinesEvent();
1209     } else if (*appData.cmailGameName != NULLCHAR) {
1210         /* Set up cmail mode */
1211         ReloadCmailMsgEvent(TRUE);
1212     } else {
1213         /* Set up other modes */
1214         if (initialMode == AnalyzeFile) {
1215           if (*appData.loadGameFile == NULLCHAR) {
1216             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217             return;
1218           }
1219         }
1220         if (*appData.loadGameFile != NULLCHAR) {
1221             (void) LoadGameFromFile(appData.loadGameFile,
1222                                     appData.loadGameIndex,
1223                                     appData.loadGameFile, TRUE);
1224         } else if (*appData.loadPositionFile != NULLCHAR) {
1225             (void) LoadPositionFromFile(appData.loadPositionFile,
1226                                         appData.loadPositionIndex,
1227                                         appData.loadPositionFile);
1228             /* [HGM] try to make self-starting even after FEN load */
1229             /* to allow automatic setup of fairy variants with wtm */
1230             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231                 gameMode = BeginningOfGame;
1232                 setboardSpoiledMachineBlack = 1;
1233             }
1234             /* [HGM] loadPos: make that every new game uses the setup */
1235             /* from file as long as we do not switch variant          */
1236             if(!blackPlaysFirst) {
1237                 startedFromPositionFile = TRUE;
1238                 CopyBoard(filePosition, boards[0]);
1239             }
1240         }
1241         if (initialMode == AnalyzeMode) {
1242           if (appData.noChessProgram) {
1243             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244             return;
1245           }
1246           if (appData.icsActive) {
1247             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248             return;
1249           }
1250           AnalyzeModeEvent();
1251         } else if (initialMode == AnalyzeFile) {
1252           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253           ShowThinkingEvent();
1254           AnalyzeFileEvent();
1255           AnalysisPeriodicEvent(1);
1256         } else if (initialMode == MachinePlaysWhite) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineWhiteEvent();
1268         } else if (initialMode == MachinePlaysBlack) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           MachineBlackEvent();
1280         } else if (initialMode == TwoMachinesPlay) {
1281           if (appData.noChessProgram) {
1282             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283                               0, 2);
1284             return;
1285           }
1286           if (appData.icsActive) {
1287             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1288                               0, 2);
1289             return;
1290           }
1291           TwoMachinesEvent();
1292         } else if (initialMode == EditGame) {
1293           EditGameEvent();
1294         } else if (initialMode == EditPosition) {
1295           EditPositionEvent();
1296         } else if (initialMode == Training) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1299             return;
1300           }
1301           TrainingEvent();
1302         }
1303     }
1304 }
1305
1306 /*
1307  * Establish will establish a contact to a remote host.port.
1308  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309  *  used to talk to the host.
1310  * Returns 0 if okay, error code if not.
1311  */
1312 int
1313 establish()
1314 {
1315     char buf[MSG_SIZ];
1316
1317     if (*appData.icsCommPort != NULLCHAR) {
1318         /* Talk to the host through a serial comm port */
1319         return OpenCommPort(appData.icsCommPort, &icsPR);
1320
1321     } else if (*appData.gateway != NULLCHAR) {
1322         if (*appData.remoteShell == NULLCHAR) {
1323             /* Use the rcmd protocol to run telnet program on a gateway host */
1324             snprintf(buf, sizeof(buf), "%s %s %s",
1325                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1326             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327
1328         } else {
1329             /* Use the rsh program to run telnet program on a gateway host */
1330             if (*appData.remoteUser == NULLCHAR) {
1331                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332                         appData.gateway, appData.telnetProgram,
1333                         appData.icsHost, appData.icsPort);
1334             } else {
1335                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336                         appData.remoteShell, appData.gateway, 
1337                         appData.remoteUser, appData.telnetProgram,
1338                         appData.icsHost, appData.icsPort);
1339             }
1340             return StartChildProcess(buf, "", &icsPR);
1341
1342         }
1343     } else if (appData.useTelnet) {
1344         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345
1346     } else {
1347         /* TCP socket interface differs somewhat between
1348            Unix and NT; handle details in the front end.
1349            */
1350         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1351     }
1352 }
1353
1354 void EscapeExpand(char *p, char *q)
1355 {       // [HGM] initstring: routine to shape up string arguments
1356         while(*p++ = *q++) if(p[-1] == '\\')
1357             switch(*q++) {
1358                 case 'n': p[-1] = '\n'; break;
1359                 case 'r': p[-1] = '\r'; break;
1360                 case 't': p[-1] = '\t'; break;
1361                 case '\\': p[-1] = '\\'; break;
1362                 case 0: *p = 0; return;
1363                 default: p[-1] = q[-1]; break;
1364             }
1365 }
1366
1367 void
1368 show_bytes(fp, buf, count)
1369      FILE *fp;
1370      char *buf;
1371      int count;
1372 {
1373     while (count--) {
1374         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1375             fprintf(fp, "\\%03o", *buf & 0xff);
1376         } else {
1377             putc(*buf, fp);
1378         }
1379         buf++;
1380     }
1381     fflush(fp);
1382 }
1383
1384 /* Returns an errno value */
1385 int
1386 OutputMaybeTelnet(pr, message, count, outError)
1387      ProcRef pr;
1388      char *message;
1389      int count;
1390      int *outError;
1391 {
1392     char buf[8192], *p, *q, *buflim;
1393     int left, newcount, outcount;
1394
1395     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1396         *appData.gateway != NULLCHAR) {
1397         if (appData.debugMode) {
1398             fprintf(debugFP, ">ICS: ");
1399             show_bytes(debugFP, message, count);
1400             fprintf(debugFP, "\n");
1401         }
1402         return OutputToProcess(pr, message, count, outError);
1403     }
1404
1405     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1406     p = message;
1407     q = buf;
1408     left = count;
1409     newcount = 0;
1410     while (left) {
1411         if (q >= buflim) {
1412             if (appData.debugMode) {
1413                 fprintf(debugFP, ">ICS: ");
1414                 show_bytes(debugFP, buf, newcount);
1415                 fprintf(debugFP, "\n");
1416             }
1417             outcount = OutputToProcess(pr, buf, newcount, outError);
1418             if (outcount < newcount) return -1; /* to be sure */
1419             q = buf;
1420             newcount = 0;
1421         }
1422         if (*p == '\n') {
1423             *q++ = '\r';
1424             newcount++;
1425         } else if (((unsigned char) *p) == TN_IAC) {
1426             *q++ = (char) TN_IAC;
1427             newcount ++;
1428         }
1429         *q++ = *p++;
1430         newcount++;
1431         left--;
1432     }
1433     if (appData.debugMode) {
1434         fprintf(debugFP, ">ICS: ");
1435         show_bytes(debugFP, buf, newcount);
1436         fprintf(debugFP, "\n");
1437     }
1438     outcount = OutputToProcess(pr, buf, newcount, outError);
1439     if (outcount < newcount) return -1; /* to be sure */
1440     return count;
1441 }
1442
1443 void
1444 read_from_player(isr, closure, message, count, error)
1445      InputSourceRef isr;
1446      VOIDSTAR closure;
1447      char *message;
1448      int count;
1449      int error;
1450 {
1451     int outError, outCount;
1452     static int gotEof = 0;
1453
1454     /* Pass data read from player on to ICS */
1455     if (count > 0) {
1456         gotEof = 0;
1457         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1458         if (outCount < count) {
1459             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1460         }
1461     } else if (count < 0) {
1462         RemoveInputSource(isr);
1463         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1464     } else if (gotEof++ > 0) {
1465         RemoveInputSource(isr);
1466         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1467     }
1468 }
1469
1470 void
1471 KeepAlive()
1472 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1473     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1474     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1475     SendToICS("date\n");
1476     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1477 }
1478
1479 /* added routine for printf style output to ics */
1480 void ics_printf(char *format, ...)
1481 {
1482     char buffer[MSG_SIZ];
1483     va_list args;
1484
1485     va_start(args, format);
1486     vsnprintf(buffer, sizeof(buffer), format, args);
1487     buffer[sizeof(buffer)-1] = '\0';
1488     SendToICS(buffer);
1489     va_end(args);
1490 }
1491
1492 void
1493 SendToICS(s)
1494      char *s;
1495 {
1496     int count, outCount, outError;
1497
1498     if (icsPR == NULL) return;
1499
1500     count = strlen(s);
1501     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507 /* This is used for sending logon scripts to the ICS. Sending
1508    without a delay causes problems when using timestamp on ICC
1509    (at least on my machine). */
1510 void
1511 SendToICSDelayed(s,msdelay)
1512      char *s;
1513      long msdelay;
1514 {
1515     int count, outCount, outError;
1516
1517     if (icsPR == NULL) return;
1518
1519     count = strlen(s);
1520     if (appData.debugMode) {
1521         fprintf(debugFP, ">ICS: ");
1522         show_bytes(debugFP, s, count);
1523         fprintf(debugFP, "\n");
1524     }
1525     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1526                                       msdelay);
1527     if (outCount < count) {
1528         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1529     }
1530 }
1531
1532
1533 /* Remove all highlighting escape sequences in s
1534    Also deletes any suffix starting with '(' 
1535    */
1536 char *
1537 StripHighlightAndTitle(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             if (*s == '(' || *s == '[') {
1550                 *p = NULLCHAR;
1551                 return retbuf;
1552             }
1553             *p++ = *s++;
1554         }
1555     }
1556     *p = NULLCHAR;
1557     return retbuf;
1558 }
1559
1560 /* Remove all highlighting escape sequences in s */
1561 char *
1562 StripHighlight(s)
1563      char *s;
1564 {
1565     static char retbuf[MSG_SIZ];
1566     char *p = retbuf;
1567
1568     while (*s != NULLCHAR) {
1569         while (*s == '\033') {
1570             while (*s != NULLCHAR && !isalpha(*s)) s++;
1571             if (*s != NULLCHAR) s++;
1572         }
1573         while (*s != NULLCHAR && *s != '\033') {
1574             *p++ = *s++;
1575         }
1576     }
1577     *p = NULLCHAR;
1578     return retbuf;
1579 }
1580
1581 char *variantNames[] = VARIANT_NAMES;
1582 char *
1583 VariantName(v)
1584      VariantClass v;
1585 {
1586     return variantNames[v];
1587 }
1588
1589
1590 /* Identify a variant from the strings the chess servers use or the
1591    PGN Variant tag names we use. */
1592 VariantClass
1593 StringToVariant(e)
1594      char *e;
1595 {
1596     char *p;
1597     int wnum = -1;
1598     VariantClass v = VariantNormal;
1599     int i, found = FALSE;
1600     char buf[MSG_SIZ];
1601
1602     if (!e) return v;
1603
1604     /* [HGM] skip over optional board-size prefixes */
1605     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1606         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1607         while( *e++ != '_');
1608     }
1609
1610     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1611         v = VariantNormal;
1612         found = TRUE;
1613     } else
1614     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1615       if (StrCaseStr(e, variantNames[i])) {
1616         v = (VariantClass) i;
1617         found = TRUE;
1618         break;
1619       }
1620     }
1621
1622     if (!found) {
1623       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1624           || StrCaseStr(e, "wild/fr") 
1625           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1626         v = VariantFischeRandom;
1627       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1628                  (i = 1, p = StrCaseStr(e, "w"))) {
1629         p += i;
1630         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1631         if (isdigit(*p)) {
1632           wnum = atoi(p);
1633         } else {
1634           wnum = -1;
1635         }
1636         switch (wnum) {
1637         case 0: /* FICS only, actually */
1638         case 1:
1639           /* Castling legal even if K starts on d-file */
1640           v = VariantWildCastle;
1641           break;
1642         case 2:
1643         case 3:
1644         case 4:
1645           /* Castling illegal even if K & R happen to start in
1646              normal positions. */
1647           v = VariantNoCastle;
1648           break;
1649         case 5:
1650         case 7:
1651         case 8:
1652         case 10:
1653         case 11:
1654         case 12:
1655         case 13:
1656         case 14:
1657         case 15:
1658         case 18:
1659         case 19:
1660           /* Castling legal iff K & R start in normal positions */
1661           v = VariantNormal;
1662           break;
1663         case 6:
1664         case 20:
1665         case 21:
1666           /* Special wilds for position setup; unclear what to do here */
1667           v = VariantLoadable;
1668           break;
1669         case 9:
1670           /* Bizarre ICC game */
1671           v = VariantTwoKings;
1672           break;
1673         case 16:
1674           v = VariantKriegspiel;
1675           break;
1676         case 17:
1677           v = VariantLosers;
1678           break;
1679         case 22:
1680           v = VariantFischeRandom;
1681           break;
1682         case 23:
1683           v = VariantCrazyhouse;
1684           break;
1685         case 24:
1686           v = VariantBughouse;
1687           break;
1688         case 25:
1689           v = Variant3Check;
1690           break;
1691         case 26:
1692           /* Not quite the same as FICS suicide! */
1693           v = VariantGiveaway;
1694           break;
1695         case 27:
1696           v = VariantAtomic;
1697           break;
1698         case 28:
1699           v = VariantShatranj;
1700           break;
1701
1702         /* Temporary names for future ICC types.  The name *will* change in 
1703            the next xboard/WinBoard release after ICC defines it. */
1704         case 29:
1705           v = Variant29;
1706           break;
1707         case 30:
1708           v = Variant30;
1709           break;
1710         case 31:
1711           v = Variant31;
1712           break;
1713         case 32:
1714           v = Variant32;
1715           break;
1716         case 33:
1717           v = Variant33;
1718           break;
1719         case 34:
1720           v = Variant34;
1721           break;
1722         case 35:
1723           v = Variant35;
1724           break;
1725         case 36:
1726           v = Variant36;
1727           break;
1728         case 37:
1729           v = VariantShogi;
1730           break;
1731         case 38:
1732           v = VariantXiangqi;
1733           break;
1734         case 39:
1735           v = VariantCourier;
1736           break;
1737         case 40:
1738           v = VariantGothic;
1739           break;
1740         case 41:
1741           v = VariantCapablanca;
1742           break;
1743         case 42:
1744           v = VariantKnightmate;
1745           break;
1746         case 43:
1747           v = VariantFairy;
1748           break;
1749         case 44:
1750           v = VariantCylinder;
1751           break;
1752         case 45:
1753           v = VariantFalcon;
1754           break;
1755         case 46:
1756           v = VariantCapaRandom;
1757           break;
1758         case 47:
1759           v = VariantBerolina;
1760           break;
1761         case 48:
1762           v = VariantJanus;
1763           break;
1764         case 49:
1765           v = VariantSuper;
1766           break;
1767         case 50:
1768           v = VariantGreat;
1769           break;
1770         case -1:
1771           /* Found "wild" or "w" in the string but no number;
1772              must assume it's normal chess. */
1773           v = VariantNormal;
1774           break;
1775         default:
1776           sprintf(buf, _("Unknown wild type %d"), wnum);
1777           DisplayError(buf, 0);
1778           v = VariantUnknown;
1779           break;
1780         }
1781       }
1782     }
1783     if (appData.debugMode) {
1784       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1785               e, wnum, VariantName(v));
1786     }
1787     return v;
1788 }
1789
1790 static int leftover_start = 0, leftover_len = 0;
1791 char star_match[STAR_MATCH_N][MSG_SIZ];
1792
1793 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1794    advance *index beyond it, and set leftover_start to the new value of
1795    *index; else return FALSE.  If pattern contains the character '*', it
1796    matches any sequence of characters not containing '\r', '\n', or the
1797    character following the '*' (if any), and the matched sequence(s) are
1798    copied into star_match.
1799    */
1800 int
1801 looking_at(buf, index, pattern)
1802      char *buf;
1803      int *index;
1804      char *pattern;
1805 {
1806     char *bufp = &buf[*index], *patternp = pattern;
1807     int star_count = 0;
1808     char *matchp = star_match[0];
1809     
1810     for (;;) {
1811         if (*patternp == NULLCHAR) {
1812             *index = leftover_start = bufp - buf;
1813             *matchp = NULLCHAR;
1814             return TRUE;
1815         }
1816         if (*bufp == NULLCHAR) return FALSE;
1817         if (*patternp == '*') {
1818             if (*bufp == *(patternp + 1)) {
1819                 *matchp = NULLCHAR;
1820                 matchp = star_match[++star_count];
1821                 patternp += 2;
1822                 bufp++;
1823                 continue;
1824             } else if (*bufp == '\n' || *bufp == '\r') {
1825                 patternp++;
1826                 if (*patternp == NULLCHAR)
1827                   continue;
1828                 else
1829                   return FALSE;
1830             } else {
1831                 *matchp++ = *bufp++;
1832                 continue;
1833             }
1834         }
1835         if (*patternp != *bufp) return FALSE;
1836         patternp++;
1837         bufp++;
1838     }
1839 }
1840
1841 void
1842 SendToPlayer(data, length)
1843      char *data;
1844      int length;
1845 {
1846     int error, outCount;
1847     outCount = OutputToProcess(NoProc, data, length, &error);
1848     if (outCount < length) {
1849         DisplayFatalError(_("Error writing to display"), error, 1);
1850     }
1851 }
1852
1853 void
1854 PackHolding(packed, holding)
1855      char packed[];
1856      char *holding;
1857 {
1858     char *p = holding;
1859     char *q = packed;
1860     int runlength = 0;
1861     int curr = 9999;
1862     do {
1863         if (*p == curr) {
1864             runlength++;
1865         } else {
1866             switch (runlength) {
1867               case 0:
1868                 break;
1869               case 1:
1870                 *q++ = curr;
1871                 break;
1872               case 2:
1873                 *q++ = curr;
1874                 *q++ = curr;
1875                 break;
1876               default:
1877                 sprintf(q, "%d", runlength);
1878                 while (*q) q++;
1879                 *q++ = curr;
1880                 break;
1881             }
1882             runlength = 1;
1883             curr = *p;
1884         }
1885     } while (*p++);
1886     *q = NULLCHAR;
1887 }
1888
1889 /* Telnet protocol requests from the front end */
1890 void
1891 TelnetRequest(ddww, option)
1892      unsigned char ddww, option;
1893 {
1894     unsigned char msg[3];
1895     int outCount, outError;
1896
1897     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1898
1899     if (appData.debugMode) {
1900         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1901         switch (ddww) {
1902           case TN_DO:
1903             ddwwStr = "DO";
1904             break;
1905           case TN_DONT:
1906             ddwwStr = "DONT";
1907             break;
1908           case TN_WILL:
1909             ddwwStr = "WILL";
1910             break;
1911           case TN_WONT:
1912             ddwwStr = "WONT";
1913             break;
1914           default:
1915             ddwwStr = buf1;
1916             sprintf(buf1, "%d", ddww);
1917             break;
1918         }
1919         switch (option) {
1920           case TN_ECHO:
1921             optionStr = "ECHO";
1922             break;
1923           default:
1924             optionStr = buf2;
1925             sprintf(buf2, "%d", option);
1926             break;
1927         }
1928         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1929     }
1930     msg[0] = TN_IAC;
1931     msg[1] = ddww;
1932     msg[2] = option;
1933     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1934     if (outCount < 3) {
1935         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1936     }
1937 }
1938
1939 void
1940 DoEcho()
1941 {
1942     if (!appData.icsActive) return;
1943     TelnetRequest(TN_DO, TN_ECHO);
1944 }
1945
1946 void
1947 DontEcho()
1948 {
1949     if (!appData.icsActive) return;
1950     TelnetRequest(TN_DONT, TN_ECHO);
1951 }
1952
1953 void
1954 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1955 {
1956     /* put the holdings sent to us by the server on the board holdings area */
1957     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1958     char p;
1959     ChessSquare piece;
1960
1961     if(gameInfo.holdingsWidth < 2)  return;
1962     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1963         return; // prevent overwriting by pre-board holdings
1964
1965     if( (int)lowestPiece >= BlackPawn ) {
1966         holdingsColumn = 0;
1967         countsColumn = 1;
1968         holdingsStartRow = BOARD_HEIGHT-1;
1969         direction = -1;
1970     } else {
1971         holdingsColumn = BOARD_WIDTH-1;
1972         countsColumn = BOARD_WIDTH-2;
1973         holdingsStartRow = 0;
1974         direction = 1;
1975     }
1976
1977     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1978         board[i][holdingsColumn] = EmptySquare;
1979         board[i][countsColumn]   = (ChessSquare) 0;
1980     }
1981     while( (p=*holdings++) != NULLCHAR ) {
1982         piece = CharToPiece( ToUpper(p) );
1983         if(piece == EmptySquare) continue;
1984         /*j = (int) piece - (int) WhitePawn;*/
1985         j = PieceToNumber(piece);
1986         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1987         if(j < 0) continue;               /* should not happen */
1988         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1989         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1990         board[holdingsStartRow+j*direction][countsColumn]++;
1991     }
1992 }
1993
1994
1995 void
1996 VariantSwitch(Board board, VariantClass newVariant)
1997 {
1998    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1999    static Board oldBoard;
2000
2001    startedFromPositionFile = FALSE;
2002    if(gameInfo.variant == newVariant) return;
2003
2004    /* [HGM] This routine is called each time an assignment is made to
2005     * gameInfo.variant during a game, to make sure the board sizes
2006     * are set to match the new variant. If that means adding or deleting
2007     * holdings, we shift the playing board accordingly
2008     * This kludge is needed because in ICS observe mode, we get boards
2009     * of an ongoing game without knowing the variant, and learn about the
2010     * latter only later. This can be because of the move list we requested,
2011     * in which case the game history is refilled from the beginning anyway,
2012     * but also when receiving holdings of a crazyhouse game. In the latter
2013     * case we want to add those holdings to the already received position.
2014     */
2015
2016    
2017    if (appData.debugMode) {
2018      fprintf(debugFP, "Switch board from %s to %s\n",
2019              VariantName(gameInfo.variant), VariantName(newVariant));
2020      setbuf(debugFP, NULL);
2021    }
2022    shuffleOpenings = 0;       /* [HGM] shuffle */
2023    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2024    switch(newVariant) 
2025      {
2026      case VariantShogi:
2027        newWidth = 9;  newHeight = 9;
2028        gameInfo.holdingsSize = 7;
2029      case VariantBughouse:
2030      case VariantCrazyhouse:
2031        newHoldingsWidth = 2; break;
2032      case VariantGreat:
2033        newWidth = 10;
2034      case VariantSuper:
2035        newHoldingsWidth = 2;
2036        gameInfo.holdingsSize = 8;
2037        break;
2038      case VariantGothic:
2039      case VariantCapablanca:
2040      case VariantCapaRandom:
2041        newWidth = 10;
2042      default:
2043        newHoldingsWidth = gameInfo.holdingsSize = 0;
2044      };
2045    
2046    if(newWidth  != gameInfo.boardWidth  ||
2047       newHeight != gameInfo.boardHeight ||
2048       newHoldingsWidth != gameInfo.holdingsWidth ) {
2049      
2050      /* shift position to new playing area, if needed */
2051      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2052        for(i=0; i<BOARD_HEIGHT; i++) 
2053          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2054            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2055              board[i][j];
2056        for(i=0; i<newHeight; i++) {
2057          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2058          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2059        }
2060      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2061        for(i=0; i<BOARD_HEIGHT; i++)
2062          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2063            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2064              board[i][j];
2065      }
2066      gameInfo.boardWidth  = newWidth;
2067      gameInfo.boardHeight = newHeight;
2068      gameInfo.holdingsWidth = newHoldingsWidth;
2069      gameInfo.variant = newVariant;
2070      InitDrawingSizes(-2, 0);
2071    } else gameInfo.variant = newVariant;
2072    CopyBoard(oldBoard, board);   // remember correctly formatted board
2073      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2074    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2075 }
2076
2077 static int loggedOn = FALSE;
2078
2079 /*-- Game start info cache: --*/
2080 int gs_gamenum;
2081 char gs_kind[MSG_SIZ];
2082 static char player1Name[128] = "";
2083 static char player2Name[128] = "";
2084 static char cont_seq[] = "\n\\   ";
2085 static int player1Rating = -1;
2086 static int player2Rating = -1;
2087 /*----------------------------*/
2088
2089 ColorClass curColor = ColorNormal;
2090 int suppressKibitz = 0;
2091
2092 // [HGM] seekgraph
2093 Boolean soughtPending = FALSE;
2094 Boolean seekGraphUp;
2095 #define MAX_SEEK_ADS 200
2096 #define SQUARE 0x80
2097 char *seekAdList[MAX_SEEK_ADS];
2098 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2099 float tcList[MAX_SEEK_ADS];
2100 char colorList[MAX_SEEK_ADS];
2101 int nrOfSeekAds = 0;
2102 int minRating = 1010, maxRating = 2800;
2103 int hMargin = 10, vMargin = 20, h, w;
2104 extern int squareSize, lineGap;
2105
2106 void
2107 PlotSeekAd(int i)
2108 {
2109         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2110         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2111         if(r < minRating+100 && r >=0 ) r = minRating+100;
2112         if(r > maxRating) r = maxRating;
2113         if(tc < 1.) tc = 1.;
2114         if(tc > 95.) tc = 95.;
2115         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2116         y = ((double)r - minRating)/(maxRating - minRating)
2117             * (h-vMargin-squareSize/8-1) + vMargin;
2118         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2119         if(strstr(seekAdList[i], " u ")) color = 1;
2120         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2121            !strstr(seekAdList[i], "bullet") &&
2122            !strstr(seekAdList[i], "blitz") &&
2123            !strstr(seekAdList[i], "standard") ) color = 2;
2124         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2125         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2126 }
2127
2128 void
2129 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2130 {
2131         char buf[MSG_SIZ], *ext = "";
2132         VariantClass v = StringToVariant(type);
2133         if(strstr(type, "wild")) {
2134             ext = type + 4; // append wild number
2135             if(v == VariantFischeRandom) type = "chess960"; else
2136             if(v == VariantLoadable) type = "setup"; else
2137             type = VariantName(v);
2138         }
2139         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2140         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2141             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2142             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2143             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2144             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2145             seekNrList[nrOfSeekAds] = nr;
2146             zList[nrOfSeekAds] = 0;
2147             seekAdList[nrOfSeekAds++] = StrSave(buf);
2148             if(plot) PlotSeekAd(nrOfSeekAds-1);
2149         }
2150 }
2151
2152 void
2153 EraseSeekDot(int i)
2154 {
2155     int x = xList[i], y = yList[i], d=squareSize/4, k;
2156     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2157     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2158     // now replot every dot that overlapped
2159     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2160         int xx = xList[k], yy = yList[k];
2161         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2162             DrawSeekDot(xx, yy, colorList[k]);
2163     }
2164 }
2165
2166 void
2167 RemoveSeekAd(int nr)
2168 {
2169         int i;
2170         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2171             EraseSeekDot(i);
2172             if(seekAdList[i]) free(seekAdList[i]);
2173             seekAdList[i] = seekAdList[--nrOfSeekAds];
2174             seekNrList[i] = seekNrList[nrOfSeekAds];
2175             ratingList[i] = ratingList[nrOfSeekAds];
2176             colorList[i]  = colorList[nrOfSeekAds];
2177             tcList[i] = tcList[nrOfSeekAds];
2178             xList[i]  = xList[nrOfSeekAds];
2179             yList[i]  = yList[nrOfSeekAds];
2180             zList[i]  = zList[nrOfSeekAds];
2181             seekAdList[nrOfSeekAds] = NULL;
2182             break;
2183         }
2184 }
2185
2186 Boolean
2187 MatchSoughtLine(char *line)
2188 {
2189     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2190     int nr, base, inc, u=0; char dummy;
2191
2192     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2193        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2194        (u=1) &&
2195        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2196         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2197         // match: compact and save the line
2198         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2199         return TRUE;
2200     }
2201     return FALSE;
2202 }
2203
2204 int
2205 DrawSeekGraph()
2206 {
2207     if(!seekGraphUp) return FALSE;
2208     int i;
2209     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2210     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2211
2212     DrawSeekBackground(0, 0, w, h);
2213     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2214     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2215     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2216         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2217         yy = h-1-yy;
2218         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2219         if(i%500 == 0) {
2220             char buf[MSG_SIZ];
2221             sprintf(buf, "%d", i);
2222             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2223         }
2224     }
2225     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2226     for(i=1; i<100; i+=(i<10?1:5)) {
2227         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2228         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2229         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2230             char buf[MSG_SIZ];
2231             sprintf(buf, "%d", i);
2232             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2233         }
2234     }
2235     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2236     return TRUE;
2237 }
2238
2239 int SeekGraphClick(ClickType click, int x, int y, int moving)
2240 {
2241     static int lastDown = 0, displayed = 0, lastSecond;
2242     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2243         if(click == Release || moving) return FALSE;
2244         nrOfSeekAds = 0;
2245         soughtPending = TRUE;
2246         SendToICS(ics_prefix);
2247         SendToICS("sought\n"); // should this be "sought all"?
2248     } else { // issue challenge based on clicked ad
2249         int dist = 10000; int i, closest = 0, second = 0;
2250         for(i=0; i<nrOfSeekAds; i++) {
2251             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2252             if(d < dist) { dist = d; closest = i; }
2253             second += (d - zList[i] < 120); // count in-range ads
2254             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2255         }
2256         if(dist < 120) {
2257             char buf[MSG_SIZ];
2258             second = (second > 1);
2259             if(displayed != closest || second != lastSecond) {
2260                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2261                 lastSecond = second; displayed = closest;
2262             }
2263             if(click == Press) {
2264                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2265                 lastDown = closest;
2266                 return TRUE;
2267             } // on press 'hit', only show info
2268             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2269             sprintf(buf, "play %d\n", seekNrList[closest]);
2270             SendToICS(ics_prefix);
2271             SendToICS(buf);
2272             return TRUE; // let incoming board of started game pop down the graph
2273         } else if(click == Release) { // release 'miss' is ignored
2274             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2275             if(moving == 2) { // right up-click
2276                 nrOfSeekAds = 0; // refresh graph
2277                 soughtPending = TRUE;
2278                 SendToICS(ics_prefix);
2279                 SendToICS("sought\n"); // should this be "sought all"?
2280             }
2281             return TRUE;
2282         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2283         // press miss or release hit 'pop down' seek graph
2284         seekGraphUp = FALSE;
2285         DrawPosition(TRUE, NULL);
2286     }
2287     return TRUE;
2288 }
2289
2290 void
2291 read_from_ics(isr, closure, data, count, error)
2292      InputSourceRef isr;
2293      VOIDSTAR closure;
2294      char *data;
2295      int count;
2296      int error;
2297 {
2298 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2299 #define STARTED_NONE 0
2300 #define STARTED_MOVES 1
2301 #define STARTED_BOARD 2
2302 #define STARTED_OBSERVE 3
2303 #define STARTED_HOLDINGS 4
2304 #define STARTED_CHATTER 5
2305 #define STARTED_COMMENT 6
2306 #define STARTED_MOVES_NOHIDE 7
2307     
2308     static int started = STARTED_NONE;
2309     static char parse[20000];
2310     static int parse_pos = 0;
2311     static char buf[BUF_SIZE + 1];
2312     static int firstTime = TRUE, intfSet = FALSE;
2313     static ColorClass prevColor = ColorNormal;
2314     static int savingComment = FALSE;
2315     static int cmatch = 0; // continuation sequence match
2316     char *bp;
2317     char str[500];
2318     int i, oldi;
2319     int buf_len;
2320     int next_out;
2321     int tkind;
2322     int backup;    /* [DM] For zippy color lines */
2323     char *p;
2324     char talker[MSG_SIZ]; // [HGM] chat
2325     int channel;
2326
2327     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2328
2329     if (appData.debugMode) {
2330       if (!error) {
2331         fprintf(debugFP, "<ICS: ");
2332         show_bytes(debugFP, data, count);
2333         fprintf(debugFP, "\n");
2334       }
2335     }
2336
2337     if (appData.debugMode) { int f = forwardMostMove;
2338         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2339                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2340                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2341     }
2342     if (count > 0) {
2343         /* If last read ended with a partial line that we couldn't parse,
2344            prepend it to the new read and try again. */
2345         if (leftover_len > 0) {
2346             for (i=0; i<leftover_len; i++)
2347               buf[i] = buf[leftover_start + i];
2348         }
2349
2350     /* copy new characters into the buffer */
2351     bp = buf + leftover_len;
2352     buf_len=leftover_len;
2353     for (i=0; i<count; i++)
2354     {
2355         // ignore these
2356         if (data[i] == '\r')
2357             continue;
2358
2359         // join lines split by ICS?
2360         if (!appData.noJoin)
2361         {
2362             /*
2363                 Joining just consists of finding matches against the
2364                 continuation sequence, and discarding that sequence
2365                 if found instead of copying it.  So, until a match
2366                 fails, there's nothing to do since it might be the
2367                 complete sequence, and thus, something we don't want
2368                 copied.
2369             */
2370             if (data[i] == cont_seq[cmatch])
2371             {
2372                 cmatch++;
2373                 if (cmatch == strlen(cont_seq))
2374                 {
2375                     cmatch = 0; // complete match.  just reset the counter
2376
2377                     /*
2378                         it's possible for the ICS to not include the space
2379                         at the end of the last word, making our [correct]
2380                         join operation fuse two separate words.  the server
2381                         does this when the space occurs at the width setting.
2382                     */
2383                     if (!buf_len || buf[buf_len-1] != ' ')
2384                     {
2385                         *bp++ = ' ';
2386                         buf_len++;
2387                     }
2388                 }
2389                 continue;
2390             }
2391             else if (cmatch)
2392             {
2393                 /*
2394                     match failed, so we have to copy what matched before
2395                     falling through and copying this character.  In reality,
2396                     this will only ever be just the newline character, but
2397                     it doesn't hurt to be precise.
2398                 */
2399                 strncpy(bp, cont_seq, cmatch);
2400                 bp += cmatch;
2401                 buf_len += cmatch;
2402                 cmatch = 0;
2403             }
2404         }
2405
2406         // copy this char
2407         *bp++ = data[i];
2408         buf_len++;
2409     }
2410
2411         buf[buf_len] = NULLCHAR;
2412 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2413         next_out = 0;
2414         leftover_start = 0;
2415         
2416         i = 0;
2417         while (i < buf_len) {
2418             /* Deal with part of the TELNET option negotiation
2419                protocol.  We refuse to do anything beyond the
2420                defaults, except that we allow the WILL ECHO option,
2421                which ICS uses to turn off password echoing when we are
2422                directly connected to it.  We reject this option
2423                if localLineEditing mode is on (always on in xboard)
2424                and we are talking to port 23, which might be a real
2425                telnet server that will try to keep WILL ECHO on permanently.
2426              */
2427             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2428                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2429                 unsigned char option;
2430                 oldi = i;
2431                 switch ((unsigned char) buf[++i]) {
2432                   case TN_WILL:
2433                     if (appData.debugMode)
2434                       fprintf(debugFP, "\n<WILL ");
2435                     switch (option = (unsigned char) buf[++i]) {
2436                       case TN_ECHO:
2437                         if (appData.debugMode)
2438                           fprintf(debugFP, "ECHO ");
2439                         /* Reply only if this is a change, according
2440                            to the protocol rules. */
2441                         if (remoteEchoOption) break;
2442                         if (appData.localLineEditing &&
2443                             atoi(appData.icsPort) == TN_PORT) {
2444                             TelnetRequest(TN_DONT, TN_ECHO);
2445                         } else {
2446                             EchoOff();
2447                             TelnetRequest(TN_DO, TN_ECHO);
2448                             remoteEchoOption = TRUE;
2449                         }
2450                         break;
2451                       default:
2452                         if (appData.debugMode)
2453                           fprintf(debugFP, "%d ", option);
2454                         /* Whatever this is, we don't want it. */
2455                         TelnetRequest(TN_DONT, option);
2456                         break;
2457                     }
2458                     break;
2459                   case TN_WONT:
2460                     if (appData.debugMode)
2461                       fprintf(debugFP, "\n<WONT ");
2462                     switch (option = (unsigned char) buf[++i]) {
2463                       case TN_ECHO:
2464                         if (appData.debugMode)
2465                           fprintf(debugFP, "ECHO ");
2466                         /* Reply only if this is a change, according
2467                            to the protocol rules. */
2468                         if (!remoteEchoOption) break;
2469                         EchoOn();
2470                         TelnetRequest(TN_DONT, TN_ECHO);
2471                         remoteEchoOption = FALSE;
2472                         break;
2473                       default:
2474                         if (appData.debugMode)
2475                           fprintf(debugFP, "%d ", (unsigned char) option);
2476                         /* Whatever this is, it must already be turned
2477                            off, because we never agree to turn on
2478                            anything non-default, so according to the
2479                            protocol rules, we don't reply. */
2480                         break;
2481                     }
2482                     break;
2483                   case TN_DO:
2484                     if (appData.debugMode)
2485                       fprintf(debugFP, "\n<DO ");
2486                     switch (option = (unsigned char) buf[++i]) {
2487                       default:
2488                         /* Whatever this is, we refuse to do it. */
2489                         if (appData.debugMode)
2490                           fprintf(debugFP, "%d ", option);
2491                         TelnetRequest(TN_WONT, option);
2492                         break;
2493                     }
2494                     break;
2495                   case TN_DONT:
2496                     if (appData.debugMode)
2497                       fprintf(debugFP, "\n<DONT ");
2498                     switch (option = (unsigned char) buf[++i]) {
2499                       default:
2500                         if (appData.debugMode)
2501                           fprintf(debugFP, "%d ", option);
2502                         /* Whatever this is, we are already not doing
2503                            it, because we never agree to do anything
2504                            non-default, so according to the protocol
2505                            rules, we don't reply. */
2506                         break;
2507                     }
2508                     break;
2509                   case TN_IAC:
2510                     if (appData.debugMode)
2511                       fprintf(debugFP, "\n<IAC ");
2512                     /* Doubled IAC; pass it through */
2513                     i--;
2514                     break;
2515                   default:
2516                     if (appData.debugMode)
2517                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2518                     /* Drop all other telnet commands on the floor */
2519                     break;
2520                 }
2521                 if (oldi > next_out)
2522                   SendToPlayer(&buf[next_out], oldi - next_out);
2523                 if (++i > next_out)
2524                   next_out = i;
2525                 continue;
2526             }
2527                 
2528             /* OK, this at least will *usually* work */
2529             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2530                 loggedOn = TRUE;
2531             }
2532             
2533             if (loggedOn && !intfSet) {
2534                 if (ics_type == ICS_ICC) {
2535                   sprintf(str,
2536                           "/set-quietly interface %s\n/set-quietly style 12\n",
2537                           programVersion);
2538                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2539                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2540                 } else if (ics_type == ICS_CHESSNET) {
2541                   sprintf(str, "/style 12\n");
2542                 } else {
2543                   strcpy(str, "alias $ @\n$set interface ");
2544                   strcat(str, programVersion);
2545                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2546                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2547                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2548 #ifdef WIN32
2549                   strcat(str, "$iset nohighlight 1\n");
2550 #endif
2551                   strcat(str, "$iset lock 1\n$style 12\n");
2552                 }
2553                 SendToICS(str);
2554                 NotifyFrontendLogin();
2555                 intfSet = TRUE;
2556             }
2557
2558             if (started == STARTED_COMMENT) {
2559                 /* Accumulate characters in comment */
2560                 parse[parse_pos++] = buf[i];
2561                 if (buf[i] == '\n') {
2562                     parse[parse_pos] = NULLCHAR;
2563                     if(chattingPartner>=0) {
2564                         char mess[MSG_SIZ];
2565                         sprintf(mess, "%s%s", talker, parse);
2566                         OutputChatMessage(chattingPartner, mess);
2567                         chattingPartner = -1;
2568                         next_out = i+1; // [HGM] suppress printing in ICS window
2569                     } else
2570                     if(!suppressKibitz) // [HGM] kibitz
2571                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2572                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2573                         int nrDigit = 0, nrAlph = 0, j;
2574                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2575                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2576                         parse[parse_pos] = NULLCHAR;
2577                         // try to be smart: if it does not look like search info, it should go to
2578                         // ICS interaction window after all, not to engine-output window.
2579                         for(j=0; j<parse_pos; j++) { // count letters and digits
2580                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2581                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2582                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2583                         }
2584                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2585                             int depth=0; float score;
2586                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2587                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2588                                 pvInfoList[forwardMostMove-1].depth = depth;
2589                                 pvInfoList[forwardMostMove-1].score = 100*score;
2590                             }
2591                             OutputKibitz(suppressKibitz, parse);
2592                         } else {
2593                             char tmp[MSG_SIZ];
2594                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2595                             SendToPlayer(tmp, strlen(tmp));
2596                         }
2597                         next_out = i+1; // [HGM] suppress printing in ICS window
2598                     }
2599                     started = STARTED_NONE;
2600                 } else {
2601                     /* Don't match patterns against characters in comment */
2602                     i++;
2603                     continue;
2604                 }
2605             }
2606             if (started == STARTED_CHATTER) {
2607                 if (buf[i] != '\n') {
2608                     /* Don't match patterns against characters in chatter */
2609                     i++;
2610                     continue;
2611                 }
2612                 started = STARTED_NONE;
2613                 if(suppressKibitz) next_out = i+1;
2614             }
2615
2616             /* Kludge to deal with rcmd protocol */
2617             if (firstTime && looking_at(buf, &i, "\001*")) {
2618                 DisplayFatalError(&buf[1], 0, 1);
2619                 continue;
2620             } else {
2621                 firstTime = FALSE;
2622             }
2623
2624             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2625                 ics_type = ICS_ICC;
2626                 ics_prefix = "/";
2627                 if (appData.debugMode)
2628                   fprintf(debugFP, "ics_type %d\n", ics_type);
2629                 continue;
2630             }
2631             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2632                 ics_type = ICS_FICS;
2633                 ics_prefix = "$";
2634                 if (appData.debugMode)
2635                   fprintf(debugFP, "ics_type %d\n", ics_type);
2636                 continue;
2637             }
2638             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2639                 ics_type = ICS_CHESSNET;
2640                 ics_prefix = "/";
2641                 if (appData.debugMode)
2642                   fprintf(debugFP, "ics_type %d\n", ics_type);
2643                 continue;
2644             }
2645
2646             if (!loggedOn &&
2647                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2648                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2649                  looking_at(buf, &i, "will be \"*\""))) {
2650               strcpy(ics_handle, star_match[0]);
2651               continue;
2652             }
2653
2654             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2655               char buf[MSG_SIZ];
2656               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2657               DisplayIcsInteractionTitle(buf);
2658               have_set_title = TRUE;
2659             }
2660
2661             /* skip finger notes */
2662             if (started == STARTED_NONE &&
2663                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2664                  (buf[i] == '1' && buf[i+1] == '0')) &&
2665                 buf[i+2] == ':' && buf[i+3] == ' ') {
2666               started = STARTED_CHATTER;
2667               i += 3;
2668               continue;
2669             }
2670
2671             oldi = i;
2672             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2673             if(appData.seekGraph) {
2674                 if(soughtPending && MatchSoughtLine(buf+i)) {
2675                     i = strstr(buf+i, "rated") - buf;
2676                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2677                     next_out = leftover_start = i;
2678                     started = STARTED_CHATTER;
2679                     suppressKibitz = TRUE;
2680                     continue;
2681                 }
2682                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2683                         && looking_at(buf, &i, "* ads displayed")) {
2684                     soughtPending = FALSE;
2685                     seekGraphUp = TRUE;
2686                     DrawSeekGraph();
2687                     continue;
2688                 }
2689                 if(appData.autoRefresh) {
2690                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2691                         int s = (ics_type == ICS_ICC); // ICC format differs
2692                         if(seekGraphUp)
2693                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2694                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2695                         looking_at(buf, &i, "*% "); // eat prompt
2696                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2697                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2698                         next_out = i; // suppress
2699                         continue;
2700                     }
2701                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2702                         char *p = star_match[0];
2703                         while(*p) {
2704                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2705                             while(*p && *p++ != ' '); // next
2706                         }
2707                         looking_at(buf, &i, "*% "); // eat prompt
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         continue;
2711                     }
2712                 }
2713             }
2714
2715             /* skip formula vars */
2716             if (started == STARTED_NONE &&
2717                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2718               started = STARTED_CHATTER;
2719               i += 3;
2720               continue;
2721             }
2722
2723             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2724             if (appData.autoKibitz && started == STARTED_NONE && 
2725                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2726                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2727                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2728                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2729                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2730                         suppressKibitz = TRUE;
2731                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2732                         next_out = i;
2733                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2734                                 && (gameMode == IcsPlayingWhite)) ||
2735                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2736                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2737                             started = STARTED_CHATTER; // own kibitz we simply discard
2738                         else {
2739                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2740                             parse_pos = 0; parse[0] = NULLCHAR;
2741                             savingComment = TRUE;
2742                             suppressKibitz = gameMode != IcsObserving ? 2 :
2743                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2744                         } 
2745                         continue;
2746                 } else
2747                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2748                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2749                          && atoi(star_match[0])) {
2750                     // suppress the acknowledgements of our own autoKibitz
2751                     char *p;
2752                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2754                     SendToPlayer(star_match[0], strlen(star_match[0]));
2755                     if(looking_at(buf, &i, "*% ")) // eat prompt
2756                         suppressKibitz = FALSE;
2757                     next_out = i;
2758                     continue;
2759                 }
2760             } // [HGM] kibitz: end of patch
2761
2762             // [HGM] chat: intercept tells by users for which we have an open chat window
2763             channel = -1;
2764             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2765                                            looking_at(buf, &i, "* whispers:") ||
2766                                            looking_at(buf, &i, "* kibitzes:") ||
2767                                            looking_at(buf, &i, "* shouts:") ||
2768                                            looking_at(buf, &i, "* c-shouts:") ||
2769                                            looking_at(buf, &i, "--> * ") ||
2770                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2771                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2772                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2773                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2774                 int p;
2775                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2776                 chattingPartner = -1;
2777
2778                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2779                 for(p=0; p<MAX_CHAT; p++) {
2780                     if(channel == atoi(chatPartner[p])) {
2781                     talker[0] = '['; strcat(talker, "] ");
2782                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2783                     chattingPartner = p; break;
2784                     }
2785                 } else
2786                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2787                 for(p=0; p<MAX_CHAT; p++) {
2788                     if(!strcmp("kibitzes", chatPartner[p])) {
2789                         talker[0] = '['; strcat(talker, "] ");
2790                         chattingPartner = p; break;
2791                     }
2792                 } else
2793                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2794                 for(p=0; p<MAX_CHAT; p++) {
2795                     if(!strcmp("whispers", chatPartner[p])) {
2796                         talker[0] = '['; strcat(talker, "] ");
2797                         chattingPartner = p; break;
2798                     }
2799                 } else
2800                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2801                   if(buf[i-8] == '-' && buf[i-3] == 't')
2802                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2803                     if(!strcmp("c-shouts", chatPartner[p])) {
2804                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2805                         chattingPartner = p; break;
2806                     }
2807                   }
2808                   if(chattingPartner < 0)
2809                   for(p=0; p<MAX_CHAT; p++) {
2810                     if(!strcmp("shouts", chatPartner[p])) {
2811                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2812                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2813                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2814                         chattingPartner = p; break;
2815                     }
2816                   }
2817                 }
2818                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2819                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2820                     talker[0] = 0; Colorize(ColorTell, FALSE);
2821                     chattingPartner = p; break;
2822                 }
2823                 if(chattingPartner<0) i = oldi; else {
2824                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2825                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2826                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2827                     started = STARTED_COMMENT;
2828                     parse_pos = 0; parse[0] = NULLCHAR;
2829                     savingComment = 3 + chattingPartner; // counts as TRUE
2830                     suppressKibitz = TRUE;
2831                     continue;
2832                 }
2833             } // [HGM] chat: end of patch
2834
2835             if (appData.zippyTalk || appData.zippyPlay) {
2836                 /* [DM] Backup address for color zippy lines */
2837                 backup = i;
2838 #if ZIPPY
2839        #ifdef WIN32
2840                if (loggedOn == TRUE)
2841                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2842                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2843        #else
2844                 if (ZippyControl(buf, &i) ||
2845                     ZippyConverse(buf, &i) ||
2846                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2847                       loggedOn = TRUE;
2848                       if (!appData.colorize) continue;
2849                 }
2850        #endif
2851 #endif
2852             } // [DM] 'else { ' deleted
2853                 if (
2854                     /* Regular tells and says */
2855                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2856                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2857                     looking_at(buf, &i, "* says: ") ||
2858                     /* Don't color "message" or "messages" output */
2859                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2860                     looking_at(buf, &i, "*. * at *:*: ") ||
2861                     looking_at(buf, &i, "--* (*:*): ") ||
2862                     /* Message notifications (same color as tells) */
2863                     looking_at(buf, &i, "* has left a message ") ||
2864                     looking_at(buf, &i, "* just sent you a message:\n") ||
2865                     /* Whispers and kibitzes */
2866                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2867                     looking_at(buf, &i, "* kibitzes: ") ||
2868                     /* Channel tells */
2869                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2870
2871                   if (tkind == 1 && strchr(star_match[0], ':')) {
2872                       /* Avoid "tells you:" spoofs in channels */
2873                      tkind = 3;
2874                   }
2875                   if (star_match[0][0] == NULLCHAR ||
2876                       strchr(star_match[0], ' ') ||
2877                       (tkind == 3 && strchr(star_match[1], ' '))) {
2878                     /* Reject bogus matches */
2879                     i = oldi;
2880                   } else {
2881                     if (appData.colorize) {
2882                       if (oldi > next_out) {
2883                         SendToPlayer(&buf[next_out], oldi - next_out);
2884                         next_out = oldi;
2885                       }
2886                       switch (tkind) {
2887                       case 1:
2888                         Colorize(ColorTell, FALSE);
2889                         curColor = ColorTell;
2890                         break;
2891                       case 2:
2892                         Colorize(ColorKibitz, FALSE);
2893                         curColor = ColorKibitz;
2894                         break;
2895                       case 3:
2896                         p = strrchr(star_match[1], '(');
2897                         if (p == NULL) {
2898                           p = star_match[1];
2899                         } else {
2900                           p++;
2901                         }
2902                         if (atoi(p) == 1) {
2903                           Colorize(ColorChannel1, FALSE);
2904                           curColor = ColorChannel1;
2905                         } else {
2906                           Colorize(ColorChannel, FALSE);
2907                           curColor = ColorChannel;
2908                         }
2909                         break;
2910                       case 5:
2911                         curColor = ColorNormal;
2912                         break;
2913                       }
2914                     }
2915                     if (started == STARTED_NONE && appData.autoComment &&
2916                         (gameMode == IcsObserving ||
2917                          gameMode == IcsPlayingWhite ||
2918                          gameMode == IcsPlayingBlack)) {
2919                       parse_pos = i - oldi;
2920                       memcpy(parse, &buf[oldi], parse_pos);
2921                       parse[parse_pos] = NULLCHAR;
2922                       started = STARTED_COMMENT;
2923                       savingComment = TRUE;
2924                     } else {
2925                       started = STARTED_CHATTER;
2926                       savingComment = FALSE;
2927                     }
2928                     loggedOn = TRUE;
2929                     continue;
2930                   }
2931                 }
2932
2933                 if (looking_at(buf, &i, "* s-shouts: ") ||
2934                     looking_at(buf, &i, "* c-shouts: ")) {
2935                     if (appData.colorize) {
2936                         if (oldi > next_out) {
2937                             SendToPlayer(&buf[next_out], oldi - next_out);
2938                             next_out = oldi;
2939                         }
2940                         Colorize(ColorSShout, FALSE);
2941                         curColor = ColorSShout;
2942                     }
2943                     loggedOn = TRUE;
2944                     started = STARTED_CHATTER;
2945                     continue;
2946                 }
2947
2948                 if (looking_at(buf, &i, "--->")) {
2949                     loggedOn = TRUE;
2950                     continue;
2951                 }
2952
2953                 if (looking_at(buf, &i, "* shouts: ") ||
2954                     looking_at(buf, &i, "--> ")) {
2955                     if (appData.colorize) {
2956                         if (oldi > next_out) {
2957                             SendToPlayer(&buf[next_out], oldi - next_out);
2958                             next_out = oldi;
2959                         }
2960                         Colorize(ColorShout, FALSE);
2961                         curColor = ColorShout;
2962                     }
2963                     loggedOn = TRUE;
2964                     started = STARTED_CHATTER;
2965                     continue;
2966                 }
2967
2968                 if (looking_at( buf, &i, "Challenge:")) {
2969                     if (appData.colorize) {
2970                         if (oldi > next_out) {
2971                             SendToPlayer(&buf[next_out], oldi - next_out);
2972                             next_out = oldi;
2973                         }
2974                         Colorize(ColorChallenge, FALSE);
2975                         curColor = ColorChallenge;
2976                     }
2977                     loggedOn = TRUE;
2978                     continue;
2979                 }
2980
2981                 if (looking_at(buf, &i, "* offers you") ||
2982                     looking_at(buf, &i, "* offers to be") ||
2983                     looking_at(buf, &i, "* would like to") ||
2984                     looking_at(buf, &i, "* requests to") ||
2985                     looking_at(buf, &i, "Your opponent offers") ||
2986                     looking_at(buf, &i, "Your opponent requests")) {
2987
2988                     if (appData.colorize) {
2989                         if (oldi > next_out) {
2990                             SendToPlayer(&buf[next_out], oldi - next_out);
2991                             next_out = oldi;
2992                         }
2993                         Colorize(ColorRequest, FALSE);
2994                         curColor = ColorRequest;
2995                     }
2996                     continue;
2997                 }
2998
2999                 if (looking_at(buf, &i, "* (*) seeking")) {
3000                     if (appData.colorize) {
3001                         if (oldi > next_out) {
3002                             SendToPlayer(&buf[next_out], oldi - next_out);
3003                             next_out = oldi;
3004                         }
3005                         Colorize(ColorSeek, FALSE);
3006                         curColor = ColorSeek;
3007                     }
3008                     continue;
3009             }
3010
3011             if (looking_at(buf, &i, "\\   ")) {
3012                 if (prevColor != ColorNormal) {
3013                     if (oldi > next_out) {
3014                         SendToPlayer(&buf[next_out], oldi - next_out);
3015                         next_out = oldi;
3016                     }
3017                     Colorize(prevColor, TRUE);
3018                     curColor = prevColor;
3019                 }
3020                 if (savingComment) {
3021                     parse_pos = i - oldi;
3022                     memcpy(parse, &buf[oldi], parse_pos);
3023                     parse[parse_pos] = NULLCHAR;
3024                     started = STARTED_COMMENT;
3025                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3026                         chattingPartner = savingComment - 3; // kludge to remember the box
3027                 } else {
3028                     started = STARTED_CHATTER;
3029                 }
3030                 continue;
3031             }
3032
3033             if (looking_at(buf, &i, "Black Strength :") ||
3034                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3035                 looking_at(buf, &i, "<10>") ||
3036                 looking_at(buf, &i, "#@#")) {
3037                 /* Wrong board style */
3038                 loggedOn = TRUE;
3039                 SendToICS(ics_prefix);
3040                 SendToICS("set style 12\n");
3041                 SendToICS(ics_prefix);
3042                 SendToICS("refresh\n");
3043                 continue;
3044             }
3045             
3046             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3047                 ICSInitScript();
3048                 have_sent_ICS_logon = 1;
3049                 continue;
3050             }
3051               
3052             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3053                 (looking_at(buf, &i, "\n<12> ") ||
3054                  looking_at(buf, &i, "<12> "))) {
3055                 loggedOn = TRUE;
3056                 if (oldi > next_out) {
3057                     SendToPlayer(&buf[next_out], oldi - next_out);
3058                 }
3059                 next_out = i;
3060                 started = STARTED_BOARD;
3061                 parse_pos = 0;
3062                 continue;
3063             }
3064
3065             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3066                 looking_at(buf, &i, "<b1> ")) {
3067                 if (oldi > next_out) {
3068                     SendToPlayer(&buf[next_out], oldi - next_out);
3069                 }
3070                 next_out = i;
3071                 started = STARTED_HOLDINGS;
3072                 parse_pos = 0;
3073                 continue;
3074             }
3075
3076             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3077                 loggedOn = TRUE;
3078                 /* Header for a move list -- first line */
3079
3080                 switch (ics_getting_history) {
3081                   case H_FALSE:
3082                     switch (gameMode) {
3083                       case IcsIdle:
3084                       case BeginningOfGame:
3085                         /* User typed "moves" or "oldmoves" while we
3086                            were idle.  Pretend we asked for these
3087                            moves and soak them up so user can step
3088                            through them and/or save them.
3089                            */
3090                         Reset(FALSE, TRUE);
3091                         gameMode = IcsObserving;
3092                         ModeHighlight();
3093                         ics_gamenum = -1;
3094                         ics_getting_history = H_GOT_UNREQ_HEADER;
3095                         break;
3096                       case EditGame: /*?*/
3097                       case EditPosition: /*?*/
3098                         /* Should above feature work in these modes too? */
3099                         /* For now it doesn't */
3100                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3101                         break;
3102                       default:
3103                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3104                         break;
3105                     }
3106                     break;
3107                   case H_REQUESTED:
3108                     /* Is this the right one? */
3109                     if (gameInfo.white && gameInfo.black &&
3110                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3111                         strcmp(gameInfo.black, star_match[2]) == 0) {
3112                         /* All is well */
3113                         ics_getting_history = H_GOT_REQ_HEADER;
3114                     }
3115                     break;
3116                   case H_GOT_REQ_HEADER:
3117                   case H_GOT_UNREQ_HEADER:
3118                   case H_GOT_UNWANTED_HEADER:
3119                   case H_GETTING_MOVES:
3120                     /* Should not happen */
3121                     DisplayError(_("Error gathering move list: two headers"), 0);
3122                     ics_getting_history = H_FALSE;
3123                     break;
3124                 }
3125
3126                 /* Save player ratings into gameInfo if needed */
3127                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3128                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3129                     (gameInfo.whiteRating == -1 ||
3130                      gameInfo.blackRating == -1)) {
3131
3132                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3133                     gameInfo.blackRating = string_to_rating(star_match[3]);
3134                     if (appData.debugMode)
3135                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3136                               gameInfo.whiteRating, gameInfo.blackRating);
3137                 }
3138                 continue;
3139             }
3140
3141             if (looking_at(buf, &i,
3142               "* * match, initial time: * minute*, increment: * second")) {
3143                 /* Header for a move list -- second line */
3144                 /* Initial board will follow if this is a wild game */
3145                 if (gameInfo.event != NULL) free(gameInfo.event);
3146                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3147                 gameInfo.event = StrSave(str);
3148                 /* [HGM] we switched variant. Translate boards if needed. */
3149                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "Move  ")) {
3154                 /* Beginning of a move list */
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     /* Normally should not happen */
3158                     /* Maybe user hit reset while we were parsing */
3159                     break;
3160                   case H_REQUESTED:
3161                     /* Happens if we are ignoring a move list that is not
3162                      * the one we just requested.  Common if the user
3163                      * tries to observe two games without turning off
3164                      * getMoveList */
3165                     break;
3166                   case H_GETTING_MOVES:
3167                     /* Should not happen */
3168                     DisplayError(_("Error gathering move list: nested"), 0);
3169                     ics_getting_history = H_FALSE;
3170                     break;
3171                   case H_GOT_REQ_HEADER:
3172                     ics_getting_history = H_GETTING_MOVES;
3173                     started = STARTED_MOVES;
3174                     parse_pos = 0;
3175                     if (oldi > next_out) {
3176                         SendToPlayer(&buf[next_out], oldi - next_out);
3177                     }
3178                     break;
3179                   case H_GOT_UNREQ_HEADER:
3180                     ics_getting_history = H_GETTING_MOVES;
3181                     started = STARTED_MOVES_NOHIDE;
3182                     parse_pos = 0;
3183                     break;
3184                   case H_GOT_UNWANTED_HEADER:
3185                     ics_getting_history = H_FALSE;
3186                     break;
3187                 }
3188                 continue;
3189             }                           
3190             
3191             if (looking_at(buf, &i, "% ") ||
3192                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3193                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3194                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3195                     soughtPending = FALSE;
3196                     seekGraphUp = TRUE;
3197                     DrawSeekGraph();
3198                 }
3199                 if(suppressKibitz) next_out = i;
3200                 savingComment = FALSE;
3201                 suppressKibitz = 0;
3202                 switch (started) {
3203                   case STARTED_MOVES:
3204                   case STARTED_MOVES_NOHIDE:
3205                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3206                     parse[parse_pos + i - oldi] = NULLCHAR;
3207                     ParseGameHistory(parse);
3208 #if ZIPPY
3209                     if (appData.zippyPlay && first.initDone) {
3210                         FeedMovesToProgram(&first, forwardMostMove);
3211                         if (gameMode == IcsPlayingWhite) {
3212                             if (WhiteOnMove(forwardMostMove)) {
3213                                 if (first.sendTime) {
3214                                   if (first.useColors) {
3215                                     SendToProgram("black\n", &first); 
3216                                   }
3217                                   SendTimeRemaining(&first, TRUE);
3218                                 }
3219                                 if (first.useColors) {
3220                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3221                                 }
3222                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3223                                 first.maybeThinking = TRUE;
3224                             } else {
3225                                 if (first.usePlayother) {
3226                                   if (first.sendTime) {
3227                                     SendTimeRemaining(&first, TRUE);
3228                                   }
3229                                   SendToProgram("playother\n", &first);
3230                                   firstMove = FALSE;
3231                                 } else {
3232                                   firstMove = TRUE;
3233                                 }
3234                             }
3235                         } else if (gameMode == IcsPlayingBlack) {
3236                             if (!WhiteOnMove(forwardMostMove)) {
3237                                 if (first.sendTime) {
3238                                   if (first.useColors) {
3239                                     SendToProgram("white\n", &first);
3240                                   }
3241                                   SendTimeRemaining(&first, FALSE);
3242                                 }
3243                                 if (first.useColors) {
3244                                   SendToProgram("black\n", &first);
3245                                 }
3246                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3247                                 first.maybeThinking = TRUE;
3248                             } else {
3249                                 if (first.usePlayother) {
3250                                   if (first.sendTime) {
3251                                     SendTimeRemaining(&first, FALSE);
3252                                   }
3253                                   SendToProgram("playother\n", &first);
3254                                   firstMove = FALSE;
3255                                 } else {
3256                                   firstMove = TRUE;
3257                                 }
3258                             }
3259                         }                       
3260                     }
3261 #endif
3262                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3263                         /* Moves came from oldmoves or moves command
3264                            while we weren't doing anything else.
3265                            */
3266                         currentMove = forwardMostMove;
3267                         ClearHighlights();/*!!could figure this out*/
3268                         flipView = appData.flipView;
3269                         DrawPosition(TRUE, boards[currentMove]);
3270                         DisplayBothClocks();
3271                         sprintf(str, "%s vs. %s",
3272                                 gameInfo.white, gameInfo.black);
3273                         DisplayTitle(str);
3274                         gameMode = IcsIdle;
3275                     } else {
3276                         /* Moves were history of an active game */
3277                         if (gameInfo.resultDetails != NULL) {
3278                             free(gameInfo.resultDetails);
3279                             gameInfo.resultDetails = NULL;
3280                         }
3281                     }
3282                     HistorySet(parseList, backwardMostMove,
3283                                forwardMostMove, currentMove-1);
3284                     DisplayMove(currentMove - 1);
3285                     if (started == STARTED_MOVES) next_out = i;
3286                     started = STARTED_NONE;
3287                     ics_getting_history = H_FALSE;
3288                     break;
3289
3290                   case STARTED_OBSERVE:
3291                     started = STARTED_NONE;
3292                     SendToICS(ics_prefix);
3293                     SendToICS("refresh\n");
3294                     break;
3295
3296                   default:
3297                     break;
3298                 }
3299                 if(bookHit) { // [HGM] book: simulate book reply
3300                     static char bookMove[MSG_SIZ]; // a bit generous?
3301
3302                     programStats.nodes = programStats.depth = programStats.time = 
3303                     programStats.score = programStats.got_only_move = 0;
3304                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3305
3306                     strcpy(bookMove, "move ");
3307                     strcat(bookMove, bookHit);
3308                     HandleMachineMove(bookMove, &first);
3309                 }
3310                 continue;
3311             }
3312             
3313             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3314                  started == STARTED_HOLDINGS ||
3315                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3316                 /* Accumulate characters in move list or board */
3317                 parse[parse_pos++] = buf[i];
3318             }
3319             
3320             /* Start of game messages.  Mostly we detect start of game
3321                when the first board image arrives.  On some versions
3322                of the ICS, though, we need to do a "refresh" after starting
3323                to observe in order to get the current board right away. */
3324             if (looking_at(buf, &i, "Adding game * to observation list")) {
3325                 started = STARTED_OBSERVE;
3326                 continue;
3327             }
3328
3329             /* Handle auto-observe */
3330             if (appData.autoObserve &&
3331                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3332                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3333                 char *player;
3334                 /* Choose the player that was highlighted, if any. */
3335                 if (star_match[0][0] == '\033' ||
3336                     star_match[1][0] != '\033') {
3337                     player = star_match[0];
3338                 } else {
3339                     player = star_match[2];
3340                 }
3341                 sprintf(str, "%sobserve %s\n",
3342                         ics_prefix, StripHighlightAndTitle(player));
3343                 SendToICS(str);
3344
3345                 /* Save ratings from notify string */
3346                 strcpy(player1Name, star_match[0]);
3347                 player1Rating = string_to_rating(star_match[1]);
3348                 strcpy(player2Name, star_match[2]);
3349                 player2Rating = string_to_rating(star_match[3]);
3350
3351                 if (appData.debugMode)
3352                   fprintf(debugFP, 
3353                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3354                           player1Name, player1Rating,
3355                           player2Name, player2Rating);
3356
3357                 continue;
3358             }
3359
3360             /* Deal with automatic examine mode after a game,
3361                and with IcsObserving -> IcsExamining transition */
3362             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3363                 looking_at(buf, &i, "has made you an examiner of game *")) {
3364
3365                 int gamenum = atoi(star_match[0]);
3366                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3367                     gamenum == ics_gamenum) {
3368                     /* We were already playing or observing this game;
3369                        no need to refetch history */
3370                     gameMode = IcsExamining;
3371                     if (pausing) {
3372                         pauseExamForwardMostMove = forwardMostMove;
3373                     } else if (currentMove < forwardMostMove) {
3374                         ForwardInner(forwardMostMove);
3375                     }
3376                 } else {
3377                     /* I don't think this case really can happen */
3378                     SendToICS(ics_prefix);
3379                     SendToICS("refresh\n");
3380                 }
3381                 continue;
3382             }    
3383             
3384             /* Error messages */
3385 //          if (ics_user_moved) {
3386             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3387                 if (looking_at(buf, &i, "Illegal move") ||
3388                     looking_at(buf, &i, "Not a legal move") ||
3389                     looking_at(buf, &i, "Your king is in check") ||
3390                     looking_at(buf, &i, "It isn't your turn") ||
3391                     looking_at(buf, &i, "It is not your move")) {
3392                     /* Illegal move */
3393                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3394                         currentMove = forwardMostMove-1;
3395                         DisplayMove(currentMove - 1); /* before DMError */
3396                         DrawPosition(FALSE, boards[currentMove]);
3397                         SwitchClocks(forwardMostMove-1); // [HGM] race
3398                         DisplayBothClocks();
3399                     }
3400                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3401                     ics_user_moved = 0;
3402                     continue;
3403                 }
3404             }
3405
3406             if (looking_at(buf, &i, "still have time") ||
3407                 looking_at(buf, &i, "not out of time") ||
3408                 looking_at(buf, &i, "either player is out of time") ||
3409                 looking_at(buf, &i, "has timeseal; checking")) {
3410                 /* We must have called his flag a little too soon */
3411                 whiteFlag = blackFlag = FALSE;
3412                 continue;
3413             }
3414
3415             if (looking_at(buf, &i, "added * seconds to") ||
3416                 looking_at(buf, &i, "seconds were added to")) {
3417                 /* Update the clocks */
3418                 SendToICS(ics_prefix);
3419                 SendToICS("refresh\n");
3420                 continue;
3421             }
3422
3423             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3424                 ics_clock_paused = TRUE;
3425                 StopClocks();
3426                 continue;
3427             }
3428
3429             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3430                 ics_clock_paused = FALSE;
3431                 StartClocks();
3432                 continue;
3433             }
3434
3435             /* Grab player ratings from the Creating: message.
3436                Note we have to check for the special case when
3437                the ICS inserts things like [white] or [black]. */
3438             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3439                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3440                 /* star_matches:
3441                    0    player 1 name (not necessarily white)
3442                    1    player 1 rating
3443                    2    empty, white, or black (IGNORED)
3444                    3    player 2 name (not necessarily black)
3445                    4    player 2 rating
3446                    
3447                    The names/ratings are sorted out when the game
3448                    actually starts (below).
3449                 */
3450                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3451                 player1Rating = string_to_rating(star_match[1]);
3452                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3453                 player2Rating = string_to_rating(star_match[4]);
3454
3455                 if (appData.debugMode)
3456                   fprintf(debugFP, 
3457                           "Ratings from 'Creating:' %s %d, %s %d\n",
3458                           player1Name, player1Rating,
3459                           player2Name, player2Rating);
3460
3461                 continue;
3462             }
3463             
3464             /* Improved generic start/end-of-game messages */
3465             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3466                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3467                 /* If tkind == 0: */
3468                 /* star_match[0] is the game number */
3469                 /*           [1] is the white player's name */
3470                 /*           [2] is the black player's name */
3471                 /* For end-of-game: */
3472                 /*           [3] is the reason for the game end */
3473                 /*           [4] is a PGN end game-token, preceded by " " */
3474                 /* For start-of-game: */
3475                 /*           [3] begins with "Creating" or "Continuing" */
3476                 /*           [4] is " *" or empty (don't care). */
3477                 int gamenum = atoi(star_match[0]);
3478                 char *whitename, *blackname, *why, *endtoken;
3479                 ChessMove endtype = (ChessMove) 0;
3480
3481                 if (tkind == 0) {
3482                   whitename = star_match[1];
3483                   blackname = star_match[2];
3484                   why = star_match[3];
3485                   endtoken = star_match[4];
3486                 } else {
3487                   whitename = star_match[1];
3488                   blackname = star_match[3];
3489                   why = star_match[5];
3490                   endtoken = star_match[6];
3491                 }
3492
3493                 /* Game start messages */
3494                 if (strncmp(why, "Creating ", 9) == 0 ||
3495                     strncmp(why, "Continuing ", 11) == 0) {
3496                     gs_gamenum = gamenum;
3497                     strcpy(gs_kind, strchr(why, ' ') + 1);
3498                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3499 #if ZIPPY
3500                     if (appData.zippyPlay) {
3501                         ZippyGameStart(whitename, blackname);
3502                     }
3503 #endif /*ZIPPY*/
3504                     partnerBoardValid = FALSE; // [HGM] bughouse
3505                     continue;
3506                 }
3507
3508                 /* Game end messages */
3509                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3510                     ics_gamenum != gamenum) {
3511                     continue;
3512                 }
3513                 while (endtoken[0] == ' ') endtoken++;
3514                 switch (endtoken[0]) {
3515                   case '*':
3516                   default:
3517                     endtype = GameUnfinished;
3518                     break;
3519                   case '0':
3520                     endtype = BlackWins;
3521                     break;
3522                   case '1':
3523                     if (endtoken[1] == '/')
3524                       endtype = GameIsDrawn;
3525                     else
3526                       endtype = WhiteWins;
3527                     break;
3528                 }
3529                 GameEnds(endtype, why, GE_ICS);
3530 #if ZIPPY
3531                 if (appData.zippyPlay && first.initDone) {
3532                     ZippyGameEnd(endtype, why);
3533                     if (first.pr == NULL) {
3534                       /* Start the next process early so that we'll
3535                          be ready for the next challenge */
3536                       StartChessProgram(&first);
3537                     }
3538                     /* Send "new" early, in case this command takes
3539                        a long time to finish, so that we'll be ready
3540                        for the next challenge. */
3541                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3542                     Reset(TRUE, TRUE);
3543                 }
3544 #endif /*ZIPPY*/
3545                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3546                 continue;
3547             }
3548
3549             if (looking_at(buf, &i, "Removing game * from observation") ||
3550                 looking_at(buf, &i, "no longer observing game *") ||
3551                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3552                 if (gameMode == IcsObserving &&
3553                     atoi(star_match[0]) == ics_gamenum)
3554                   {
3555                       /* icsEngineAnalyze */
3556                       if (appData.icsEngineAnalyze) {
3557                             ExitAnalyzeMode();
3558                             ModeHighlight();
3559                       }
3560                       StopClocks();
3561                       gameMode = IcsIdle;
3562                       ics_gamenum = -1;
3563                       ics_user_moved = FALSE;
3564                   }
3565                 continue;
3566             }
3567
3568             if (looking_at(buf, &i, "no longer examining game *")) {
3569                 if (gameMode == IcsExamining &&
3570                     atoi(star_match[0]) == ics_gamenum)
3571                   {
3572                       gameMode = IcsIdle;
3573                       ics_gamenum = -1;
3574                       ics_user_moved = FALSE;
3575                   }
3576                 continue;
3577             }
3578
3579             /* Advance leftover_start past any newlines we find,
3580                so only partial lines can get reparsed */
3581             if (looking_at(buf, &i, "\n")) {
3582                 prevColor = curColor;
3583                 if (curColor != ColorNormal) {
3584                     if (oldi > next_out) {
3585                         SendToPlayer(&buf[next_out], oldi - next_out);
3586                         next_out = oldi;
3587                     }
3588                     Colorize(ColorNormal, FALSE);
3589                     curColor = ColorNormal;
3590                 }
3591                 if (started == STARTED_BOARD) {
3592                     started = STARTED_NONE;
3593                     parse[parse_pos] = NULLCHAR;
3594                     ParseBoard12(parse);
3595                     ics_user_moved = 0;
3596
3597                     /* Send premove here */
3598                     if (appData.premove) {
3599                       char str[MSG_SIZ];
3600                       if (currentMove == 0 &&
3601                           gameMode == IcsPlayingWhite &&
3602                           appData.premoveWhite) {
3603                         sprintf(str, "%s\n", appData.premoveWhiteText);
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                         SendToICS(str);
3607                       } else if (currentMove == 1 &&
3608                                  gameMode == IcsPlayingBlack &&
3609                                  appData.premoveBlack) {
3610                         sprintf(str, "%s\n", appData.premoveBlackText);
3611                         if (appData.debugMode)
3612                           fprintf(debugFP, "Sending premove:\n");
3613                         SendToICS(str);
3614                       } else if (gotPremove) {
3615                         gotPremove = 0;
3616                         ClearPremoveHighlights();
3617                         if (appData.debugMode)
3618                           fprintf(debugFP, "Sending premove:\n");
3619                           UserMoveEvent(premoveFromX, premoveFromY, 
3620                                         premoveToX, premoveToY, 
3621                                         premovePromoChar);
3622                       }
3623                     }
3624
3625                     /* Usually suppress following prompt */
3626                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3627                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3628                         if (looking_at(buf, &i, "*% ")) {
3629                             savingComment = FALSE;
3630                             suppressKibitz = 0;
3631                         }
3632                     }
3633                     next_out = i;
3634                 } else if (started == STARTED_HOLDINGS) {
3635                     int gamenum;
3636                     char new_piece[MSG_SIZ];
3637                     started = STARTED_NONE;
3638                     parse[parse_pos] = NULLCHAR;
3639                     if (appData.debugMode)
3640                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3641                                                         parse, currentMove);
3642                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3643                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3644                         if (gameInfo.variant == VariantNormal) {
3645                           /* [HGM] We seem to switch variant during a game!
3646                            * Presumably no holdings were displayed, so we have
3647                            * to move the position two files to the right to
3648                            * create room for them!
3649                            */
3650                           VariantClass newVariant;
3651                           switch(gameInfo.boardWidth) { // base guess on board width
3652                                 case 9:  newVariant = VariantShogi; break;
3653                                 case 10: newVariant = VariantGreat; break;
3654                                 default: newVariant = VariantCrazyhouse; break;
3655                           }
3656                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3657                           /* Get a move list just to see the header, which
3658                              will tell us whether this is really bug or zh */
3659                           if (ics_getting_history == H_FALSE) {
3660                             ics_getting_history = H_REQUESTED;
3661                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3662                             SendToICS(str);
3663                           }
3664                         }
3665                         new_piece[0] = NULLCHAR;
3666                         sscanf(parse, "game %d white [%s black [%s <- %s",
3667                                &gamenum, white_holding, black_holding,
3668                                new_piece);
3669                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3670                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3671                         /* [HGM] copy holdings to board holdings area */
3672                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3673                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3674                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3675 #if ZIPPY
3676                         if (appData.zippyPlay && first.initDone) {
3677                             ZippyHoldings(white_holding, black_holding,
3678                                           new_piece);
3679                         }
3680 #endif /*ZIPPY*/
3681                         if (tinyLayout || smallLayout) {
3682                             char wh[16], bh[16];
3683                             PackHolding(wh, white_holding);
3684                             PackHolding(bh, black_holding);
3685                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3686                                     gameInfo.white, gameInfo.black);
3687                         } else {
3688                             sprintf(str, "%s [%s] vs. %s [%s]",
3689                                     gameInfo.white, white_holding,
3690                                     gameInfo.black, black_holding);
3691                         }
3692                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3693                         DrawPosition(FALSE, boards[currentMove]);
3694                         DisplayTitle(str);
3695                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3696                         sscanf(parse, "game %d white [%s black [%s <- %s",
3697                                &gamenum, white_holding, black_holding,
3698                                new_piece);
3699                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3700                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3701                         /* [HGM] copy holdings to partner-board holdings area */
3702                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3703                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3704                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3705                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3706                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707                       }
3708                     }
3709                     /* Suppress following prompt */
3710                     if (looking_at(buf, &i, "*% ")) {
3711                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3712                         savingComment = FALSE;
3713                         suppressKibitz = 0;
3714                     }
3715                     next_out = i;
3716                 }
3717                 continue;
3718             }
3719
3720             i++;                /* skip unparsed character and loop back */
3721         }
3722         
3723         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3724 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3725 //          SendToPlayer(&buf[next_out], i - next_out);
3726             started != STARTED_HOLDINGS && leftover_start > next_out) {
3727             SendToPlayer(&buf[next_out], leftover_start - next_out);
3728             next_out = i;
3729         }
3730         
3731         leftover_len = buf_len - leftover_start;
3732         /* if buffer ends with something we couldn't parse,
3733            reparse it after appending the next read */
3734         
3735     } else if (count == 0) {
3736         RemoveInputSource(isr);
3737         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3738     } else {
3739         DisplayFatalError(_("Error reading from ICS"), error, 1);
3740     }
3741 }
3742
3743
3744 /* Board style 12 looks like this:
3745    
3746    <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
3747    
3748  * The "<12> " is stripped before it gets to this routine.  The two
3749  * trailing 0's (flip state and clock ticking) are later addition, and
3750  * some chess servers may not have them, or may have only the first.
3751  * Additional trailing fields may be added in the future.  
3752  */
3753
3754 #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"
3755
3756 #define RELATION_OBSERVING_PLAYED    0
3757 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3758 #define RELATION_PLAYING_MYMOVE      1
3759 #define RELATION_PLAYING_NOTMYMOVE  -1
3760 #define RELATION_EXAMINING           2
3761 #define RELATION_ISOLATED_BOARD     -3
3762 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3763
3764 void
3765 ParseBoard12(string)
3766      char *string;
3767
3768     GameMode newGameMode;
3769     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3770     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3771     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3772     char to_play, board_chars[200];
3773     char move_str[500], str[500], elapsed_time[500];
3774     char black[32], white[32];
3775     Board board;
3776     int prevMove = currentMove;
3777     int ticking = 2;
3778     ChessMove moveType;
3779     int fromX, fromY, toX, toY;
3780     char promoChar;
3781     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3782     char *bookHit = NULL; // [HGM] book
3783     Boolean weird = FALSE, reqFlag = FALSE;
3784
3785     fromX = fromY = toX = toY = -1;
3786     
3787     newGame = FALSE;
3788
3789     if (appData.debugMode)
3790       fprintf(debugFP, _("Parsing board: %s\n"), string);
3791
3792     move_str[0] = NULLCHAR;
3793     elapsed_time[0] = NULLCHAR;
3794     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3795         int  i = 0, j;
3796         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3797             if(string[i] == ' ') { ranks++; files = 0; }
3798             else files++;
3799             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800             i++;
3801         }
3802         for(j = 0; j <i; j++) board_chars[j] = string[j];
3803         board_chars[i] = '\0';
3804         string += i + 1;
3805     }
3806     n = sscanf(string, PATTERN, &to_play, &double_push,
3807                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3808                &gamenum, white, black, &relation, &basetime, &increment,
3809                &white_stren, &black_stren, &white_time, &black_time,
3810                &moveNum, str, elapsed_time, move_str, &ics_flip,
3811                &ticking);
3812
3813     if (n < 21) {
3814         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3815         DisplayError(str, 0);
3816         return;
3817     }
3818
3819     /* Convert the move number to internal form */
3820     moveNum = (moveNum - 1) * 2;
3821     if (to_play == 'B') moveNum++;
3822     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3823       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3824                         0, 1);
3825       return;
3826     }
3827     
3828     switch (relation) {
3829       case RELATION_OBSERVING_PLAYED:
3830       case RELATION_OBSERVING_STATIC:
3831         if (gamenum == -1) {
3832             /* Old ICC buglet */
3833             relation = RELATION_OBSERVING_STATIC;
3834         }
3835         newGameMode = IcsObserving;
3836         break;
3837       case RELATION_PLAYING_MYMOVE:
3838       case RELATION_PLAYING_NOTMYMOVE:
3839         newGameMode =
3840           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3841             IcsPlayingWhite : IcsPlayingBlack;
3842         break;
3843       case RELATION_EXAMINING:
3844         newGameMode = IcsExamining;
3845         break;
3846       case RELATION_ISOLATED_BOARD:
3847       default:
3848         /* Just display this board.  If user was doing something else,
3849            we will forget about it until the next board comes. */ 
3850         newGameMode = IcsIdle;
3851         break;
3852       case RELATION_STARTING_POSITION:
3853         newGameMode = gameMode;
3854         break;
3855     }
3856     
3857     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3858          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3859       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3860       char *toSqr;
3861       for (k = 0; k < ranks; k++) {
3862         for (j = 0; j < files; j++)
3863           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3864         if(gameInfo.holdingsWidth > 1) {
3865              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3866              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3867         }
3868       }
3869       CopyBoard(partnerBoard, board);
3870       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3871         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3872         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3873       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3874       if(toSqr = strchr(str, '-')) {
3875         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3876         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3877       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3878       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3879       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3880       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3881       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3882       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3883                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3884       DisplayMessage(partnerStatus, "");
3885         partnerBoardValid = TRUE;
3886       return;
3887     }
3888
3889     /* Modify behavior for initial board display on move listing
3890        of wild games.
3891        */
3892     switch (ics_getting_history) {
3893       case H_FALSE:
3894       case H_REQUESTED:
3895         break;
3896       case H_GOT_REQ_HEADER:
3897       case H_GOT_UNREQ_HEADER:
3898         /* This is the initial position of the current game */
3899         gamenum = ics_gamenum;
3900         moveNum = 0;            /* old ICS bug workaround */
3901         if (to_play == 'B') {
3902           startedFromSetupPosition = TRUE;
3903           blackPlaysFirst = TRUE;
3904           moveNum = 1;
3905           if (forwardMostMove == 0) forwardMostMove = 1;
3906           if (backwardMostMove == 0) backwardMostMove = 1;
3907           if (currentMove == 0) currentMove = 1;
3908         }
3909         newGameMode = gameMode;
3910         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3911         break;
3912       case H_GOT_UNWANTED_HEADER:
3913         /* This is an initial board that we don't want */
3914         return;
3915       case H_GETTING_MOVES:
3916         /* Should not happen */
3917         DisplayError(_("Error gathering move list: extra board"), 0);
3918         ics_getting_history = H_FALSE;
3919         return;
3920     }
3921
3922    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3923                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3924      /* [HGM] We seem to have switched variant unexpectedly
3925       * Try to guess new variant from board size
3926       */
3927           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3928           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3929           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3930           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3931           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3932           if(!weird) newVariant = VariantNormal;
3933           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3934           /* Get a move list just to see the header, which
3935              will tell us whether this is really bug or zh */
3936           if (ics_getting_history == H_FALSE) {
3937             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3938             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3939             SendToICS(str);
3940           }
3941     }
3942     
3943     /* Take action if this is the first board of a new game, or of a
3944        different game than is currently being displayed.  */
3945     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3946         relation == RELATION_ISOLATED_BOARD) {
3947         
3948         /* Forget the old game and get the history (if any) of the new one */
3949         if (gameMode != BeginningOfGame) {
3950           Reset(TRUE, TRUE);
3951         }
3952         newGame = TRUE;
3953         if (appData.autoRaiseBoard) BoardToTop();
3954         prevMove = -3;
3955         if (gamenum == -1) {
3956             newGameMode = IcsIdle;
3957         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3958                    appData.getMoveList && !reqFlag) {
3959             /* Need to get game history */
3960             ics_getting_history = H_REQUESTED;
3961             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3962             SendToICS(str);
3963         }
3964         
3965         /* Initially flip the board to have black on the bottom if playing
3966            black or if the ICS flip flag is set, but let the user change
3967            it with the Flip View button. */
3968         flipView = appData.autoFlipView ? 
3969           (newGameMode == IcsPlayingBlack) || ics_flip :
3970           appData.flipView;
3971         
3972         /* Done with values from previous mode; copy in new ones */
3973         gameMode = newGameMode;
3974         ModeHighlight();
3975         ics_gamenum = gamenum;
3976         if (gamenum == gs_gamenum) {
3977             int klen = strlen(gs_kind);
3978             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3979             sprintf(str, "ICS %s", gs_kind);
3980             gameInfo.event = StrSave(str);
3981         } else {
3982             gameInfo.event = StrSave("ICS game");
3983         }
3984         gameInfo.site = StrSave(appData.icsHost);
3985         gameInfo.date = PGNDate();
3986         gameInfo.round = StrSave("-");
3987         gameInfo.white = StrSave(white);
3988         gameInfo.black = StrSave(black);
3989         timeControl = basetime * 60 * 1000;
3990         timeControl_2 = 0;
3991         timeIncrement = increment * 1000;
3992         movesPerSession = 0;
3993         gameInfo.timeControl = TimeControlTagValue();
3994         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3995   if (appData.debugMode) {
3996     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3997     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3998     setbuf(debugFP, NULL);
3999   }
4000
4001         gameInfo.outOfBook = NULL;
4002         
4003         /* Do we have the ratings? */
4004         if (strcmp(player1Name, white) == 0 &&
4005             strcmp(player2Name, black) == 0) {
4006             if (appData.debugMode)
4007               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4008                       player1Rating, player2Rating);
4009             gameInfo.whiteRating = player1Rating;
4010             gameInfo.blackRating = player2Rating;
4011         } else if (strcmp(player2Name, white) == 0 &&
4012                    strcmp(player1Name, black) == 0) {
4013             if (appData.debugMode)
4014               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4015                       player2Rating, player1Rating);
4016             gameInfo.whiteRating = player2Rating;
4017             gameInfo.blackRating = player1Rating;
4018         }
4019         player1Name[0] = player2Name[0] = NULLCHAR;
4020
4021         /* Silence shouts if requested */
4022         if (appData.quietPlay &&
4023             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4024             SendToICS(ics_prefix);
4025             SendToICS("set shout 0\n");
4026         }
4027     }
4028     
4029     /* Deal with midgame name changes */
4030     if (!newGame) {
4031         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4032             if (gameInfo.white) free(gameInfo.white);
4033             gameInfo.white = StrSave(white);
4034         }
4035         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4036             if (gameInfo.black) free(gameInfo.black);
4037             gameInfo.black = StrSave(black);
4038         }
4039     }
4040     
4041     /* Throw away game result if anything actually changes in examine mode */
4042     if (gameMode == IcsExamining && !newGame) {
4043         gameInfo.result = GameUnfinished;
4044         if (gameInfo.resultDetails != NULL) {
4045             free(gameInfo.resultDetails);
4046             gameInfo.resultDetails = NULL;
4047         }
4048     }
4049     
4050     /* In pausing && IcsExamining mode, we ignore boards coming
4051        in if they are in a different variation than we are. */
4052     if (pauseExamInvalid) return;
4053     if (pausing && gameMode == IcsExamining) {
4054         if (moveNum <= pauseExamForwardMostMove) {
4055             pauseExamInvalid = TRUE;
4056             forwardMostMove = pauseExamForwardMostMove;
4057             return;
4058         }
4059     }
4060     
4061   if (appData.debugMode) {
4062     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4063   }
4064     /* Parse the board */
4065     for (k = 0; k < ranks; k++) {
4066       for (j = 0; j < files; j++)
4067         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4068       if(gameInfo.holdingsWidth > 1) {
4069            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4070            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4071       }
4072     }
4073     CopyBoard(boards[moveNum], board);
4074     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4075     if (moveNum == 0) {
4076         startedFromSetupPosition =
4077           !CompareBoards(board, initialPosition);
4078         if(startedFromSetupPosition)
4079             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4080     }
4081
4082     /* [HGM] Set castling rights. Take the outermost Rooks,
4083        to make it also work for FRC opening positions. Note that board12
4084        is really defective for later FRC positions, as it has no way to
4085        indicate which Rook can castle if they are on the same side of King.
4086        For the initial position we grant rights to the outermost Rooks,
4087        and remember thos rights, and we then copy them on positions
4088        later in an FRC game. This means WB might not recognize castlings with
4089        Rooks that have moved back to their original position as illegal,
4090        but in ICS mode that is not its job anyway.
4091     */
4092     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4093     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4094
4095         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4096             if(board[0][i] == WhiteRook) j = i;
4097         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4098         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4099             if(board[0][i] == WhiteRook) j = i;
4100         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4101         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4102             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4103         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4104         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4105             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4106         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4107
4108         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4109         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4110             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4111         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4112             if(board[BOARD_HEIGHT-1][k] == bKing)
4113                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4114         if(gameInfo.variant == VariantTwoKings) {
4115             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4116             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4117             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4118         }
4119     } else { int r;
4120         r = boards[moveNum][CASTLING][0] = initialRights[0];
4121         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4122         r = boards[moveNum][CASTLING][1] = initialRights[1];
4123         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4124         r = boards[moveNum][CASTLING][3] = initialRights[3];
4125         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4126         r = boards[moveNum][CASTLING][4] = initialRights[4];
4127         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4128         /* wildcastle kludge: always assume King has rights */
4129         r = boards[moveNum][CASTLING][2] = initialRights[2];
4130         r = boards[moveNum][CASTLING][5] = initialRights[5];
4131     }
4132     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4133     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4134
4135     
4136     if (ics_getting_history == H_GOT_REQ_HEADER ||
4137         ics_getting_history == H_GOT_UNREQ_HEADER) {
4138         /* This was an initial position from a move list, not
4139            the current position */
4140         return;
4141     }
4142     
4143     /* Update currentMove and known move number limits */
4144     newMove = newGame || moveNum > forwardMostMove;
4145
4146     if (newGame) {
4147         forwardMostMove = backwardMostMove = currentMove = moveNum;
4148         if (gameMode == IcsExamining && moveNum == 0) {
4149           /* Workaround for ICS limitation: we are not told the wild
4150              type when starting to examine a game.  But if we ask for
4151              the move list, the move list header will tell us */
4152             ics_getting_history = H_REQUESTED;
4153             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4154             SendToICS(str);
4155         }
4156     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4157                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4158 #if ZIPPY
4159         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4160         /* [HGM] applied this also to an engine that is silently watching        */
4161         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4162             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4163             gameInfo.variant == currentlyInitializedVariant) {
4164           takeback = forwardMostMove - moveNum;
4165           for (i = 0; i < takeback; i++) {
4166             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4167             SendToProgram("undo\n", &first);
4168           }
4169         }
4170 #endif
4171
4172         forwardMostMove = moveNum;
4173         if (!pausing || currentMove > forwardMostMove)
4174           currentMove = forwardMostMove;
4175     } else {
4176         /* New part of history that is not contiguous with old part */ 
4177         if (pausing && gameMode == IcsExamining) {
4178             pauseExamInvalid = TRUE;
4179             forwardMostMove = pauseExamForwardMostMove;
4180             return;
4181         }
4182         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4183 #if ZIPPY
4184             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4185                 // [HGM] when we will receive the move list we now request, it will be
4186                 // fed to the engine from the first move on. So if the engine is not
4187                 // in the initial position now, bring it there.
4188                 InitChessProgram(&first, 0);
4189             }
4190 #endif
4191             ics_getting_history = H_REQUESTED;
4192             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4193             SendToICS(str);
4194         }
4195         forwardMostMove = backwardMostMove = currentMove = moveNum;
4196     }
4197     
4198     /* Update the clocks */
4199     if (strchr(elapsed_time, '.')) {
4200       /* Time is in ms */
4201       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4202       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4203     } else {
4204       /* Time is in seconds */
4205       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4206       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4207     }
4208       
4209
4210 #if ZIPPY
4211     if (appData.zippyPlay && newGame &&
4212         gameMode != IcsObserving && gameMode != IcsIdle &&
4213         gameMode != IcsExamining)
4214       ZippyFirstBoard(moveNum, basetime, increment);
4215 #endif
4216     
4217     /* Put the move on the move list, first converting
4218        to canonical algebraic form. */
4219     if (moveNum > 0) {
4220   if (appData.debugMode) {
4221     if (appData.debugMode) { int f = forwardMostMove;
4222         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4223                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4224                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4225     }
4226     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4227     fprintf(debugFP, "moveNum = %d\n", moveNum);
4228     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4229     setbuf(debugFP, NULL);
4230   }
4231         if (moveNum <= backwardMostMove) {
4232             /* We don't know what the board looked like before
4233                this move.  Punt. */
4234             strcpy(parseList[moveNum - 1], move_str);
4235             strcat(parseList[moveNum - 1], " ");
4236             strcat(parseList[moveNum - 1], elapsed_time);
4237             moveList[moveNum - 1][0] = NULLCHAR;
4238         } else if (strcmp(move_str, "none") == 0) {
4239             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4240             /* Again, we don't know what the board looked like;
4241                this is really the start of the game. */
4242             parseList[moveNum - 1][0] = NULLCHAR;
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             backwardMostMove = moveNum;
4245             startedFromSetupPosition = TRUE;
4246             fromX = fromY = toX = toY = -1;
4247         } else {
4248           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4249           //                 So we parse the long-algebraic move string in stead of the SAN move
4250           int valid; char buf[MSG_SIZ], *prom;
4251
4252           // str looks something like "Q/a1-a2"; kill the slash
4253           if(str[1] == '/') 
4254                 sprintf(buf, "%c%s", str[0], str+2);
4255           else  strcpy(buf, str); // might be castling
4256           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4257                 strcat(buf, prom); // long move lacks promo specification!
4258           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4259                 if(appData.debugMode) 
4260                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4261                 strcpy(move_str, buf);
4262           }
4263           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4264                                 &fromX, &fromY, &toX, &toY, &promoChar)
4265                || ParseOneMove(buf, moveNum - 1, &moveType,
4266                                 &fromX, &fromY, &toX, &toY, &promoChar);
4267           // end of long SAN patch
4268           if (valid) {
4269             (void) CoordsToAlgebraic(boards[moveNum - 1],
4270                                      PosFlags(moveNum - 1),
4271                                      fromY, fromX, toY, toX, promoChar,
4272                                      parseList[moveNum-1]);
4273             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4274               case MT_NONE:
4275               case MT_STALEMATE:
4276               default:
4277                 break;
4278               case MT_CHECK:
4279                 if(gameInfo.variant != VariantShogi)
4280                     strcat(parseList[moveNum - 1], "+");
4281                 break;
4282               case MT_CHECKMATE:
4283               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4284                 strcat(parseList[moveNum - 1], "#");
4285                 break;
4286             }
4287             strcat(parseList[moveNum - 1], " ");
4288             strcat(parseList[moveNum - 1], elapsed_time);
4289             /* currentMoveString is set as a side-effect of ParseOneMove */
4290             strcpy(moveList[moveNum - 1], currentMoveString);
4291             strcat(moveList[moveNum - 1], "\n");
4292           } else {
4293             /* Move from ICS was illegal!?  Punt. */
4294   if (appData.debugMode) {
4295     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4296     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4297   }
4298             strcpy(parseList[moveNum - 1], move_str);
4299             strcat(parseList[moveNum - 1], " ");
4300             strcat(parseList[moveNum - 1], elapsed_time);
4301             moveList[moveNum - 1][0] = NULLCHAR;
4302             fromX = fromY = toX = toY = -1;
4303           }
4304         }
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4307     setbuf(debugFP, NULL);
4308   }
4309
4310 #if ZIPPY
4311         /* Send move to chess program (BEFORE animating it). */
4312         if (appData.zippyPlay && !newGame && newMove && 
4313            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4314
4315             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4316                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4317                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4318                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4319                             move_str);
4320                     DisplayError(str, 0);
4321                 } else {
4322                     if (first.sendTime) {
4323                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4324                     }
4325                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4326                     if (firstMove && !bookHit) {
4327                         firstMove = FALSE;
4328                         if (first.useColors) {
4329                           SendToProgram(gameMode == IcsPlayingWhite ?
4330                                         "white\ngo\n" :
4331                                         "black\ngo\n", &first);
4332                         } else {
4333                           SendToProgram("go\n", &first);
4334                         }
4335                         first.maybeThinking = TRUE;
4336                     }
4337                 }
4338             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4339               if (moveList[moveNum - 1][0] == NULLCHAR) {
4340                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4341                 DisplayError(str, 0);
4342               } else {
4343                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4344                 SendMoveToProgram(moveNum - 1, &first);
4345               }
4346             }
4347         }
4348 #endif
4349     }
4350
4351     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4352         /* If move comes from a remote source, animate it.  If it
4353            isn't remote, it will have already been animated. */
4354         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4355             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4356         }
4357         if (!pausing && appData.highlightLastMove) {
4358             SetHighlights(fromX, fromY, toX, toY);
4359         }
4360     }
4361     
4362     /* Start the clocks */
4363     whiteFlag = blackFlag = FALSE;
4364     appData.clockMode = !(basetime == 0 && increment == 0);
4365     if (ticking == 0) {
4366       ics_clock_paused = TRUE;
4367       StopClocks();
4368     } else if (ticking == 1) {
4369       ics_clock_paused = FALSE;
4370     }
4371     if (gameMode == IcsIdle ||
4372         relation == RELATION_OBSERVING_STATIC ||
4373         relation == RELATION_EXAMINING ||
4374         ics_clock_paused)
4375       DisplayBothClocks();
4376     else
4377       StartClocks();
4378     
4379     /* Display opponents and material strengths */
4380     if (gameInfo.variant != VariantBughouse &&
4381         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4382         if (tinyLayout || smallLayout) {
4383             if(gameInfo.variant == VariantNormal)
4384                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment);
4387             else
4388                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4389                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4390                     basetime, increment, (int) gameInfo.variant);
4391         } else {
4392             if(gameInfo.variant == VariantNormal)
4393                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4394                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4395                     basetime, increment);
4396             else
4397                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4398                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4399                     basetime, increment, VariantName(gameInfo.variant));
4400         }
4401         DisplayTitle(str);
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4404   }
4405     }
4406
4407
4408     /* Display the board */
4409     if (!pausing && !appData.noGUI) {
4410       
4411       if (appData.premove)
4412           if (!gotPremove || 
4413              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4414              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4415               ClearPremoveHighlights();
4416
4417       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4418         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4419       DrawPosition(j, boards[currentMove]);
4420
4421       DisplayMove(moveNum - 1);
4422       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4423             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4424               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4425         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4426       }
4427     }
4428
4429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4430 #if ZIPPY
4431     if(bookHit) { // [HGM] book: simulate book reply
4432         static char bookMove[MSG_SIZ]; // a bit generous?
4433
4434         programStats.nodes = programStats.depth = programStats.time = 
4435         programStats.score = programStats.got_only_move = 0;
4436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4437
4438         strcpy(bookMove, "move ");
4439         strcat(bookMove, bookHit);
4440         HandleMachineMove(bookMove, &first);
4441     }
4442 #endif
4443 }
4444
4445 void
4446 GetMoveListEvent()
4447 {
4448     char buf[MSG_SIZ];
4449     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4450         ics_getting_history = H_REQUESTED;
4451         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4452         SendToICS(buf);
4453     }
4454 }
4455
4456 void
4457 AnalysisPeriodicEvent(force)
4458      int force;
4459 {
4460     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4461          && !force) || !appData.periodicUpdates)
4462       return;
4463
4464     /* Send . command to Crafty to collect stats */
4465     SendToProgram(".\n", &first);
4466
4467     /* Don't send another until we get a response (this makes
4468        us stop sending to old Crafty's which don't understand
4469        the "." command (sending illegal cmds resets node count & time,
4470        which looks bad)) */
4471     programStats.ok_to_send = 0;
4472 }
4473
4474 void ics_update_width(new_width)
4475         int new_width;
4476 {
4477         ics_printf("set width %d\n", new_width);
4478 }
4479
4480 void
4481 SendMoveToProgram(moveNum, cps)
4482      int moveNum;
4483      ChessProgramState *cps;
4484 {
4485     char buf[MSG_SIZ];
4486
4487     if (cps->useUsermove) {
4488       SendToProgram("usermove ", cps);
4489     }
4490     if (cps->useSAN) {
4491       char *space;
4492       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4493         int len = space - parseList[moveNum];
4494         memcpy(buf, parseList[moveNum], len);
4495         buf[len++] = '\n';
4496         buf[len] = NULLCHAR;
4497       } else {
4498         sprintf(buf, "%s\n", parseList[moveNum]);
4499       }
4500       SendToProgram(buf, cps);
4501     } else {
4502       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4503         AlphaRank(moveList[moveNum], 4);
4504         SendToProgram(moveList[moveNum], cps);
4505         AlphaRank(moveList[moveNum], 4); // and back
4506       } else
4507       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4508        * the engine. It would be nice to have a better way to identify castle 
4509        * moves here. */
4510       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4511                                                                          && cps->useOOCastle) {
4512         int fromX = moveList[moveNum][0] - AAA; 
4513         int fromY = moveList[moveNum][1] - ONE;
4514         int toX = moveList[moveNum][2] - AAA; 
4515         int toY = moveList[moveNum][3] - ONE;
4516         if((boards[moveNum][fromY][fromX] == WhiteKing 
4517             && boards[moveNum][toY][toX] == WhiteRook)
4518            || (boards[moveNum][fromY][fromX] == BlackKing 
4519                && boards[moveNum][toY][toX] == BlackRook)) {
4520           if(toX > fromX) SendToProgram("O-O\n", cps);
4521           else SendToProgram("O-O-O\n", cps);
4522         }
4523         else SendToProgram(moveList[moveNum], cps);
4524       }
4525       else SendToProgram(moveList[moveNum], cps);
4526       /* End of additions by Tord */
4527     }
4528
4529     /* [HGM] setting up the opening has brought engine in force mode! */
4530     /*       Send 'go' if we are in a mode where machine should play. */
4531     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4532         (gameMode == TwoMachinesPlay   ||
4533 #ifdef ZIPPY
4534          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4535 #endif
4536          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4537         SendToProgram("go\n", cps);
4538   if (appData.debugMode) {
4539     fprintf(debugFP, "(extra)\n");
4540   }
4541     }
4542     setboardSpoiledMachineBlack = 0;
4543 }
4544
4545 void
4546 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4547      ChessMove moveType;
4548      int fromX, fromY, toX, toY;
4549 {
4550     char user_move[MSG_SIZ];
4551
4552     switch (moveType) {
4553       default:
4554         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4555                 (int)moveType, fromX, fromY, toX, toY);
4556         DisplayError(user_move + strlen("say "), 0);
4557         break;
4558       case WhiteKingSideCastle:
4559       case BlackKingSideCastle:
4560       case WhiteQueenSideCastleWild:
4561       case BlackQueenSideCastleWild:
4562       /* PUSH Fabien */
4563       case WhiteHSideCastleFR:
4564       case BlackHSideCastleFR:
4565       /* POP Fabien */
4566         sprintf(user_move, "o-o\n");
4567         break;
4568       case WhiteQueenSideCastle:
4569       case BlackQueenSideCastle:
4570       case WhiteKingSideCastleWild:
4571       case BlackKingSideCastleWild:
4572       /* PUSH Fabien */
4573       case WhiteASideCastleFR:
4574       case BlackASideCastleFR:
4575       /* POP Fabien */
4576         sprintf(user_move, "o-o-o\n");
4577         break;
4578       case WhitePromotionQueen:
4579       case BlackPromotionQueen:
4580       case WhitePromotionRook:
4581       case BlackPromotionRook:
4582       case WhitePromotionBishop:
4583       case BlackPromotionBishop:
4584       case WhitePromotionKnight:
4585       case BlackPromotionKnight:
4586       case WhitePromotionKing:
4587       case BlackPromotionKing:
4588       case WhitePromotionChancellor:
4589       case BlackPromotionChancellor:
4590       case WhitePromotionArchbishop:
4591       case BlackPromotionArchbishop:
4592         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4593             sprintf(user_move, "%c%c%c%c=%c\n",
4594                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4595                 PieceToChar(WhiteFerz));
4596         else if(gameInfo.variant == VariantGreat)
4597             sprintf(user_move, "%c%c%c%c=%c\n",
4598                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4599                 PieceToChar(WhiteMan));
4600         else
4601             sprintf(user_move, "%c%c%c%c=%c\n",
4602                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4603                 PieceToChar(PromoPiece(moveType)));
4604         break;
4605       case WhiteDrop:
4606       case BlackDrop:
4607         sprintf(user_move, "%c@%c%c\n",
4608                 ToUpper(PieceToChar((ChessSquare) fromX)),
4609                 AAA + toX, ONE + toY);
4610         break;
4611       case NormalMove:
4612       case WhiteCapturesEnPassant:
4613       case BlackCapturesEnPassant:
4614       case IllegalMove:  /* could be a variant we don't quite understand */
4615         sprintf(user_move, "%c%c%c%c\n",
4616                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4617         break;
4618     }
4619     SendToICS(user_move);
4620     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4621         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4622 }
4623
4624 void
4625 UploadGameEvent()
4626 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4627     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4628     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4629     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4630         DisplayError("You cannot do this while you are playing or observing", 0);
4631         return;
4632     }
4633     if(gameMode != IcsExamining) { // is this ever not the case?
4634         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4635
4636         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4637             sprintf(command, "match %s", ics_handle);
4638         } else { // on FICS we must first go to general examine mode
4639             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4640         }
4641         if(gameInfo.variant != VariantNormal) {
4642             // try figure out wild number, as xboard names are not always valid on ICS
4643             for(i=1; i<=36; i++) {
4644                 sprintf(buf, "wild/%d", i);
4645                 if(StringToVariant(buf) == gameInfo.variant) break;
4646             }
4647             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4648             else if(i == 22) sprintf(buf, "%s fr\n", command);
4649             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4650         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4651         SendToICS(ics_prefix);
4652         SendToICS(buf);
4653         if(startedFromSetupPosition || backwardMostMove != 0) {
4654           fen = PositionToFEN(backwardMostMove, NULL);
4655           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4656             sprintf(buf, "loadfen %s\n", fen);
4657             SendToICS(buf);
4658           } else { // FICS: everything has to set by separate bsetup commands
4659             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4660             sprintf(buf, "bsetup fen %s\n", fen);
4661             SendToICS(buf);
4662             if(!WhiteOnMove(backwardMostMove)) {
4663                 SendToICS("bsetup tomove black\n");
4664             }
4665             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4666             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4667             SendToICS(buf);
4668             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4669             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4670             SendToICS(buf);
4671             i = boards[backwardMostMove][EP_STATUS];
4672             if(i >= 0) { // set e.p.
4673                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4674                 SendToICS(buf);
4675             }
4676             bsetup++;
4677           }
4678         }
4679       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4680             SendToICS("bsetup done\n"); // switch to normal examining.
4681     }
4682     for(i = backwardMostMove; i<last; i++) {
4683         char buf[20];
4684         sprintf(buf, "%s\n", parseList[i]);
4685         SendToICS(buf);
4686     }
4687     SendToICS(ics_prefix);
4688     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4689 }
4690
4691 void
4692 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4693      int rf, ff, rt, ft;
4694      char promoChar;
4695      char move[7];
4696 {
4697     if (rf == DROP_RANK) {
4698         sprintf(move, "%c@%c%c\n",
4699                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4700     } else {
4701         if (promoChar == 'x' || promoChar == NULLCHAR) {
4702             sprintf(move, "%c%c%c%c\n",
4703                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4704         } else {
4705             sprintf(move, "%c%c%c%c%c\n",
4706                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4707         }
4708     }
4709 }
4710
4711 void
4712 ProcessICSInitScript(f)
4713      FILE *f;
4714 {
4715     char buf[MSG_SIZ];
4716
4717     while (fgets(buf, MSG_SIZ, f)) {
4718         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4719     }
4720
4721     fclose(f);
4722 }
4723
4724
4725 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4726 void
4727 AlphaRank(char *move, int n)
4728 {
4729 //    char *p = move, c; int x, y;
4730
4731     if (appData.debugMode) {
4732         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4733     }
4734
4735     if(move[1]=='*' && 
4736        move[2]>='0' && move[2]<='9' &&
4737        move[3]>='a' && move[3]<='x'    ) {
4738         move[1] = '@';
4739         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4740         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4741     } else
4742     if(move[0]>='0' && move[0]<='9' &&
4743        move[1]>='a' && move[1]<='x' &&
4744        move[2]>='0' && move[2]<='9' &&
4745        move[3]>='a' && move[3]<='x'    ) {
4746         /* input move, Shogi -> normal */
4747         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4748         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4749         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4750         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4751     } else
4752     if(move[1]=='@' &&
4753        move[3]>='0' && move[3]<='9' &&
4754        move[2]>='a' && move[2]<='x'    ) {
4755         move[1] = '*';
4756         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4757         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4758     } else
4759     if(
4760        move[0]>='a' && move[0]<='x' &&
4761        move[3]>='0' && move[3]<='9' &&
4762        move[2]>='a' && move[2]<='x'    ) {
4763          /* output move, normal -> Shogi */
4764         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4765         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4766         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4767         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4768         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4769     }
4770     if (appData.debugMode) {
4771         fprintf(debugFP, "   out = '%s'\n", move);
4772     }
4773 }
4774
4775 char yy_textstr[8000];
4776
4777 /* Parser for moves from gnuchess, ICS, or user typein box */
4778 Boolean
4779 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4780      char *move;
4781      int moveNum;
4782      ChessMove *moveType;
4783      int *fromX, *fromY, *toX, *toY;
4784      char *promoChar;
4785 {       
4786     if (appData.debugMode) {
4787         fprintf(debugFP, "move to parse: %s\n", move);
4788     }
4789     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4790
4791     switch (*moveType) {
4792       case WhitePromotionChancellor:
4793       case BlackPromotionChancellor:
4794       case WhitePromotionArchbishop:
4795       case BlackPromotionArchbishop:
4796       case WhitePromotionQueen:
4797       case BlackPromotionQueen:
4798       case WhitePromotionRook:
4799       case BlackPromotionRook:
4800       case WhitePromotionBishop:
4801       case BlackPromotionBishop:
4802       case WhitePromotionKnight:
4803       case BlackPromotionKnight:
4804       case WhitePromotionKing:
4805       case BlackPromotionKing:
4806       case NormalMove:
4807       case WhiteCapturesEnPassant:
4808       case BlackCapturesEnPassant:
4809       case WhiteKingSideCastle:
4810       case WhiteQueenSideCastle:
4811       case BlackKingSideCastle:
4812       case BlackQueenSideCastle:
4813       case WhiteKingSideCastleWild:
4814       case WhiteQueenSideCastleWild:
4815       case BlackKingSideCastleWild:
4816       case BlackQueenSideCastleWild:
4817       /* Code added by Tord: */
4818       case WhiteHSideCastleFR:
4819       case WhiteASideCastleFR:
4820       case BlackHSideCastleFR:
4821       case BlackASideCastleFR:
4822       /* End of code added by Tord */
4823       case IllegalMove:         /* bug or odd chess variant */
4824         *fromX = currentMoveString[0] - AAA;
4825         *fromY = currentMoveString[1] - ONE;
4826         *toX = currentMoveString[2] - AAA;
4827         *toY = currentMoveString[3] - ONE;
4828         *promoChar = currentMoveString[4];
4829         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4830             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4831     if (appData.debugMode) {
4832         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4833     }
4834             *fromX = *fromY = *toX = *toY = 0;
4835             return FALSE;
4836         }
4837         if (appData.testLegality) {
4838           return (*moveType != IllegalMove);
4839         } else {
4840           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4841                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4842         }
4843
4844       case WhiteDrop:
4845       case BlackDrop:
4846         *fromX = *moveType == WhiteDrop ?
4847           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4848           (int) CharToPiece(ToLower(currentMoveString[0]));
4849         *fromY = DROP_RANK;
4850         *toX = currentMoveString[2] - AAA;
4851         *toY = currentMoveString[3] - ONE;
4852         *promoChar = NULLCHAR;
4853         return TRUE;
4854
4855       case AmbiguousMove:
4856       case ImpossibleMove:
4857       case (ChessMove) 0:       /* end of file */
4858       case ElapsedTime:
4859       case Comment:
4860       case PGNTag:
4861       case NAG:
4862       case WhiteWins:
4863       case BlackWins:
4864       case GameIsDrawn:
4865       default:
4866     if (appData.debugMode) {
4867         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4868     }
4869         /* bug? */
4870         *fromX = *fromY = *toX = *toY = 0;
4871         *promoChar = NULLCHAR;
4872         return FALSE;
4873     }
4874 }
4875
4876
4877 void
4878 ParsePV(char *pv, Boolean storeComments)
4879 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4880   int fromX, fromY, toX, toY; char promoChar;
4881   ChessMove moveType;
4882   Boolean valid;
4883   int nr = 0;
4884
4885   endPV = forwardMostMove;
4886   do {
4887     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4888     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4889     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4890 if(appData.debugMode){
4891 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);
4892 }
4893     if(!valid && nr == 0 &&
4894        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4895         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4896         // Hande case where played move is different from leading PV move
4897         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4898         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4899         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4900         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4901           endPV += 2; // if position different, keep this
4902           moveList[endPV-1][0] = fromX + AAA;
4903           moveList[endPV-1][1] = fromY + ONE;
4904           moveList[endPV-1][2] = toX + AAA;
4905           moveList[endPV-1][3] = toY + ONE;
4906           parseList[endPV-1][0] = NULLCHAR;
4907           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4908         }
4909       }
4910     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4911     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4912     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4913     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4914         valid++; // allow comments in PV
4915         continue;
4916     }
4917     nr++;
4918     if(endPV+1 > framePtr) break; // no space, truncate
4919     if(!valid) break;
4920     endPV++;
4921     CopyBoard(boards[endPV], boards[endPV-1]);
4922     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4923     moveList[endPV-1][0] = fromX + AAA;
4924     moveList[endPV-1][1] = fromY + ONE;
4925     moveList[endPV-1][2] = toX + AAA;
4926     moveList[endPV-1][3] = toY + ONE;
4927     if(storeComments)
4928         CoordsToAlgebraic(boards[endPV - 1],
4929                              PosFlags(endPV - 1),
4930                              fromY, fromX, toY, toX, promoChar,
4931                              parseList[endPV - 1]);
4932     else
4933         parseList[endPV-1][0] = NULLCHAR;
4934   } while(valid);
4935   currentMove = endPV;
4936   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4937   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4938                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4939   DrawPosition(TRUE, boards[currentMove]);
4940 }
4941
4942 static int lastX, lastY;
4943
4944 Boolean
4945 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4946 {
4947         int startPV;
4948         char *p;
4949
4950         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4951         lastX = x; lastY = y;
4952         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4953         startPV = index;
4954         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4955         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4956         index = startPV;
4957         do{ while(buf[index] && buf[index] != '\n') index++;
4958         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4959         buf[index] = 0;
4960         ParsePV(buf+startPV, FALSE);
4961         *start = startPV; *end = index-1;
4962         return TRUE;
4963 }
4964
4965 Boolean
4966 LoadPV(int x, int y)
4967 { // called on right mouse click to load PV
4968   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4969   lastX = x; lastY = y;
4970   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4971   return TRUE;
4972 }
4973
4974 void
4975 UnLoadPV()
4976 {
4977   if(endPV < 0) return;
4978   endPV = -1;
4979   currentMove = forwardMostMove;
4980   ClearPremoveHighlights();
4981   DrawPosition(TRUE, boards[currentMove]);
4982 }
4983
4984 void
4985 MovePV(int x, int y, int h)
4986 { // step through PV based on mouse coordinates (called on mouse move)
4987   int margin = h>>3, step = 0;
4988
4989   if(endPV < 0) return;
4990   // we must somehow check if right button is still down (might be released off board!)
4991   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4992   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4993   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4994   if(!step) return;
4995   lastX = x; lastY = y;
4996   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4997   currentMove += step;
4998   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4999   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5000                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5001   DrawPosition(FALSE, boards[currentMove]);
5002 }
5003
5004
5005 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5006 // All positions will have equal probability, but the current method will not provide a unique
5007 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5008 #define DARK 1
5009 #define LITE 2
5010 #define ANY 3
5011
5012 int squaresLeft[4];
5013 int piecesLeft[(int)BlackPawn];
5014 int seed, nrOfShuffles;
5015
5016 void GetPositionNumber()
5017 {       // sets global variable seed
5018         int i;
5019
5020         seed = appData.defaultFrcPosition;
5021         if(seed < 0) { // randomize based on time for negative FRC position numbers
5022                 for(i=0; i<50; i++) seed += random();
5023                 seed = random() ^ random() >> 8 ^ random() << 8;
5024                 if(seed<0) seed = -seed;
5025         }
5026 }
5027
5028 int put(Board board, int pieceType, int rank, int n, int shade)
5029 // put the piece on the (n-1)-th empty squares of the given shade
5030 {
5031         int i;
5032
5033         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5034                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5035                         board[rank][i] = (ChessSquare) pieceType;
5036                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5037                         squaresLeft[ANY]--;
5038                         piecesLeft[pieceType]--; 
5039                         return i;
5040                 }
5041         }
5042         return -1;
5043 }
5044
5045
5046 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5047 // calculate where the next piece goes, (any empty square), and put it there
5048 {
5049         int i;
5050
5051         i = seed % squaresLeft[shade];
5052         nrOfShuffles *= squaresLeft[shade];
5053         seed /= squaresLeft[shade];
5054         put(board, pieceType, rank, i, shade);
5055 }
5056
5057 void AddTwoPieces(Board board, int pieceType, int rank)
5058 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5059 {
5060         int i, n=squaresLeft[ANY], j=n-1, k;
5061
5062         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5063         i = seed % k;  // pick one
5064         nrOfShuffles *= k;
5065         seed /= k;
5066         while(i >= j) i -= j--;
5067         j = n - 1 - j; i += j;
5068         put(board, pieceType, rank, j, ANY);
5069         put(board, pieceType, rank, i, ANY);
5070 }
5071
5072 void SetUpShuffle(Board board, int number)
5073 {
5074         int i, p, first=1;
5075
5076         GetPositionNumber(); nrOfShuffles = 1;
5077
5078         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5079         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5080         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5081
5082         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5083
5084         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5085             p = (int) board[0][i];
5086             if(p < (int) BlackPawn) piecesLeft[p] ++;
5087             board[0][i] = EmptySquare;
5088         }
5089
5090         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5091             // shuffles restricted to allow normal castling put KRR first
5092             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5093                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5094             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5095                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5096             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5097                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5098             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5099                 put(board, WhiteRook, 0, 0, ANY);
5100             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5101         }
5102
5103         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5104             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5105             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5106                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5107                 while(piecesLeft[p] >= 2) {
5108                     AddOnePiece(board, p, 0, LITE);
5109                     AddOnePiece(board, p, 0, DARK);
5110                 }
5111                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5112             }
5113
5114         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5115             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5116             // but we leave King and Rooks for last, to possibly obey FRC restriction
5117             if(p == (int)WhiteRook) continue;
5118             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5119             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5120         }
5121
5122         // now everything is placed, except perhaps King (Unicorn) and Rooks
5123
5124         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5125             // Last King gets castling rights
5126             while(piecesLeft[(int)WhiteUnicorn]) {
5127                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5128                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5129             }
5130
5131             while(piecesLeft[(int)WhiteKing]) {
5132                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5133                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5134             }
5135
5136
5137         } else {
5138             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5139             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5140         }
5141
5142         // Only Rooks can be left; simply place them all
5143         while(piecesLeft[(int)WhiteRook]) {
5144                 i = put(board, WhiteRook, 0, 0, ANY);
5145                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5146                         if(first) {
5147                                 first=0;
5148                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5149                         }
5150                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5151                 }
5152         }
5153         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5154             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5155         }
5156
5157         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5158 }
5159
5160 int SetCharTable( char *table, const char * map )
5161 /* [HGM] moved here from winboard.c because of its general usefulness */
5162 /*       Basically a safe strcpy that uses the last character as King */
5163 {
5164     int result = FALSE; int NrPieces;
5165
5166     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5167                     && NrPieces >= 12 && !(NrPieces&1)) {
5168         int i; /* [HGM] Accept even length from 12 to 34 */
5169
5170         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5171         for( i=0; i<NrPieces/2-1; i++ ) {
5172             table[i] = map[i];
5173             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5174         }
5175         table[(int) WhiteKing]  = map[NrPieces/2-1];
5176         table[(int) BlackKing]  = map[NrPieces-1];
5177
5178         result = TRUE;
5179     }
5180
5181     return result;
5182 }
5183
5184 void Prelude(Board board)
5185 {       // [HGM] superchess: random selection of exo-pieces
5186         int i, j, k; ChessSquare p; 
5187         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5188
5189         GetPositionNumber(); // use FRC position number
5190
5191         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5192             SetCharTable(pieceToChar, appData.pieceToCharTable);
5193             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5194                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5195         }
5196
5197         j = seed%4;                 seed /= 4; 
5198         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5199         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5200         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5201         j = seed%3 + (seed%3 >= j); seed /= 3; 
5202         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5203         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5204         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5205         j = seed%3;                 seed /= 3; 
5206         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5207         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5208         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5209         j = seed%2 + (seed%2 >= j); seed /= 2; 
5210         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5211         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5212         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5213         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5214         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5215         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5216         put(board, exoPieces[0],    0, 0, ANY);
5217         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5218 }
5219
5220 void
5221 InitPosition(redraw)
5222      int redraw;
5223 {
5224     ChessSquare (* pieces)[BOARD_FILES];
5225     int i, j, pawnRow, overrule,
5226     oldx = gameInfo.boardWidth,
5227     oldy = gameInfo.boardHeight,
5228     oldh = gameInfo.holdingsWidth,
5229     oldv = gameInfo.variant;
5230
5231     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5232
5233     /* [AS] Initialize pv info list [HGM] and game status */
5234     {
5235         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5236             pvInfoList[i].depth = 0;
5237             boards[i][EP_STATUS] = EP_NONE;
5238             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5239         }
5240
5241         initialRulePlies = 0; /* 50-move counter start */
5242
5243         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5244         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5245     }
5246
5247     
5248     /* [HGM] logic here is completely changed. In stead of full positions */
5249     /* the initialized data only consist of the two backranks. The switch */
5250     /* selects which one we will use, which is than copied to the Board   */
5251     /* initialPosition, which for the rest is initialized by Pawns and    */
5252     /* empty squares. This initial position is then copied to boards[0],  */
5253     /* possibly after shuffling, so that it remains available.            */
5254
5255     gameInfo.holdingsWidth = 0; /* default board sizes */
5256     gameInfo.boardWidth    = 8;
5257     gameInfo.boardHeight   = 8;
5258     gameInfo.holdingsSize  = 0;
5259     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5260     for(i=0; i<BOARD_FILES-2; i++)
5261       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5262     initialPosition[EP_STATUS] = EP_NONE;
5263     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5264
5265     switch (gameInfo.variant) {
5266     case VariantFischeRandom:
5267       shuffleOpenings = TRUE;
5268     default:
5269       pieces = FIDEArray;
5270       break;
5271     case VariantShatranj:
5272       pieces = ShatranjArray;
5273       nrCastlingRights = 0;
5274       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5275       break;
5276     case VariantMakruk:
5277       pieces = makrukArray;
5278       nrCastlingRights = 0;
5279       startedFromSetupPosition = TRUE;
5280       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5281       break;
5282     case VariantTwoKings:
5283       pieces = twoKingsArray;
5284       break;
5285     case VariantCapaRandom:
5286       shuffleOpenings = TRUE;
5287     case VariantCapablanca:
5288       pieces = CapablancaArray;
5289       gameInfo.boardWidth = 10;
5290       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5291       break;
5292     case VariantGothic:
5293       pieces = GothicArray;
5294       gameInfo.boardWidth = 10;
5295       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5296       break;
5297     case VariantJanus:
5298       pieces = JanusArray;
5299       gameInfo.boardWidth = 10;
5300       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5301       nrCastlingRights = 6;
5302         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5303         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5304         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5305         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5306         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5307         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5308       break;
5309     case VariantFalcon:
5310       pieces = FalconArray;
5311       gameInfo.boardWidth = 10;
5312       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5313       break;
5314     case VariantXiangqi:
5315       pieces = XiangqiArray;
5316       gameInfo.boardWidth  = 9;
5317       gameInfo.boardHeight = 10;
5318       nrCastlingRights = 0;
5319       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5320       break;
5321     case VariantShogi:
5322       pieces = ShogiArray;
5323       gameInfo.boardWidth  = 9;
5324       gameInfo.boardHeight = 9;
5325       gameInfo.holdingsSize = 7;
5326       nrCastlingRights = 0;
5327       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5328       break;
5329     case VariantCourier:
5330       pieces = CourierArray;
5331       gameInfo.boardWidth  = 12;
5332       nrCastlingRights = 0;
5333       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5334       break;
5335     case VariantKnightmate:
5336       pieces = KnightmateArray;
5337       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5338       break;
5339     case VariantFairy:
5340       pieces = fairyArray;
5341       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5342       break;
5343     case VariantGreat:
5344       pieces = GreatArray;
5345       gameInfo.boardWidth = 10;
5346       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5347       gameInfo.holdingsSize = 8;
5348       break;
5349     case VariantSuper:
5350       pieces = FIDEArray;
5351       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5352       gameInfo.holdingsSize = 8;
5353       startedFromSetupPosition = TRUE;
5354       break;
5355     case VariantCrazyhouse:
5356     case VariantBughouse:
5357       pieces = FIDEArray;
5358       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5359       gameInfo.holdingsSize = 5;
5360       break;
5361     case VariantWildCastle:
5362       pieces = FIDEArray;
5363       /* !!?shuffle with kings guaranteed to be on d or e file */
5364       shuffleOpenings = 1;
5365       break;
5366     case VariantNoCastle:
5367       pieces = FIDEArray;
5368       nrCastlingRights = 0;
5369       /* !!?unconstrained back-rank shuffle */
5370       shuffleOpenings = 1;
5371       break;
5372     }
5373
5374     overrule = 0;
5375     if(appData.NrFiles >= 0) {
5376         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5377         gameInfo.boardWidth = appData.NrFiles;
5378     }
5379     if(appData.NrRanks >= 0) {
5380         gameInfo.boardHeight = appData.NrRanks;
5381     }
5382     if(appData.holdingsSize >= 0) {
5383         i = appData.holdingsSize;
5384         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5385         gameInfo.holdingsSize = i;
5386     }
5387     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5388     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5389         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5390
5391     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5392     if(pawnRow < 1) pawnRow = 1;
5393     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5394
5395     /* User pieceToChar list overrules defaults */
5396     if(appData.pieceToCharTable != NULL)
5397         SetCharTable(pieceToChar, appData.pieceToCharTable);
5398
5399     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5400
5401         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5402             s = (ChessSquare) 0; /* account holding counts in guard band */
5403         for( i=0; i<BOARD_HEIGHT; i++ )
5404             initialPosition[i][j] = s;
5405
5406         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5407         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5408         initialPosition[pawnRow][j] = WhitePawn;
5409         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5410         if(gameInfo.variant == VariantXiangqi) {
5411             if(j&1) {
5412                 initialPosition[pawnRow][j] = 
5413                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5414                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5415                    initialPosition[2][j] = WhiteCannon;
5416                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5417                 }
5418             }
5419         }
5420         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5421     }
5422     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5423
5424             j=BOARD_LEFT+1;
5425             initialPosition[1][j] = WhiteBishop;
5426             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5427             j=BOARD_RGHT-2;
5428             initialPosition[1][j] = WhiteRook;
5429             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5430     }
5431
5432     if( nrCastlingRights == -1) {
5433         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5434         /*       This sets default castling rights from none to normal corners   */
5435         /* Variants with other castling rights must set them themselves above    */
5436         nrCastlingRights = 6;
5437        
5438         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5439         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5440         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5441         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5442         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5443         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5444      }
5445
5446      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5447      if(gameInfo.variant == VariantGreat) { // promotion commoners
5448         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5449         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5450         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5451         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5452      }
5453   if (appData.debugMode) {
5454     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5455   }
5456     if(shuffleOpenings) {
5457         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5458         startedFromSetupPosition = TRUE;
5459     }
5460     if(startedFromPositionFile) {
5461       /* [HGM] loadPos: use PositionFile for every new game */
5462       CopyBoard(initialPosition, filePosition);
5463       for(i=0; i<nrCastlingRights; i++)
5464           initialRights[i] = filePosition[CASTLING][i];
5465       startedFromSetupPosition = TRUE;
5466     }
5467
5468     CopyBoard(boards[0], initialPosition);
5469
5470     if(oldx != gameInfo.boardWidth ||
5471        oldy != gameInfo.boardHeight ||
5472        oldh != gameInfo.holdingsWidth
5473 #ifdef GOTHIC
5474        || oldv == VariantGothic ||        // For licensing popups
5475        gameInfo.variant == VariantGothic
5476 #endif
5477 #ifdef FALCON
5478        || oldv == VariantFalcon ||
5479        gameInfo.variant == VariantFalcon
5480 #endif
5481                                          )
5482             InitDrawingSizes(-2 ,0);
5483
5484     if (redraw)
5485       DrawPosition(TRUE, boards[currentMove]);
5486 }
5487
5488 void
5489 SendBoard(cps, moveNum)
5490      ChessProgramState *cps;
5491      int moveNum;
5492 {
5493     char message[MSG_SIZ];
5494     
5495     if (cps->useSetboard) {
5496       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5497       sprintf(message, "setboard %s\n", fen);
5498       SendToProgram(message, cps);
5499       free(fen);
5500
5501     } else {
5502       ChessSquare *bp;
5503       int i, j;
5504       /* Kludge to set black to move, avoiding the troublesome and now
5505        * deprecated "black" command.
5506        */
5507       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5508
5509       SendToProgram("edit\n", cps);
5510       SendToProgram("#\n", cps);
5511       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5512         bp = &boards[moveNum][i][BOARD_LEFT];
5513         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5514           if ((int) *bp < (int) BlackPawn) {
5515             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5516                     AAA + j, ONE + i);
5517             if(message[0] == '+' || message[0] == '~') {
5518                 sprintf(message, "%c%c%c+\n",
5519                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5520                         AAA + j, ONE + i);
5521             }
5522             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5523                 message[1] = BOARD_RGHT   - 1 - j + '1';
5524                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5525             }
5526             SendToProgram(message, cps);
5527           }
5528         }
5529       }
5530     
5531       SendToProgram("c\n", cps);
5532       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5533         bp = &boards[moveNum][i][BOARD_LEFT];
5534         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5535           if (((int) *bp != (int) EmptySquare)
5536               && ((int) *bp >= (int) BlackPawn)) {
5537             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5538                     AAA + j, ONE + i);
5539             if(message[0] == '+' || message[0] == '~') {
5540                 sprintf(message, "%c%c%c+\n",
5541                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5542                         AAA + j, ONE + i);
5543             }
5544             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5545                 message[1] = BOARD_RGHT   - 1 - j + '1';
5546                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5547             }
5548             SendToProgram(message, cps);
5549           }
5550         }
5551       }
5552     
5553       SendToProgram(".\n", cps);
5554     }
5555     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5556 }
5557
5558 static int autoQueen; // [HGM] oneclick
5559
5560 int
5561 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5562 {
5563     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5564     /* [HGM] add Shogi promotions */
5565     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5566     ChessSquare piece;
5567     ChessMove moveType;
5568     Boolean premove;
5569
5570     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5571     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5572
5573     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5574       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5575         return FALSE;
5576
5577     piece = boards[currentMove][fromY][fromX];
5578     if(gameInfo.variant == VariantShogi) {
5579         promotionZoneSize = 3;
5580         highestPromotingPiece = (int)WhiteFerz;
5581     } else if(gameInfo.variant == VariantMakruk) {
5582         promotionZoneSize = 3;
5583     }
5584
5585     // next weed out all moves that do not touch the promotion zone at all
5586     if((int)piece >= BlackPawn) {
5587         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5588              return FALSE;
5589         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5590     } else {
5591         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5592            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5593     }
5594
5595     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5596
5597     // weed out mandatory Shogi promotions
5598     if(gameInfo.variant == VariantShogi) {
5599         if(piece >= BlackPawn) {
5600             if(toY == 0 && piece == BlackPawn ||
5601                toY == 0 && piece == BlackQueen ||
5602                toY <= 1 && piece == BlackKnight) {
5603                 *promoChoice = '+';
5604                 return FALSE;
5605             }
5606         } else {
5607             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5608                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5609                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5610                 *promoChoice = '+';
5611                 return FALSE;
5612             }
5613         }
5614     }
5615
5616     // weed out obviously illegal Pawn moves
5617     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5618         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5619         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5620         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5621         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5622         // note we are not allowed to test for valid (non-)capture, due to premove
5623     }
5624
5625     // we either have a choice what to promote to, or (in Shogi) whether to promote
5626     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5627         *promoChoice = PieceToChar(BlackFerz);  // no choice
5628         return FALSE;
5629     }
5630     if(autoQueen) { // predetermined
5631         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5632              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5633         else *promoChoice = PieceToChar(BlackQueen);
5634         return FALSE;
5635     }
5636
5637     // suppress promotion popup on illegal moves that are not premoves
5638     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5639               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5640     if(appData.testLegality && !premove) {
5641         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5642                         fromY, fromX, toY, toX, NULLCHAR);
5643         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5644            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5645             return FALSE;
5646     }
5647
5648     return TRUE;
5649 }
5650
5651 int
5652 InPalace(row, column)
5653      int row, column;
5654 {   /* [HGM] for Xiangqi */
5655     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5656          column < (BOARD_WIDTH + 4)/2 &&
5657          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5658     return FALSE;
5659 }
5660
5661 int
5662 PieceForSquare (x, y)
5663      int x;
5664      int y;
5665 {
5666   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5667      return -1;
5668   else
5669      return boards[currentMove][y][x];
5670 }
5671
5672 int
5673 OKToStartUserMove(x, y)
5674      int x, y;
5675 {
5676     ChessSquare from_piece;
5677     int white_piece;
5678
5679     if (matchMode) return FALSE;
5680     if (gameMode == EditPosition) return TRUE;
5681
5682     if (x >= 0 && y >= 0)
5683       from_piece = boards[currentMove][y][x];
5684     else
5685       from_piece = EmptySquare;
5686
5687     if (from_piece == EmptySquare) return FALSE;
5688
5689     white_piece = (int)from_piece >= (int)WhitePawn &&
5690       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5691
5692     switch (gameMode) {
5693       case PlayFromGameFile:
5694       case AnalyzeFile:
5695       case TwoMachinesPlay:
5696       case EndOfGame:
5697         return FALSE;
5698
5699       case IcsObserving:
5700       case IcsIdle:
5701         return FALSE;
5702
5703       case MachinePlaysWhite:
5704       case IcsPlayingBlack:
5705         if (appData.zippyPlay) return FALSE;
5706         if (white_piece) {
5707             DisplayMoveError(_("You are playing Black"));
5708             return FALSE;
5709         }
5710         break;
5711
5712       case MachinePlaysBlack:
5713       case IcsPlayingWhite:
5714         if (appData.zippyPlay) return FALSE;
5715         if (!white_piece) {
5716             DisplayMoveError(_("You are playing White"));
5717             return FALSE;
5718         }
5719         break;
5720
5721       case EditGame:
5722         if (!white_piece && WhiteOnMove(currentMove)) {
5723             DisplayMoveError(_("It is White's turn"));
5724             return FALSE;
5725         }           
5726         if (white_piece && !WhiteOnMove(currentMove)) {
5727             DisplayMoveError(_("It is Black's turn"));
5728             return FALSE;
5729         }           
5730         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5731             /* Editing correspondence game history */
5732             /* Could disallow this or prompt for confirmation */
5733             cmailOldMove = -1;
5734         }
5735         break;
5736
5737       case BeginningOfGame:
5738         if (appData.icsActive) return FALSE;
5739         if (!appData.noChessProgram) {
5740             if (!white_piece) {
5741                 DisplayMoveError(_("You are playing White"));
5742                 return FALSE;
5743             }
5744         }
5745         break;
5746         
5747       case Training:
5748         if (!white_piece && WhiteOnMove(currentMove)) {
5749             DisplayMoveError(_("It is White's turn"));
5750             return FALSE;
5751         }           
5752         if (white_piece && !WhiteOnMove(currentMove)) {
5753             DisplayMoveError(_("It is Black's turn"));
5754             return FALSE;
5755         }           
5756         break;
5757
5758       default:
5759       case IcsExamining:
5760         break;
5761     }
5762     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5763         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5764         && gameMode != AnalyzeFile && gameMode != Training) {
5765         DisplayMoveError(_("Displayed position is not current"));
5766         return FALSE;
5767     }
5768     return TRUE;
5769 }
5770
5771 Boolean
5772 OnlyMove(int *x, int *y, Boolean captures) {
5773     DisambiguateClosure cl;
5774     if (appData.zippyPlay) return FALSE;
5775     switch(gameMode) {
5776       case MachinePlaysBlack:
5777       case IcsPlayingWhite:
5778       case BeginningOfGame:
5779         if(!WhiteOnMove(currentMove)) return FALSE;
5780         break;
5781       case MachinePlaysWhite:
5782       case IcsPlayingBlack:
5783         if(WhiteOnMove(currentMove)) return FALSE;
5784         break;
5785       default:
5786         return FALSE;
5787     }
5788     cl.pieceIn = EmptySquare; 
5789     cl.rfIn = *y;
5790     cl.ffIn = *x;
5791     cl.rtIn = -1;
5792     cl.ftIn = -1;
5793     cl.promoCharIn = NULLCHAR;
5794     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5795     if( cl.kind == NormalMove ||
5796         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5797         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5798         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5799         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5800       fromX = cl.ff;
5801       fromY = cl.rf;
5802       *x = cl.ft;
5803       *y = cl.rt;
5804       return TRUE;
5805     }
5806     if(cl.kind != ImpossibleMove) return FALSE;
5807     cl.pieceIn = EmptySquare;
5808     cl.rfIn = -1;
5809     cl.ffIn = -1;
5810     cl.rtIn = *y;
5811     cl.ftIn = *x;
5812     cl.promoCharIn = NULLCHAR;
5813     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5814     if( cl.kind == NormalMove ||
5815         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5816         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5817         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5818         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5819       fromX = cl.ff;
5820       fromY = cl.rf;
5821       *x = cl.ft;
5822       *y = cl.rt;
5823       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5824       return TRUE;
5825     }
5826     return FALSE;
5827 }
5828
5829 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5830 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5831 int lastLoadGameUseList = FALSE;
5832 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5833 ChessMove lastLoadGameStart = (ChessMove) 0;
5834
5835 ChessMove
5836 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5837      int fromX, fromY, toX, toY;
5838      int promoChar;
5839      Boolean captureOwn;
5840 {
5841     ChessMove moveType;
5842     ChessSquare pdown, pup;
5843
5844     /* Check if the user is playing in turn.  This is complicated because we
5845        let the user "pick up" a piece before it is his turn.  So the piece he
5846        tried to pick up may have been captured by the time he puts it down!
5847        Therefore we use the color the user is supposed to be playing in this
5848        test, not the color of the piece that is currently on the starting
5849        square---except in EditGame mode, where the user is playing both
5850        sides; fortunately there the capture race can't happen.  (It can
5851        now happen in IcsExamining mode, but that's just too bad.  The user
5852        will get a somewhat confusing message in that case.)
5853        */
5854
5855     switch (gameMode) {
5856       case PlayFromGameFile:
5857       case AnalyzeFile:
5858       case TwoMachinesPlay:
5859       case EndOfGame:
5860       case IcsObserving:
5861       case IcsIdle:
5862         /* We switched into a game mode where moves are not accepted,
5863            perhaps while the mouse button was down. */
5864         return ImpossibleMove;
5865
5866       case MachinePlaysWhite:
5867         /* User is moving for Black */
5868         if (WhiteOnMove(currentMove)) {
5869             DisplayMoveError(_("It is White's turn"));
5870             return ImpossibleMove;
5871         }
5872         break;
5873
5874       case MachinePlaysBlack:
5875         /* User is moving for White */
5876         if (!WhiteOnMove(currentMove)) {
5877             DisplayMoveError(_("It is Black's turn"));
5878             return ImpossibleMove;
5879         }
5880         break;
5881
5882       case EditGame:
5883       case IcsExamining:
5884       case BeginningOfGame:
5885       case AnalyzeMode:
5886       case Training:
5887         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5888             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5889             /* User is moving for Black */
5890             if (WhiteOnMove(currentMove)) {
5891                 DisplayMoveError(_("It is White's turn"));
5892                 return ImpossibleMove;
5893             }
5894         } else {
5895             /* User is moving for White */
5896             if (!WhiteOnMove(currentMove)) {
5897                 DisplayMoveError(_("It is Black's turn"));
5898                 return ImpossibleMove;
5899             }
5900         }
5901         break;
5902
5903       case IcsPlayingBlack:
5904         /* User is moving for Black */
5905         if (WhiteOnMove(currentMove)) {
5906             if (!appData.premove) {
5907                 DisplayMoveError(_("It is White's turn"));
5908             } else if (toX >= 0 && toY >= 0) {
5909                 premoveToX = toX;
5910                 premoveToY = toY;
5911                 premoveFromX = fromX;
5912                 premoveFromY = fromY;
5913                 premovePromoChar = promoChar;
5914                 gotPremove = 1;
5915                 if (appData.debugMode) 
5916                     fprintf(debugFP, "Got premove: fromX %d,"
5917                             "fromY %d, toX %d, toY %d\n",
5918                             fromX, fromY, toX, toY);
5919             }
5920             return ImpossibleMove;
5921         }
5922         break;
5923
5924       case IcsPlayingWhite:
5925         /* User is moving for White */
5926         if (!WhiteOnMove(currentMove)) {
5927             if (!appData.premove) {
5928                 DisplayMoveError(_("It is Black's turn"));
5929             } else if (toX >= 0 && toY >= 0) {
5930                 premoveToX = toX;
5931                 premoveToY = toY;
5932                 premoveFromX = fromX;
5933                 premoveFromY = fromY;
5934                 premovePromoChar = promoChar;
5935                 gotPremove = 1;
5936                 if (appData.debugMode) 
5937                     fprintf(debugFP, "Got premove: fromX %d,"
5938                             "fromY %d, toX %d, toY %d\n",
5939                             fromX, fromY, toX, toY);
5940             }
5941             return ImpossibleMove;
5942         }
5943         break;
5944
5945       default:
5946         break;
5947
5948       case EditPosition:
5949         /* EditPosition, empty square, or different color piece;
5950            click-click move is possible */
5951         if (toX == -2 || toY == -2) {
5952             boards[0][fromY][fromX] = EmptySquare;
5953             return AmbiguousMove;
5954         } else if (toX >= 0 && toY >= 0) {
5955             boards[0][toY][toX] = boards[0][fromY][fromX];
5956             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5957                 if(boards[0][fromY][0] != EmptySquare) {
5958                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5959                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5960                 }
5961             } else
5962             if(fromX == BOARD_RGHT+1) {
5963                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5964                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5965                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5966                 }
5967             } else
5968             boards[0][fromY][fromX] = EmptySquare;
5969             return AmbiguousMove;
5970         }
5971         return ImpossibleMove;
5972     }
5973
5974     if(toX < 0 || toY < 0) return ImpossibleMove;
5975     pdown = boards[currentMove][fromY][fromX];
5976     pup = boards[currentMove][toY][toX];
5977
5978     /* [HGM] If move started in holdings, it means a drop */
5979     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5980          if( pup != EmptySquare ) return ImpossibleMove;
5981          if(appData.testLegality) {
5982              /* it would be more logical if LegalityTest() also figured out
5983               * which drops are legal. For now we forbid pawns on back rank.
5984               * Shogi is on its own here...
5985               */
5986              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5987                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5988                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5989          }
5990          return WhiteDrop; /* Not needed to specify white or black yet */
5991     }
5992
5993     /* [HGM] always test for legality, to get promotion info */
5994     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5995                                          fromY, fromX, toY, toX, promoChar);
5996     /* [HGM] but possibly ignore an IllegalMove result */
5997     if (appData.testLegality) {
5998         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5999             DisplayMoveError(_("Illegal move"));
6000             return ImpossibleMove;
6001         }
6002     }
6003
6004     return moveType;
6005     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
6006        function is made into one that returns an OK move type if FinishMove
6007        should be called. This to give the calling driver routine the
6008        opportunity to finish the userMove input with a promotion popup,
6009        without bothering the user with this for invalid or illegal moves */
6010
6011 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
6012 }
6013
6014 /* Common tail of UserMoveEvent and DropMenuEvent */
6015 int
6016 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6017      ChessMove moveType;
6018      int fromX, fromY, toX, toY;
6019      /*char*/int promoChar;
6020 {
6021     char *bookHit = 0;
6022
6023     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6024         // [HGM] superchess: suppress promotions to non-available piece
6025         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6026         if(WhiteOnMove(currentMove)) {
6027             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6028         } else {
6029             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6030         }
6031     }
6032
6033     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6034        move type in caller when we know the move is a legal promotion */
6035     if(moveType == NormalMove && promoChar)
6036         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6037
6038     /* [HGM] convert drag-and-drop piece drops to standard form */
6039     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6040          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6041            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6042                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6043            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6044            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6045            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6046            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6047          fromY = DROP_RANK;
6048     }
6049
6050     /* [HGM] <popupFix> The following if has been moved here from
6051        UserMoveEvent(). Because it seemed to belong here (why not allow
6052        piece drops in training games?), and because it can only be
6053        performed after it is known to what we promote. */
6054     if (gameMode == Training) {
6055       /* compare the move played on the board to the next move in the
6056        * game. If they match, display the move and the opponent's response. 
6057        * If they don't match, display an error message.
6058        */
6059       int saveAnimate;
6060       Board testBoard;
6061       CopyBoard(testBoard, boards[currentMove]);
6062       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6063
6064       if (CompareBoards(testBoard, boards[currentMove+1])) {
6065         ForwardInner(currentMove+1);
6066
6067         /* Autoplay the opponent's response.
6068          * if appData.animate was TRUE when Training mode was entered,
6069          * the response will be animated.
6070          */
6071         saveAnimate = appData.animate;
6072         appData.animate = animateTraining;
6073         ForwardInner(currentMove+1);
6074         appData.animate = saveAnimate;
6075
6076         /* check for the end of the game */
6077         if (currentMove >= forwardMostMove) {
6078           gameMode = PlayFromGameFile;
6079           ModeHighlight();
6080           SetTrainingModeOff();
6081           DisplayInformation(_("End of game"));
6082         }
6083       } else {
6084         DisplayError(_("Incorrect move"), 0);
6085       }
6086       return 1;
6087     }
6088
6089   /* Ok, now we know that the move is good, so we can kill
6090      the previous line in Analysis Mode */
6091   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6092                                 && currentMove < forwardMostMove) {
6093     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6094   }
6095
6096   /* If we need the chess program but it's dead, restart it */
6097   ResurrectChessProgram();
6098
6099   /* A user move restarts a paused game*/
6100   if (pausing)
6101     PauseEvent();
6102
6103   thinkOutput[0] = NULLCHAR;
6104
6105   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6106
6107   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6108
6109   if (gameMode == BeginningOfGame) {
6110     if (appData.noChessProgram) {
6111       gameMode = EditGame;
6112       SetGameInfo();
6113     } else {
6114       char buf[MSG_SIZ];
6115       gameMode = MachinePlaysBlack;
6116       StartClocks();
6117       SetGameInfo();
6118       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6119       DisplayTitle(buf);
6120       if (first.sendName) {
6121         sprintf(buf, "name %s\n", gameInfo.white);
6122         SendToProgram(buf, &first);
6123       }
6124       StartClocks();
6125     }
6126     ModeHighlight();
6127   }
6128
6129   /* Relay move to ICS or chess engine */
6130   if (appData.icsActive) {
6131     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6132         gameMode == IcsExamining) {
6133       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6134         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6135         SendToICS("draw ");
6136         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6137       }
6138       // also send plain move, in case ICS does not understand atomic claims
6139       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6140       ics_user_moved = 1;
6141     }
6142   } else {
6143     if (first.sendTime && (gameMode == BeginningOfGame ||
6144                            gameMode == MachinePlaysWhite ||
6145                            gameMode == MachinePlaysBlack)) {
6146       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6147     }
6148     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6149          // [HGM] book: if program might be playing, let it use book
6150         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6151         first.maybeThinking = TRUE;
6152     } else SendMoveToProgram(forwardMostMove-1, &first);
6153     if (currentMove == cmailOldMove + 1) {
6154       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6155     }
6156   }
6157
6158   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6159
6160   switch (gameMode) {
6161   case EditGame:
6162     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6163     case MT_NONE:
6164     case MT_CHECK:
6165       break;
6166     case MT_CHECKMATE:
6167     case MT_STAINMATE:
6168       if (WhiteOnMove(currentMove)) {
6169         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6170       } else {
6171         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6172       }
6173       break;
6174     case MT_STALEMATE:
6175       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6176       break;
6177     }
6178     break;
6179     
6180   case MachinePlaysBlack:
6181   case MachinePlaysWhite:
6182     /* disable certain menu options while machine is thinking */
6183     SetMachineThinkingEnables();
6184     break;
6185
6186   default:
6187     break;
6188   }
6189
6190   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6191         
6192   if(bookHit) { // [HGM] book: simulate book reply
6193         static char bookMove[MSG_SIZ]; // a bit generous?
6194
6195         programStats.nodes = programStats.depth = programStats.time = 
6196         programStats.score = programStats.got_only_move = 0;
6197         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6198
6199         strcpy(bookMove, "move ");
6200         strcat(bookMove, bookHit);
6201         HandleMachineMove(bookMove, &first);
6202   }
6203   return 1;
6204 }
6205
6206 void
6207 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6208      int fromX, fromY, toX, toY;
6209      int promoChar;
6210 {
6211     /* [HGM] This routine was added to allow calling of its two logical
6212        parts from other modules in the old way. Before, UserMoveEvent()
6213        automatically called FinishMove() if the move was OK, and returned
6214        otherwise. I separated the two, in order to make it possible to
6215        slip a promotion popup in between. But that it always needs two
6216        calls, to the first part, (now called UserMoveTest() ), and to
6217        FinishMove if the first part succeeded. Calls that do not need
6218        to do anything in between, can call this routine the old way. 
6219     */
6220     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6221 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6222     if(moveType == AmbiguousMove)
6223         DrawPosition(FALSE, boards[currentMove]);
6224     else if(moveType != ImpossibleMove && moveType != Comment)
6225         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6226 }
6227
6228 void
6229 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6230      Board board;
6231      int flags;
6232      ChessMove kind;
6233      int rf, ff, rt, ft;
6234      VOIDSTAR closure;
6235 {
6236     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6237     Markers *m = (Markers *) closure;
6238     if(rf == fromY && ff == fromX)
6239         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6240                          || kind == WhiteCapturesEnPassant
6241                          || kind == BlackCapturesEnPassant);
6242     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6243 }
6244
6245 void
6246 MarkTargetSquares(int clear)
6247 {
6248   int x, y;
6249   if(!appData.markers || !appData.highlightDragging || 
6250      !appData.testLegality || gameMode == EditPosition) return;
6251   if(clear) {
6252     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6253   } else {
6254     int capt = 0;
6255     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6256     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6257       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6258       if(capt)
6259       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6260     }
6261   }
6262   DrawPosition(TRUE, NULL);
6263 }
6264
6265 void LeftClick(ClickType clickType, int xPix, int yPix)
6266 {
6267     int x, y;
6268     Boolean saveAnimate;
6269     static int second = 0, promotionChoice = 0, dragging = 0;
6270     char promoChoice = NULLCHAR;
6271
6272     if(appData.seekGraph && appData.icsActive && loggedOn &&
6273         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6274         SeekGraphClick(clickType, xPix, yPix, 0);
6275         return;
6276     }
6277
6278     if (clickType == Press) ErrorPopDown();
6279     MarkTargetSquares(1);
6280
6281     x = EventToSquare(xPix, BOARD_WIDTH);
6282     y = EventToSquare(yPix, BOARD_HEIGHT);
6283     if (!flipView && y >= 0) {
6284         y = BOARD_HEIGHT - 1 - y;
6285     }
6286     if (flipView && x >= 0) {
6287         x = BOARD_WIDTH - 1 - x;
6288     }
6289
6290     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6291         if(clickType == Release) return; // ignore upclick of click-click destination
6292         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6293         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6294         if(gameInfo.holdingsWidth && 
6295                 (WhiteOnMove(currentMove) 
6296                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6297                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6298             // click in right holdings, for determining promotion piece
6299             ChessSquare p = boards[currentMove][y][x];
6300             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6301             if(p != EmptySquare) {
6302                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6303                 fromX = fromY = -1;
6304                 return;
6305             }
6306         }
6307         DrawPosition(FALSE, boards[currentMove]);
6308         return;
6309     }
6310
6311     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6312     if(clickType == Press
6313             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6314               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6315               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6316         return;
6317
6318     autoQueen = appData.alwaysPromoteToQueen;
6319
6320     if (fromX == -1) {
6321       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6322         if (clickType == Press) {
6323             /* First square */
6324             if (OKToStartUserMove(x, y)) {
6325                 fromX = x;
6326                 fromY = y;
6327                 second = 0;
6328                 MarkTargetSquares(0);
6329                 DragPieceBegin(xPix, yPix); dragging = 1;
6330                 if (appData.highlightDragging) {
6331                     SetHighlights(x, y, -1, -1);
6332                 }
6333             }
6334         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6335             DragPieceEnd(xPix, yPix); dragging = 0;
6336             DrawPosition(FALSE, NULL);
6337         }
6338         return;
6339       }
6340     }
6341
6342     /* fromX != -1 */
6343     if (clickType == Press && gameMode != EditPosition) {
6344         ChessSquare fromP;
6345         ChessSquare toP;
6346         int frc;
6347
6348         // ignore off-board to clicks
6349         if(y < 0 || x < 0) return;
6350
6351         /* Check if clicking again on the same color piece */
6352         fromP = boards[currentMove][fromY][fromX];
6353         toP = boards[currentMove][y][x];
6354         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6355         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6356              WhitePawn <= toP && toP <= WhiteKing &&
6357              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6358              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6359             (BlackPawn <= fromP && fromP <= BlackKing && 
6360              BlackPawn <= toP && toP <= BlackKing &&
6361              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6362              !(fromP == BlackKing && toP == BlackRook && frc))) {
6363             /* Clicked again on same color piece -- changed his mind */
6364             second = (x == fromX && y == fromY);
6365            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6366             if (appData.highlightDragging) {
6367                 SetHighlights(x, y, -1, -1);
6368             } else {
6369                 ClearHighlights();
6370             }
6371             if (OKToStartUserMove(x, y)) {
6372                 fromX = x;
6373                 fromY = y; dragging = 1;
6374                 MarkTargetSquares(0);
6375                 DragPieceBegin(xPix, yPix);
6376             }
6377             return;
6378            }
6379         }
6380         // ignore clicks on holdings
6381         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6382     }
6383
6384     if (clickType == Release && x == fromX && y == fromY) {
6385         DragPieceEnd(xPix, yPix); dragging = 0;
6386         if (appData.animateDragging) {
6387             /* Undo animation damage if any */
6388             DrawPosition(FALSE, NULL);
6389         }
6390         if (second) {
6391             /* Second up/down in same square; just abort move */
6392             second = 0;
6393             fromX = fromY = -1;
6394             ClearHighlights();
6395             gotPremove = 0;
6396             ClearPremoveHighlights();
6397         } else {
6398             /* First upclick in same square; start click-click mode */
6399             SetHighlights(x, y, -1, -1);
6400         }
6401         return;
6402     }
6403
6404     /* we now have a different from- and (possibly off-board) to-square */
6405     /* Completed move */
6406     toX = x;
6407     toY = y;
6408     saveAnimate = appData.animate;
6409     if (clickType == Press) {
6410         /* Finish clickclick move */
6411         if (appData.animate || appData.highlightLastMove) {
6412             SetHighlights(fromX, fromY, toX, toY);
6413         } else {
6414             ClearHighlights();
6415         }
6416     } else {
6417         /* Finish drag move */
6418         if (appData.highlightLastMove) {
6419             SetHighlights(fromX, fromY, toX, toY);
6420         } else {
6421             ClearHighlights();
6422         }
6423         DragPieceEnd(xPix, yPix); dragging = 0;
6424         /* Don't animate move and drag both */
6425         appData.animate = FALSE;
6426     }
6427
6428     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6429     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6430         ChessSquare piece = boards[currentMove][fromY][fromX];
6431         if(gameMode == EditPosition && piece != EmptySquare &&
6432            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6433             int n;
6434              
6435             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6436                 n = PieceToNumber(piece - (int)BlackPawn);
6437                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6438                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6439                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6440             } else
6441             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6442                 n = PieceToNumber(piece);
6443                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6444                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6445                 boards[currentMove][n][BOARD_WIDTH-2]++;
6446             }
6447             boards[currentMove][fromY][fromX] = EmptySquare;
6448         }
6449         ClearHighlights();
6450         fromX = fromY = -1;
6451         DrawPosition(TRUE, boards[currentMove]);
6452         return;
6453     }
6454
6455     // off-board moves should not be highlighted
6456     if(x < 0 || x < 0) ClearHighlights();
6457
6458     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6459         SetHighlights(fromX, fromY, toX, toY);
6460         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6461             // [HGM] super: promotion to captured piece selected from holdings
6462             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6463             promotionChoice = TRUE;
6464             // kludge follows to temporarily execute move on display, without promoting yet
6465             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6466             boards[currentMove][toY][toX] = p;
6467             DrawPosition(FALSE, boards[currentMove]);
6468             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6469             boards[currentMove][toY][toX] = q;
6470             DisplayMessage("Click in holdings to choose piece", "");
6471             return;
6472         }
6473         PromotionPopUp();
6474     } else {
6475         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6476         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6477         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6478         fromX = fromY = -1;
6479     }
6480     appData.animate = saveAnimate;
6481     if (appData.animate || appData.animateDragging) {
6482         /* Undo animation damage if needed */
6483         DrawPosition(FALSE, NULL);
6484     }
6485 }
6486
6487 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6488 {   // front-end-free part taken out of PieceMenuPopup
6489     int whichMenu; int xSqr, ySqr;
6490
6491     if(seekGraphUp) { // [HGM] seekgraph
6492         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6493         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6494         return -2;
6495     }
6496
6497     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6498          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6499         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6500         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6501         if(action == Press)   {
6502             originalFlip = flipView;
6503             flipView = !flipView; // temporarily flip board to see game from partners perspective
6504             DrawPosition(TRUE, partnerBoard);
6505             DisplayMessage(partnerStatus, "");
6506             partnerUp = TRUE;
6507         } else if(action == Release) {
6508             flipView = originalFlip;
6509             DrawPosition(TRUE, boards[currentMove]);
6510             partnerUp = FALSE;
6511         }
6512         return -2;
6513     }
6514
6515     xSqr = EventToSquare(x, BOARD_WIDTH);
6516     ySqr = EventToSquare(y, BOARD_HEIGHT);
6517     if (action == Release) UnLoadPV(); // [HGM] pv
6518     if (action != Press) return -2; // return code to be ignored
6519     switch (gameMode) {
6520       case IcsExamining:
6521         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6522       case EditPosition:
6523         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6524         if (xSqr < 0 || ySqr < 0) return -1;\r
6525         whichMenu = 0; // edit-position menu
6526         break;
6527       case IcsObserving:
6528         if(!appData.icsEngineAnalyze) return -1;
6529       case IcsPlayingWhite:
6530       case IcsPlayingBlack:
6531         if(!appData.zippyPlay) goto noZip;
6532       case AnalyzeMode:
6533       case AnalyzeFile:
6534       case MachinePlaysWhite:
6535       case MachinePlaysBlack:
6536       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6537         if (!appData.dropMenu) {
6538           LoadPV(x, y);
6539           return 2; // flag front-end to grab mouse events
6540         }
6541         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6542            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6543       case EditGame:
6544       noZip:
6545         if (xSqr < 0 || ySqr < 0) return -1;
6546         if (!appData.dropMenu || appData.testLegality &&
6547             gameInfo.variant != VariantBughouse &&
6548             gameInfo.variant != VariantCrazyhouse) return -1;
6549         whichMenu = 1; // drop menu
6550         break;
6551       default:
6552         return -1;
6553     }
6554
6555     if (((*fromX = xSqr) < 0) ||
6556         ((*fromY = ySqr) < 0)) {
6557         *fromX = *fromY = -1;
6558         return -1;
6559     }
6560     if (flipView)
6561       *fromX = BOARD_WIDTH - 1 - *fromX;
6562     else
6563       *fromY = BOARD_HEIGHT - 1 - *fromY;
6564
6565     return whichMenu;
6566 }
6567
6568 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6569 {
6570 //    char * hint = lastHint;
6571     FrontEndProgramStats stats;
6572
6573     stats.which = cps == &first ? 0 : 1;
6574     stats.depth = cpstats->depth;
6575     stats.nodes = cpstats->nodes;
6576     stats.score = cpstats->score;
6577     stats.time = cpstats->time;
6578     stats.pv = cpstats->movelist;
6579     stats.hint = lastHint;
6580     stats.an_move_index = 0;
6581     stats.an_move_count = 0;
6582
6583     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6584         stats.hint = cpstats->move_name;
6585         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6586         stats.an_move_count = cpstats->nr_moves;
6587     }
6588
6589     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6590
6591     SetProgramStats( &stats );
6592 }
6593
6594 int
6595 Adjudicate(ChessProgramState *cps)
6596 {       // [HGM] some adjudications useful with buggy engines
6597         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6598         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6599         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6600         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6601         int k, count = 0; static int bare = 1;
6602         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6603         Boolean canAdjudicate = !appData.icsActive;
6604
6605         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6606         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6607             if( appData.testLegality )
6608             {   /* [HGM] Some more adjudications for obstinate engines */
6609                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6610                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6611                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6612                 static int moveCount = 6;
6613                 ChessMove result;
6614                 char *reason = NULL;
6615
6616                 /* Count what is on board. */
6617                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6618                 {   ChessSquare p = boards[forwardMostMove][i][j];
6619                     int m=i;
6620
6621                     switch((int) p)
6622                     {   /* count B,N,R and other of each side */
6623                         case WhiteKing:
6624                         case BlackKing:
6625                              NrK++; break; // [HGM] atomic: count Kings
6626                         case WhiteKnight:
6627                              NrWN++; break;
6628                         case WhiteBishop:
6629                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6630                              bishopsColor |= 1 << ((i^j)&1);
6631                              NrWB++; break;
6632                         case BlackKnight:
6633                              NrBN++; break;
6634                         case BlackBishop:
6635                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6636                              bishopsColor |= 1 << ((i^j)&1);
6637                              NrBB++; break;
6638                         case WhiteRook:
6639                              NrWR++; break;
6640                         case BlackRook:
6641                              NrBR++; break;
6642                         case WhiteQueen:
6643                         case WhiteCannon:
6644                              NrWQ++; break;
6645                         case BlackQueen:
6646                         case BlackCannon:
6647                              NrBQ++; break;
6648                         case EmptySquare: 
6649                              break;
6650                         case BlackPawn:
6651                              m = 7-i;
6652                         case WhitePawn:
6653                              PawnAdvance += m; NrPawns++;
6654                     }
6655                     NrPieces += (p != EmptySquare);
6656                     NrW += ((int)p < (int)BlackPawn);
6657                     if(gameInfo.variant == VariantXiangqi && 
6658                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6659                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6660                         NrW -= ((int)p < (int)BlackPawn);
6661                     }
6662                 }
6663
6664                 /* Some material-based adjudications that have to be made before stalemate test */
6665                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6666                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6667                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6668                      if(canAdjudicate && appData.checkMates) {
6669                          if(engineOpponent)
6670                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6671                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6672                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6673                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6674                          return 1;
6675                      }
6676                 }
6677
6678                 /* Bare King in Shatranj (loses) or Losers (wins) */
6679                 if( NrW == 1 || NrPieces - NrW == 1) {
6680                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6681                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6682                      if(canAdjudicate && appData.checkMates) {
6683                          if(engineOpponent)
6684                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6685                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6686                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6687                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6688                          return 1;
6689                      }
6690                   } else
6691                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6692                   {    /* bare King */
6693                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6694                         if(canAdjudicate && appData.checkMates) {
6695                             /* but only adjudicate if adjudication enabled */
6696                             if(engineOpponent)
6697                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6698                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6699                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6700                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6701                             return 1;
6702                         }
6703                   }
6704                 } else bare = 1;
6705
6706
6707             // don't wait for engine to announce game end if we can judge ourselves
6708             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6709               case MT_CHECK:
6710                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6711                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6712                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6713                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6714                             checkCnt++;
6715                         if(checkCnt >= 2) {
6716                             reason = "Xboard adjudication: 3rd check";
6717                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6718                             break;
6719                         }
6720                     }
6721                 }
6722               case MT_NONE:
6723               default:
6724                 break;
6725               case MT_STALEMATE:
6726               case MT_STAINMATE:
6727                 reason = "Xboard adjudication: Stalemate";
6728                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6729                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6730                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6731                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6732                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6733                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6734                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6735                                                                         EP_CHECKMATE : EP_WINS);
6736                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6737                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6738                 }
6739                 break;
6740               case MT_CHECKMATE:
6741                 reason = "Xboard adjudication: Checkmate";
6742                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6743                 break;
6744             }
6745
6746                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6747                     case EP_STALEMATE:
6748                         result = GameIsDrawn; break;
6749                     case EP_CHECKMATE:
6750                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6751                     case EP_WINS:
6752                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6753                     default:
6754                         result = (ChessMove) 0;
6755                 }
6756                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6757                     if(engineOpponent)
6758                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6759                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6760                     GameEnds( result, reason, GE_XBOARD );
6761                     return 1;
6762                 }
6763
6764                 /* Next absolutely insufficient mating material. */
6765                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6766                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6767                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6768                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3) // [HGM] all Bishops (Ferz!) same color
6769                                   || gameInfo.variant == VariantXiangqi &&
6770                         (NrPieces == 3 && (NrWQ==1 && NrWB==0 && NrBB<2 || NrBQ==1 && NrBB==0 && NrWB<2) ||
6771                          NrPieces == 4 && NrWQ==1 && NrBQ==1 && NrWB==0 && NrBB==0))
6772                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6773
6774                      /* always flag draws, for judging claims */
6775                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6776
6777                      if(canAdjudicate && appData.materialDraws) {
6778                          /* but only adjudicate them if adjudication enabled */
6779                          if(engineOpponent) {
6780                            SendToProgram("force\n", engineOpponent); // suppress reply
6781                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6782                          }
6783                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6784                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6785                          return 1;
6786                      }
6787                 }
6788
6789                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6790                 if(NrPieces == 4 && 
6791                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6792                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6793                    || NrWN==2 || NrBN==2     /* KNNK */
6794                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6795                   ) ) {
6796                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6797                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6798                           if(engineOpponent) {
6799                             SendToProgram("force\n", engineOpponent); // suppress reply
6800                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6801                           }
6802                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6803                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6804                           return 1;
6805                      }
6806                 } else moveCount = 6;
6807             }
6808         }
6809           
6810         if (appData.debugMode) { int i;
6811             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6812                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6813                     appData.drawRepeats);
6814             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6815               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6816             
6817         }
6818
6819         // Repetition draws and 50-move rule can be applied independently of legality testing
6820
6821                 /* Check for rep-draws */
6822                 count = 0;
6823                 for(k = forwardMostMove-2;
6824                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6825                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6826                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6827                     k-=2)
6828                 {   int rights=0;
6829                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6830                         /* compare castling rights */
6831                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6832                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6833                                 rights++; /* King lost rights, while rook still had them */
6834                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6835                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6836                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6837                                    rights++; /* but at least one rook lost them */
6838                         }
6839                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6840                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6841                                 rights++; 
6842                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6843                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6844                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6845                                    rights++;
6846                         }
6847                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6848                             && appData.drawRepeats > 1) {
6849                              /* adjudicate after user-specified nr of repeats */
6850                              int result = GameIsDrawn;
6851                              char *details = "XBoard adjudication: repetition draw";
6852                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6853                                 // [HGM] xiangqi: check for forbidden perpetuals
6854                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6855                                 for(m=forwardMostMove; m>k; m-=2) {
6856                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6857                                         ourPerpetual = 0; // the current mover did not always check
6858                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6859                                         hisPerpetual = 0; // the opponent did not always check
6860                                 }
6861                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6862                                                                         ourPerpetual, hisPerpetual);
6863                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6864                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6865                                     details = "Xboard adjudication: perpetual checking";
6866                                 } else
6867                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6868                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6869                                 } else
6870                                 // Now check for perpetual chases
6871                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6872                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6873                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6874                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6875                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6876                                         details = "Xboard adjudication: perpetual chasing";
6877                                     } else
6878                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6879                                         break; // Abort repetition-checking loop.
6880                                 }
6881                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6882                              }
6883                              if(engineOpponent) {
6884                                SendToProgram("force\n", engineOpponent); // suppress reply
6885                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6886                              }
6887                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6888                              GameEnds( result, details, GE_XBOARD );
6889                              return 1;
6890                         }
6891                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6892                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6893                     }
6894                 }
6895
6896                 /* Now we test for 50-move draws. Determine ply count */
6897                 count = forwardMostMove;
6898                 /* look for last irreversble move */
6899                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6900                     count--;
6901                 /* if we hit starting position, add initial plies */
6902                 if( count == backwardMostMove )
6903                     count -= initialRulePlies;
6904                 count = forwardMostMove - count; 
6905                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6906                         // adjust reversible move counter for checks in Xiangqi
6907                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6908                         if(i < backwardMostMove) i = backwardMostMove;
6909                         while(i <= forwardMostMove) {
6910                                 lastCheck = inCheck; // check evasion does not count
6911                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6912                                 if(inCheck || lastCheck) count--; // check does not count
6913                                 i++;
6914                         }
6915                 }
6916                 if( count >= 100)
6917                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6918                          /* this is used to judge if draw claims are legal */
6919                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6920                          if(engineOpponent) {
6921                            SendToProgram("force\n", engineOpponent); // suppress reply
6922                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6923                          }
6924                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6925                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6926                          return 1;
6927                 }
6928
6929                 /* if draw offer is pending, treat it as a draw claim
6930                  * when draw condition present, to allow engines a way to
6931                  * claim draws before making their move to avoid a race
6932                  * condition occurring after their move
6933                  */
6934                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6935                          char *p = NULL;
6936                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6937                              p = "Draw claim: 50-move rule";
6938                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6939                              p = "Draw claim: 3-fold repetition";
6940                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6941                              p = "Draw claim: insufficient mating material";
6942                          if( p != NULL && canAdjudicate) {
6943                              if(engineOpponent) {
6944                                SendToProgram("force\n", engineOpponent); // suppress reply
6945                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6946                              }
6947                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6948                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6949                              return 1;
6950                          }
6951                 }
6952
6953                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6954                     if(engineOpponent) {
6955                       SendToProgram("force\n", engineOpponent); // suppress reply
6956                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6957                     }
6958                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6959                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6960                     return 1;
6961                 }
6962         return 0;
6963 }
6964
6965 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6966 {   // [HGM] book: this routine intercepts moves to simulate book replies
6967     char *bookHit = NULL;
6968
6969     //first determine if the incoming move brings opponent into his book
6970     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6971         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6972     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6973     if(bookHit != NULL && !cps->bookSuspend) {
6974         // make sure opponent is not going to reply after receiving move to book position
6975         SendToProgram("force\n", cps);
6976         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6977     }
6978     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6979     // now arrange restart after book miss
6980     if(bookHit) {
6981         // after a book hit we never send 'go', and the code after the call to this routine
6982         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6983         char buf[MSG_SIZ];
6984         sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
6985         SendToProgram(buf, cps);
6986         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6987     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6988         SendToProgram("go\n", cps);
6989         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6990     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6991         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6992             SendToProgram("go\n", cps); 
6993         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6994     }
6995     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6996 }
6997
6998 char *savedMessage;
6999 ChessProgramState *savedState;
7000 void DeferredBookMove(void)
7001 {
7002         if(savedState->lastPing != savedState->lastPong)
7003                     ScheduleDelayedEvent(DeferredBookMove, 10);
7004         else
7005         HandleMachineMove(savedMessage, savedState);
7006 }
7007
7008 void
7009 HandleMachineMove(message, cps)
7010      char *message;
7011      ChessProgramState *cps;
7012 {
7013     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7014     char realname[MSG_SIZ];
7015     int fromX, fromY, toX, toY;
7016     ChessMove moveType;
7017     char promoChar;
7018     char *p;
7019     int machineWhite;
7020     char *bookHit;
7021
7022     cps->userError = 0;
7023
7024 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7025     /*
7026      * Kludge to ignore BEL characters
7027      */
7028     while (*message == '\007') message++;
7029
7030     /*
7031      * [HGM] engine debug message: ignore lines starting with '#' character
7032      */
7033     if(cps->debug && *message == '#') return;
7034
7035     /*
7036      * Look for book output
7037      */
7038     if (cps == &first && bookRequested) {
7039         if (message[0] == '\t' || message[0] == ' ') {
7040             /* Part of the book output is here; append it */
7041             strcat(bookOutput, message);
7042             strcat(bookOutput, "  \n");
7043             return;
7044         } else if (bookOutput[0] != NULLCHAR) {
7045             /* All of book output has arrived; display it */
7046             char *p = bookOutput;
7047             while (*p != NULLCHAR) {
7048                 if (*p == '\t') *p = ' ';
7049                 p++;
7050             }
7051             DisplayInformation(bookOutput);
7052             bookRequested = FALSE;
7053             /* Fall through to parse the current output */
7054         }
7055     }
7056
7057     /*
7058      * Look for machine move.
7059      */
7060     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7061         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7062     {
7063         /* This method is only useful on engines that support ping */
7064         if (cps->lastPing != cps->lastPong) {
7065           if (gameMode == BeginningOfGame) {
7066             /* Extra move from before last new; ignore */
7067             if (appData.debugMode) {
7068                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7069             }
7070           } else {
7071             if (appData.debugMode) {
7072                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7073                         cps->which, gameMode);
7074             }
7075
7076             SendToProgram("undo\n", cps);
7077           }
7078           return;
7079         }
7080
7081         switch (gameMode) {
7082           case BeginningOfGame:
7083             /* Extra move from before last reset; ignore */
7084             if (appData.debugMode) {
7085                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7086             }
7087             return;
7088
7089           case EndOfGame:
7090           case IcsIdle:
7091           default:
7092             /* Extra move after we tried to stop.  The mode test is
7093                not a reliable way of detecting this problem, but it's
7094                the best we can do on engines that don't support ping.
7095             */
7096             if (appData.debugMode) {
7097                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7098                         cps->which, gameMode);
7099             }
7100             SendToProgram("undo\n", cps);
7101             return;
7102
7103           case MachinePlaysWhite:
7104           case IcsPlayingWhite:
7105             machineWhite = TRUE;
7106             break;
7107
7108           case MachinePlaysBlack:
7109           case IcsPlayingBlack:
7110             machineWhite = FALSE;
7111             break;
7112
7113           case TwoMachinesPlay:
7114             machineWhite = (cps->twoMachinesColor[0] == 'w');
7115             break;
7116         }
7117         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7118             if (appData.debugMode) {
7119                 fprintf(debugFP,
7120                         "Ignoring move out of turn by %s, gameMode %d"
7121                         ", forwardMost %d\n",
7122                         cps->which, gameMode, forwardMostMove);
7123             }
7124             return;
7125         }
7126
7127     if (appData.debugMode) { int f = forwardMostMove;
7128         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7129                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7130                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7131     }
7132         if(cps->alphaRank) AlphaRank(machineMove, 4);
7133         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7134                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7135             /* Machine move could not be parsed; ignore it. */
7136             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7137                     machineMove, cps->which);
7138             DisplayError(buf1, 0);
7139             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7140                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7141             if (gameMode == TwoMachinesPlay) {
7142               GameEnds(machineWhite ? BlackWins : WhiteWins,
7143                        buf1, GE_XBOARD);
7144             }
7145             return;
7146         }
7147
7148         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7149         /* So we have to redo legality test with true e.p. status here,  */
7150         /* to make sure an illegal e.p. capture does not slip through,   */
7151         /* to cause a forfeit on a justified illegal-move complaint      */
7152         /* of the opponent.                                              */
7153         if( gameMode==TwoMachinesPlay && appData.testLegality
7154             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7155                                                               ) {
7156            ChessMove moveType;
7157            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7158                              fromY, fromX, toY, toX, promoChar);
7159             if (appData.debugMode) {
7160                 int i;
7161                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7162                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7163                 fprintf(debugFP, "castling rights\n");
7164             }
7165             if(moveType == IllegalMove) {
7166                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7167                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7168                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7169                            buf1, GE_XBOARD);
7170                 return;
7171            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7172            /* [HGM] Kludge to handle engines that send FRC-style castling
7173               when they shouldn't (like TSCP-Gothic) */
7174            switch(moveType) {
7175              case WhiteASideCastleFR:
7176              case BlackASideCastleFR:
7177                toX+=2;
7178                currentMoveString[2]++;
7179                break;
7180              case WhiteHSideCastleFR:
7181              case BlackHSideCastleFR:
7182                toX--;
7183                currentMoveString[2]--;
7184                break;
7185              default: ; // nothing to do, but suppresses warning of pedantic compilers
7186            }
7187         }
7188         hintRequested = FALSE;
7189         lastHint[0] = NULLCHAR;
7190         bookRequested = FALSE;
7191         /* Program may be pondering now */
7192         cps->maybeThinking = TRUE;
7193         if (cps->sendTime == 2) cps->sendTime = 1;
7194         if (cps->offeredDraw) cps->offeredDraw--;
7195
7196         /* currentMoveString is set as a side-effect of ParseOneMove */
7197         strcpy(machineMove, currentMoveString);
7198         strcat(machineMove, "\n");
7199         strcpy(moveList[forwardMostMove], machineMove);
7200
7201         /* [AS] Save move info*/
7202         pvInfoList[ forwardMostMove ].score = programStats.score;
7203         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7204         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7205
7206         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7207
7208         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7209         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7210             int count = 0;
7211
7212             while( count < adjudicateLossPlies ) {
7213                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7214
7215                 if( count & 1 ) {
7216                     score = -score; /* Flip score for winning side */
7217                 }
7218
7219                 if( score > adjudicateLossThreshold ) {
7220                     break;
7221                 }
7222
7223                 count++;
7224             }
7225
7226             if( count >= adjudicateLossPlies ) {
7227                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7228
7229                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7230                     "Xboard adjudication", 
7231                     GE_XBOARD );
7232
7233                 return;
7234             }
7235         }
7236
7237         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7238
7239 #if ZIPPY
7240         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7241             first.initDone) {
7242           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7243                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7244                 SendToICS("draw ");
7245                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7246           }
7247           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7248           ics_user_moved = 1;
7249           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7250                 char buf[3*MSG_SIZ];
7251
7252                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7253                         programStats.score / 100.,
7254                         programStats.depth,
7255                         programStats.time / 100.,
7256                         (unsigned int)programStats.nodes,
7257                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7258                         programStats.movelist);
7259                 SendToICS(buf);
7260 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7261           }
7262         }
7263 #endif
7264
7265         /* [AS] Clear stats for next move */
7266         ClearProgramStats();
7267         thinkOutput[0] = NULLCHAR;
7268         hiddenThinkOutputState = 0;
7269
7270         bookHit = NULL;
7271         if (gameMode == TwoMachinesPlay) {
7272             /* [HGM] relaying draw offers moved to after reception of move */
7273             /* and interpreting offer as claim if it brings draw condition */
7274             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7275                 SendToProgram("draw\n", cps->other);
7276             }
7277             if (cps->other->sendTime) {
7278                 SendTimeRemaining(cps->other,
7279                                   cps->other->twoMachinesColor[0] == 'w');
7280             }
7281             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7282             if (firstMove && !bookHit) {
7283                 firstMove = FALSE;
7284                 if (cps->other->useColors) {
7285                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7286                 }
7287                 SendToProgram("go\n", cps->other);
7288             }
7289             cps->other->maybeThinking = TRUE;
7290         }
7291
7292         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7293         
7294         if (!pausing && appData.ringBellAfterMoves) {
7295             RingBell();
7296         }
7297
7298         /* 
7299          * Reenable menu items that were disabled while
7300          * machine was thinking
7301          */
7302         if (gameMode != TwoMachinesPlay)
7303             SetUserThinkingEnables();
7304
7305         // [HGM] book: after book hit opponent has received move and is now in force mode
7306         // force the book reply into it, and then fake that it outputted this move by jumping
7307         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7308         if(bookHit) {
7309                 static char bookMove[MSG_SIZ]; // a bit generous?
7310
7311                 strcpy(bookMove, "move ");
7312                 strcat(bookMove, bookHit);
7313                 message = bookMove;
7314                 cps = cps->other;
7315                 programStats.nodes = programStats.depth = programStats.time = 
7316                 programStats.score = programStats.got_only_move = 0;
7317                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7318
7319                 if(cps->lastPing != cps->lastPong) {
7320                     savedMessage = message; // args for deferred call
7321                     savedState = cps;
7322                     ScheduleDelayedEvent(DeferredBookMove, 10);
7323                     return;
7324                 }
7325                 goto FakeBookMove;
7326         }
7327
7328         return;
7329     }
7330
7331     /* Set special modes for chess engines.  Later something general
7332      *  could be added here; for now there is just one kludge feature,
7333      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7334      *  when "xboard" is given as an interactive command.
7335      */
7336     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7337         cps->useSigint = FALSE;
7338         cps->useSigterm = FALSE;
7339     }
7340     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7341       ParseFeatures(message+8, cps);
7342       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7343     }
7344
7345     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7346      * want this, I was asked to put it in, and obliged.
7347      */
7348     if (!strncmp(message, "setboard ", 9)) {
7349         Board initial_position;
7350
7351         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7352
7353         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7354             DisplayError(_("Bad FEN received from engine"), 0);
7355             return ;
7356         } else {
7357            Reset(TRUE, FALSE);
7358            CopyBoard(boards[0], initial_position);
7359            initialRulePlies = FENrulePlies;
7360            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7361            else gameMode = MachinePlaysBlack;                 
7362            DrawPosition(FALSE, boards[currentMove]);
7363         }
7364         return;
7365     }
7366
7367     /*
7368      * Look for communication commands
7369      */
7370     if (!strncmp(message, "telluser ", 9)) {
7371         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7372         DisplayNote(message + 9);
7373         return;
7374     }
7375     if (!strncmp(message, "tellusererror ", 14)) {
7376         cps->userError = 1;
7377         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7378         DisplayError(message + 14, 0);
7379         return;
7380     }
7381     if (!strncmp(message, "tellopponent ", 13)) {
7382       if (appData.icsActive) {
7383         if (loggedOn) {
7384           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7385           SendToICS(buf1);
7386         }
7387       } else {
7388         DisplayNote(message + 13);
7389       }
7390       return;
7391     }
7392     if (!strncmp(message, "tellothers ", 11)) {
7393       if (appData.icsActive) {
7394         if (loggedOn) {
7395           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7396           SendToICS(buf1);
7397         }
7398       }
7399       return;
7400     }
7401     if (!strncmp(message, "tellall ", 8)) {
7402       if (appData.icsActive) {
7403         if (loggedOn) {
7404           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7405           SendToICS(buf1);
7406         }
7407       } else {
7408         DisplayNote(message + 8);
7409       }
7410       return;
7411     }
7412     if (strncmp(message, "warning", 7) == 0) {
7413         /* Undocumented feature, use tellusererror in new code */
7414         DisplayError(message, 0);
7415         return;
7416     }
7417     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7418         strcpy(realname, cps->tidy);
7419         strcat(realname, " query");
7420         AskQuestion(realname, buf2, buf1, cps->pr);
7421         return;
7422     }
7423     /* Commands from the engine directly to ICS.  We don't allow these to be 
7424      *  sent until we are logged on. Crafty kibitzes have been known to 
7425      *  interfere with the login process.
7426      */
7427     if (loggedOn) {
7428         if (!strncmp(message, "tellics ", 8)) {
7429             SendToICS(message + 8);
7430             SendToICS("\n");
7431             return;
7432         }
7433         if (!strncmp(message, "tellicsnoalias ", 15)) {
7434             SendToICS(ics_prefix);
7435             SendToICS(message + 15);
7436             SendToICS("\n");
7437             return;
7438         }
7439         /* The following are for backward compatibility only */
7440         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7441             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7442             SendToICS(ics_prefix);
7443             SendToICS(message);
7444             SendToICS("\n");
7445             return;
7446         }
7447     }
7448     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7449         return;
7450     }
7451     /*
7452      * If the move is illegal, cancel it and redraw the board.
7453      * Also deal with other error cases.  Matching is rather loose
7454      * here to accommodate engines written before the spec.
7455      */
7456     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7457         strncmp(message, "Error", 5) == 0) {
7458         if (StrStr(message, "name") || 
7459             StrStr(message, "rating") || StrStr(message, "?") ||
7460             StrStr(message, "result") || StrStr(message, "board") ||
7461             StrStr(message, "bk") || StrStr(message, "computer") ||
7462             StrStr(message, "variant") || StrStr(message, "hint") ||
7463             StrStr(message, "random") || StrStr(message, "depth") ||
7464             StrStr(message, "accepted")) {
7465             return;
7466         }
7467         if (StrStr(message, "protover")) {
7468           /* Program is responding to input, so it's apparently done
7469              initializing, and this error message indicates it is
7470              protocol version 1.  So we don't need to wait any longer
7471              for it to initialize and send feature commands. */
7472           FeatureDone(cps, 1);
7473           cps->protocolVersion = 1;
7474           return;
7475         }
7476         cps->maybeThinking = FALSE;
7477
7478         if (StrStr(message, "draw")) {
7479             /* Program doesn't have "draw" command */
7480             cps->sendDrawOffers = 0;
7481             return;
7482         }
7483         if (cps->sendTime != 1 &&
7484             (StrStr(message, "time") || StrStr(message, "otim"))) {
7485           /* Program apparently doesn't have "time" or "otim" command */
7486           cps->sendTime = 0;
7487           return;
7488         }
7489         if (StrStr(message, "analyze")) {
7490             cps->analysisSupport = FALSE;
7491             cps->analyzing = FALSE;
7492             Reset(FALSE, TRUE);
7493             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7494             DisplayError(buf2, 0);
7495             return;
7496         }
7497         if (StrStr(message, "(no matching move)st")) {
7498           /* Special kludge for GNU Chess 4 only */
7499           cps->stKludge = TRUE;
7500           SendTimeControl(cps, movesPerSession, timeControl,
7501                           timeIncrement, appData.searchDepth,
7502                           searchTime);
7503           return;
7504         }
7505         if (StrStr(message, "(no matching move)sd")) {
7506           /* Special kludge for GNU Chess 4 only */
7507           cps->sdKludge = TRUE;
7508           SendTimeControl(cps, movesPerSession, timeControl,
7509                           timeIncrement, appData.searchDepth,
7510                           searchTime);
7511           return;
7512         }
7513         if (!StrStr(message, "llegal")) {
7514             return;
7515         }
7516         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7517             gameMode == IcsIdle) return;
7518         if (forwardMostMove <= backwardMostMove) return;
7519         if (pausing) PauseEvent();
7520       if(appData.forceIllegal) {
7521             // [HGM] illegal: machine refused move; force position after move into it
7522           SendToProgram("force\n", cps);
7523           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7524                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7525                 // when black is to move, while there might be nothing on a2 or black
7526                 // might already have the move. So send the board as if white has the move.
7527                 // But first we must change the stm of the engine, as it refused the last move
7528                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7529                 if(WhiteOnMove(forwardMostMove)) {
7530                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7531                     SendBoard(cps, forwardMostMove); // kludgeless board
7532                 } else {
7533                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7534                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7535                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7536                 }
7537           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7538             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7539                  gameMode == TwoMachinesPlay)
7540               SendToProgram("go\n", cps);
7541             return;
7542       } else
7543         if (gameMode == PlayFromGameFile) {
7544             /* Stop reading this game file */
7545             gameMode = EditGame;
7546             ModeHighlight();
7547         }
7548         currentMove = forwardMostMove-1;
7549         DisplayMove(currentMove-1); /* before DisplayMoveError */
7550         SwitchClocks(forwardMostMove-1); // [HGM] race
7551         DisplayBothClocks();
7552         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7553                 parseList[currentMove], cps->which);
7554         DisplayMoveError(buf1);
7555         DrawPosition(FALSE, boards[currentMove]);
7556
7557         /* [HGM] illegal-move claim should forfeit game when Xboard */
7558         /* only passes fully legal moves                            */
7559         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7560             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7561                                 "False illegal-move claim", GE_XBOARD );
7562         }
7563         return;
7564     }
7565     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7566         /* Program has a broken "time" command that
7567            outputs a string not ending in newline.
7568            Don't use it. */
7569         cps->sendTime = 0;
7570     }
7571     
7572     /*
7573      * If chess program startup fails, exit with an error message.
7574      * Attempts to recover here are futile.
7575      */
7576     if ((StrStr(message, "unknown host") != NULL)
7577         || (StrStr(message, "No remote directory") != NULL)
7578         || (StrStr(message, "not found") != NULL)
7579         || (StrStr(message, "No such file") != NULL)
7580         || (StrStr(message, "can't alloc") != NULL)
7581         || (StrStr(message, "Permission denied") != NULL)) {
7582
7583         cps->maybeThinking = FALSE;
7584         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7585                 cps->which, cps->program, cps->host, message);
7586         RemoveInputSource(cps->isr);
7587         DisplayFatalError(buf1, 0, 1);
7588         return;
7589     }
7590     
7591     /* 
7592      * Look for hint output
7593      */
7594     if (sscanf(message, "Hint: %s", buf1) == 1) {
7595         if (cps == &first && hintRequested) {
7596             hintRequested = FALSE;
7597             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7598                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7599                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7600                                     PosFlags(forwardMostMove),
7601                                     fromY, fromX, toY, toX, promoChar, buf1);
7602                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7603                 DisplayInformation(buf2);
7604             } else {
7605                 /* Hint move could not be parsed!? */
7606               snprintf(buf2, sizeof(buf2),
7607                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7608                         buf1, cps->which);
7609                 DisplayError(buf2, 0);
7610             }
7611         } else {
7612             strcpy(lastHint, buf1);
7613         }
7614         return;
7615     }
7616
7617     /*
7618      * Ignore other messages if game is not in progress
7619      */
7620     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7621         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7622
7623     /*
7624      * look for win, lose, draw, or draw offer
7625      */
7626     if (strncmp(message, "1-0", 3) == 0) {
7627         char *p, *q, *r = "";
7628         p = strchr(message, '{');
7629         if (p) {
7630             q = strchr(p, '}');
7631             if (q) {
7632                 *q = NULLCHAR;
7633                 r = p + 1;
7634             }
7635         }
7636         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7637         return;
7638     } else if (strncmp(message, "0-1", 3) == 0) {
7639         char *p, *q, *r = "";
7640         p = strchr(message, '{');
7641         if (p) {
7642             q = strchr(p, '}');
7643             if (q) {
7644                 *q = NULLCHAR;
7645                 r = p + 1;
7646             }
7647         }
7648         /* Kludge for Arasan 4.1 bug */
7649         if (strcmp(r, "Black resigns") == 0) {
7650             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7651             return;
7652         }
7653         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7654         return;
7655     } else if (strncmp(message, "1/2", 3) == 0) {
7656         char *p, *q, *r = "";
7657         p = strchr(message, '{');
7658         if (p) {
7659             q = strchr(p, '}');
7660             if (q) {
7661                 *q = NULLCHAR;
7662                 r = p + 1;
7663             }
7664         }
7665             
7666         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7667         return;
7668
7669     } else if (strncmp(message, "White resign", 12) == 0) {
7670         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7671         return;
7672     } else if (strncmp(message, "Black resign", 12) == 0) {
7673         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7674         return;
7675     } else if (strncmp(message, "White matches", 13) == 0 ||
7676                strncmp(message, "Black matches", 13) == 0   ) {
7677         /* [HGM] ignore GNUShogi noises */
7678         return;
7679     } else if (strncmp(message, "White", 5) == 0 &&
7680                message[5] != '(' &&
7681                StrStr(message, "Black") == NULL) {
7682         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7683         return;
7684     } else if (strncmp(message, "Black", 5) == 0 &&
7685                message[5] != '(') {
7686         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7687         return;
7688     } else if (strcmp(message, "resign") == 0 ||
7689                strcmp(message, "computer resigns") == 0) {
7690         switch (gameMode) {
7691           case MachinePlaysBlack:
7692           case IcsPlayingBlack:
7693             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7694             break;
7695           case MachinePlaysWhite:
7696           case IcsPlayingWhite:
7697             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7698             break;
7699           case TwoMachinesPlay:
7700             if (cps->twoMachinesColor[0] == 'w')
7701               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7702             else
7703               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7704             break;
7705           default:
7706             /* can't happen */
7707             break;
7708         }
7709         return;
7710     } else if (strncmp(message, "opponent mates", 14) == 0) {
7711         switch (gameMode) {
7712           case MachinePlaysBlack:
7713           case IcsPlayingBlack:
7714             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7715             break;
7716           case MachinePlaysWhite:
7717           case IcsPlayingWhite:
7718             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7719             break;
7720           case TwoMachinesPlay:
7721             if (cps->twoMachinesColor[0] == 'w')
7722               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7723             else
7724               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7725             break;
7726           default:
7727             /* can't happen */
7728             break;
7729         }
7730         return;
7731     } else if (strncmp(message, "computer mates", 14) == 0) {
7732         switch (gameMode) {
7733           case MachinePlaysBlack:
7734           case IcsPlayingBlack:
7735             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7736             break;
7737           case MachinePlaysWhite:
7738           case IcsPlayingWhite:
7739             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7740             break;
7741           case TwoMachinesPlay:
7742             if (cps->twoMachinesColor[0] == 'w')
7743               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7744             else
7745               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7746             break;
7747           default:
7748             /* can't happen */
7749             break;
7750         }
7751         return;
7752     } else if (strncmp(message, "checkmate", 9) == 0) {
7753         if (WhiteOnMove(forwardMostMove)) {
7754             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7755         } else {
7756             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7757         }
7758         return;
7759     } else if (strstr(message, "Draw") != NULL ||
7760                strstr(message, "game is a draw") != NULL) {
7761         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7762         return;
7763     } else if (strstr(message, "offer") != NULL &&
7764                strstr(message, "draw") != NULL) {
7765 #if ZIPPY
7766         if (appData.zippyPlay && first.initDone) {
7767             /* Relay offer to ICS */
7768             SendToICS(ics_prefix);
7769             SendToICS("draw\n");
7770         }
7771 #endif
7772         cps->offeredDraw = 2; /* valid until this engine moves twice */
7773         if (gameMode == TwoMachinesPlay) {
7774             if (cps->other->offeredDraw) {
7775                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7776             /* [HGM] in two-machine mode we delay relaying draw offer      */
7777             /* until after we also have move, to see if it is really claim */
7778             }
7779         } else if (gameMode == MachinePlaysWhite ||
7780                    gameMode == MachinePlaysBlack) {
7781           if (userOfferedDraw) {
7782             DisplayInformation(_("Machine accepts your draw offer"));
7783             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7784           } else {
7785             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7786           }
7787         }
7788     }
7789
7790     
7791     /*
7792      * Look for thinking output
7793      */
7794     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7795           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7796                                 ) {
7797         int plylev, mvleft, mvtot, curscore, time;
7798         char mvname[MOVE_LEN];
7799         u64 nodes; // [DM]
7800         char plyext;
7801         int ignore = FALSE;
7802         int prefixHint = FALSE;
7803         mvname[0] = NULLCHAR;
7804
7805         switch (gameMode) {
7806           case MachinePlaysBlack:
7807           case IcsPlayingBlack:
7808             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7809             break;
7810           case MachinePlaysWhite:
7811           case IcsPlayingWhite:
7812             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7813             break;
7814           case AnalyzeMode:
7815           case AnalyzeFile:
7816             break;
7817           case IcsObserving: /* [DM] icsEngineAnalyze */
7818             if (!appData.icsEngineAnalyze) ignore = TRUE;
7819             break;
7820           case TwoMachinesPlay:
7821             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7822                 ignore = TRUE;
7823             }
7824             break;
7825           default:
7826             ignore = TRUE;
7827             break;
7828         }
7829
7830         if (!ignore) {
7831             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7832             buf1[0] = NULLCHAR;
7833             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7834                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7835
7836                 if (plyext != ' ' && plyext != '\t') {
7837                     time *= 100;
7838                 }
7839
7840                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7841                 if( cps->scoreIsAbsolute && 
7842                     ( gameMode == MachinePlaysBlack ||
7843                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7844                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7845                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7846                      !WhiteOnMove(currentMove)
7847                     ) )
7848                 {
7849                     curscore = -curscore;
7850                 }
7851
7852
7853                 tempStats.depth = plylev;
7854                 tempStats.nodes = nodes;
7855                 tempStats.time = time;
7856                 tempStats.score = curscore;
7857                 tempStats.got_only_move = 0;
7858
7859                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7860                         int ticklen;
7861
7862                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7863                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7864                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7865                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7866                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7867                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7868                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7869                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7870                 }
7871
7872                 /* Buffer overflow protection */
7873                 if (buf1[0] != NULLCHAR) {
7874                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7875                         && appData.debugMode) {
7876                         fprintf(debugFP,
7877                                 "PV is too long; using the first %u bytes.\n",
7878                                 (unsigned) sizeof(tempStats.movelist) - 1);
7879                     }
7880
7881                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7882                 } else {
7883                     sprintf(tempStats.movelist, " no PV\n");
7884                 }
7885
7886                 if (tempStats.seen_stat) {
7887                     tempStats.ok_to_send = 1;
7888                 }
7889
7890                 if (strchr(tempStats.movelist, '(') != NULL) {
7891                     tempStats.line_is_book = 1;
7892                     tempStats.nr_moves = 0;
7893                     tempStats.moves_left = 0;
7894                 } else {
7895                     tempStats.line_is_book = 0;
7896                 }
7897
7898                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7899                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7900
7901                 SendProgramStatsToFrontend( cps, &tempStats );
7902
7903                 /* 
7904                     [AS] Protect the thinkOutput buffer from overflow... this
7905                     is only useful if buf1 hasn't overflowed first!
7906                 */
7907                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7908                         plylev, 
7909                         (gameMode == TwoMachinesPlay ?
7910                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7911                         ((double) curscore) / 100.0,
7912                         prefixHint ? lastHint : "",
7913                         prefixHint ? " " : "" );
7914
7915                 if( buf1[0] != NULLCHAR ) {
7916                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7917
7918                     if( strlen(buf1) > max_len ) {
7919                         if( appData.debugMode) {
7920                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7921                         }
7922                         buf1[max_len+1] = '\0';
7923                     }
7924
7925                     strcat( thinkOutput, buf1 );
7926                 }
7927
7928                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7929                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7930                     DisplayMove(currentMove - 1);
7931                 }
7932                 return;
7933
7934             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7935                 /* crafty (9.25+) says "(only move) <move>"
7936                  * if there is only 1 legal move
7937                  */
7938                 sscanf(p, "(only move) %s", buf1);
7939                 sprintf(thinkOutput, "%s (only move)", buf1);
7940                 sprintf(programStats.movelist, "%s (only move)", buf1);
7941                 programStats.depth = 1;
7942                 programStats.nr_moves = 1;
7943                 programStats.moves_left = 1;
7944                 programStats.nodes = 1;
7945                 programStats.time = 1;
7946                 programStats.got_only_move = 1;
7947
7948                 /* Not really, but we also use this member to
7949                    mean "line isn't going to change" (Crafty
7950                    isn't searching, so stats won't change) */
7951                 programStats.line_is_book = 1;
7952
7953                 SendProgramStatsToFrontend( cps, &programStats );
7954                 
7955                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7956                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7957                     DisplayMove(currentMove - 1);
7958                 }
7959                 return;
7960             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7961                               &time, &nodes, &plylev, &mvleft,
7962                               &mvtot, mvname) >= 5) {
7963                 /* The stat01: line is from Crafty (9.29+) in response
7964                    to the "." command */
7965                 programStats.seen_stat = 1;
7966                 cps->maybeThinking = TRUE;
7967
7968                 if (programStats.got_only_move || !appData.periodicUpdates)
7969                   return;
7970
7971                 programStats.depth = plylev;
7972                 programStats.time = time;
7973                 programStats.nodes = nodes;
7974                 programStats.moves_left = mvleft;
7975                 programStats.nr_moves = mvtot;
7976                 strcpy(programStats.move_name, mvname);
7977                 programStats.ok_to_send = 1;
7978                 programStats.movelist[0] = '\0';
7979
7980                 SendProgramStatsToFrontend( cps, &programStats );
7981
7982                 return;
7983
7984             } else if (strncmp(message,"++",2) == 0) {
7985                 /* Crafty 9.29+ outputs this */
7986                 programStats.got_fail = 2;
7987                 return;
7988
7989             } else if (strncmp(message,"--",2) == 0) {
7990                 /* Crafty 9.29+ outputs this */
7991                 programStats.got_fail = 1;
7992                 return;
7993
7994             } else if (thinkOutput[0] != NULLCHAR &&
7995                        strncmp(message, "    ", 4) == 0) {
7996                 unsigned message_len;
7997
7998                 p = message;
7999                 while (*p && *p == ' ') p++;
8000
8001                 message_len = strlen( p );
8002
8003                 /* [AS] Avoid buffer overflow */
8004                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8005                     strcat(thinkOutput, " ");
8006                     strcat(thinkOutput, p);
8007                 }
8008
8009                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8010                     strcat(programStats.movelist, " ");
8011                     strcat(programStats.movelist, p);
8012                 }
8013
8014                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8015                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8016                     DisplayMove(currentMove - 1);
8017                 }
8018                 return;
8019             }
8020         }
8021         else {
8022             buf1[0] = NULLCHAR;
8023
8024             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8025                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8026             {
8027                 ChessProgramStats cpstats;
8028
8029                 if (plyext != ' ' && plyext != '\t') {
8030                     time *= 100;
8031                 }
8032
8033                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8034                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8035                     curscore = -curscore;
8036                 }
8037
8038                 cpstats.depth = plylev;
8039                 cpstats.nodes = nodes;
8040                 cpstats.time = time;
8041                 cpstats.score = curscore;
8042                 cpstats.got_only_move = 0;
8043                 cpstats.movelist[0] = '\0';
8044
8045                 if (buf1[0] != NULLCHAR) {
8046                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8047                 }
8048
8049                 cpstats.ok_to_send = 0;
8050                 cpstats.line_is_book = 0;
8051                 cpstats.nr_moves = 0;
8052                 cpstats.moves_left = 0;
8053
8054                 SendProgramStatsToFrontend( cps, &cpstats );
8055             }
8056         }
8057     }
8058 }
8059
8060
8061 /* Parse a game score from the character string "game", and
8062    record it as the history of the current game.  The game
8063    score is NOT assumed to start from the standard position. 
8064    The display is not updated in any way.
8065    */
8066 void
8067 ParseGameHistory(game)
8068      char *game;
8069 {
8070     ChessMove moveType;
8071     int fromX, fromY, toX, toY, boardIndex;
8072     char promoChar;
8073     char *p, *q;
8074     char buf[MSG_SIZ];
8075
8076     if (appData.debugMode)
8077       fprintf(debugFP, "Parsing game history: %s\n", game);
8078
8079     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8080     gameInfo.site = StrSave(appData.icsHost);
8081     gameInfo.date = PGNDate();
8082     gameInfo.round = StrSave("-");
8083
8084     /* Parse out names of players */
8085     while (*game == ' ') game++;
8086     p = buf;
8087     while (*game != ' ') *p++ = *game++;
8088     *p = NULLCHAR;
8089     gameInfo.white = StrSave(buf);
8090     while (*game == ' ') game++;
8091     p = buf;
8092     while (*game != ' ' && *game != '\n') *p++ = *game++;
8093     *p = NULLCHAR;
8094     gameInfo.black = StrSave(buf);
8095
8096     /* Parse moves */
8097     boardIndex = blackPlaysFirst ? 1 : 0;
8098     yynewstr(game);
8099     for (;;) {
8100         yyboardindex = boardIndex;
8101         moveType = (ChessMove) yylex();
8102         switch (moveType) {
8103           case IllegalMove:             /* maybe suicide chess, etc. */
8104   if (appData.debugMode) {
8105     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8106     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8107     setbuf(debugFP, NULL);
8108   }
8109           case WhitePromotionChancellor:
8110           case BlackPromotionChancellor:
8111           case WhitePromotionArchbishop:
8112           case BlackPromotionArchbishop:
8113           case WhitePromotionQueen:
8114           case BlackPromotionQueen:
8115           case WhitePromotionRook:
8116           case BlackPromotionRook:
8117           case WhitePromotionBishop:
8118           case BlackPromotionBishop:
8119           case WhitePromotionKnight:
8120           case BlackPromotionKnight:
8121           case WhitePromotionKing:
8122           case BlackPromotionKing:
8123           case NormalMove:
8124           case WhiteCapturesEnPassant:
8125           case BlackCapturesEnPassant:
8126           case WhiteKingSideCastle:
8127           case WhiteQueenSideCastle:
8128           case BlackKingSideCastle:
8129           case BlackQueenSideCastle:
8130           case WhiteKingSideCastleWild:
8131           case WhiteQueenSideCastleWild:
8132           case BlackKingSideCastleWild:
8133           case BlackQueenSideCastleWild:
8134           /* PUSH Fabien */
8135           case WhiteHSideCastleFR:
8136           case WhiteASideCastleFR:
8137           case BlackHSideCastleFR:
8138           case BlackASideCastleFR:
8139           /* POP Fabien */
8140             fromX = currentMoveString[0] - AAA;
8141             fromY = currentMoveString[1] - ONE;
8142             toX = currentMoveString[2] - AAA;
8143             toY = currentMoveString[3] - ONE;
8144             promoChar = currentMoveString[4];
8145             break;
8146           case WhiteDrop:
8147           case BlackDrop:
8148             fromX = moveType == WhiteDrop ?
8149               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8150             (int) CharToPiece(ToLower(currentMoveString[0]));
8151             fromY = DROP_RANK;
8152             toX = currentMoveString[2] - AAA;
8153             toY = currentMoveString[3] - ONE;
8154             promoChar = NULLCHAR;
8155             break;
8156           case AmbiguousMove:
8157             /* bug? */
8158             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8159   if (appData.debugMode) {
8160     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8161     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8162     setbuf(debugFP, NULL);
8163   }
8164             DisplayError(buf, 0);
8165             return;
8166           case ImpossibleMove:
8167             /* bug? */
8168             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8169   if (appData.debugMode) {
8170     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8171     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8172     setbuf(debugFP, NULL);
8173   }
8174             DisplayError(buf, 0);
8175             return;
8176           case (ChessMove) 0:   /* end of file */
8177             if (boardIndex < backwardMostMove) {
8178                 /* Oops, gap.  How did that happen? */
8179                 DisplayError(_("Gap in move list"), 0);
8180                 return;
8181             }
8182             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8183             if (boardIndex > forwardMostMove) {
8184                 forwardMostMove = boardIndex;
8185             }
8186             return;
8187           case ElapsedTime:
8188             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8189                 strcat(parseList[boardIndex-1], " ");
8190                 strcat(parseList[boardIndex-1], yy_text);
8191             }
8192             continue;
8193           case Comment:
8194           case PGNTag:
8195           case NAG:
8196           default:
8197             /* ignore */
8198             continue;
8199           case WhiteWins:
8200           case BlackWins:
8201           case GameIsDrawn:
8202           case GameUnfinished:
8203             if (gameMode == IcsExamining) {
8204                 if (boardIndex < backwardMostMove) {
8205                     /* Oops, gap.  How did that happen? */
8206                     return;
8207                 }
8208                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8209                 return;
8210             }
8211             gameInfo.result = moveType;
8212             p = strchr(yy_text, '{');
8213             if (p == NULL) p = strchr(yy_text, '(');
8214             if (p == NULL) {
8215                 p = yy_text;
8216                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8217             } else {
8218                 q = strchr(p, *p == '{' ? '}' : ')');
8219                 if (q != NULL) *q = NULLCHAR;
8220                 p++;
8221             }
8222             gameInfo.resultDetails = StrSave(p);
8223             continue;
8224         }
8225         if (boardIndex >= forwardMostMove &&
8226             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8227             backwardMostMove = blackPlaysFirst ? 1 : 0;
8228             return;
8229         }
8230         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8231                                  fromY, fromX, toY, toX, promoChar,
8232                                  parseList[boardIndex]);
8233         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8234         /* currentMoveString is set as a side-effect of yylex */
8235         strcpy(moveList[boardIndex], currentMoveString);
8236         strcat(moveList[boardIndex], "\n");
8237         boardIndex++;
8238         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8239         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8240           case MT_NONE:
8241           case MT_STALEMATE:
8242           default:
8243             break;
8244           case MT_CHECK:
8245             if(gameInfo.variant != VariantShogi)
8246                 strcat(parseList[boardIndex - 1], "+");
8247             break;
8248           case MT_CHECKMATE:
8249           case MT_STAINMATE:
8250             strcat(parseList[boardIndex - 1], "#");
8251             break;
8252         }
8253     }
8254 }
8255
8256
8257 /* Apply a move to the given board  */
8258 void
8259 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8260      int fromX, fromY, toX, toY;
8261      int promoChar;
8262      Board board;
8263 {
8264   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8265   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8266
8267     /* [HGM] compute & store e.p. status and castling rights for new position */
8268     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8269     { int i;
8270
8271       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8272       oldEP = (signed char)board[EP_STATUS];
8273       board[EP_STATUS] = EP_NONE;
8274
8275       if( board[toY][toX] != EmptySquare ) 
8276            board[EP_STATUS] = EP_CAPTURE;  
8277
8278       if( board[fromY][fromX] == WhitePawn ) {
8279            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8280                board[EP_STATUS] = EP_PAWN_MOVE;
8281            if( toY-fromY==2) {
8282                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8283                         gameInfo.variant != VariantBerolina || toX < fromX)
8284                       board[EP_STATUS] = toX | berolina;
8285                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8286                         gameInfo.variant != VariantBerolina || toX > fromX) 
8287                       board[EP_STATUS] = toX;
8288            }
8289       } else 
8290       if( board[fromY][fromX] == BlackPawn ) {
8291            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8292                board[EP_STATUS] = EP_PAWN_MOVE; 
8293            if( toY-fromY== -2) {
8294                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8295                         gameInfo.variant != VariantBerolina || toX < fromX)
8296                       board[EP_STATUS] = toX | berolina;
8297                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8298                         gameInfo.variant != VariantBerolina || toX > fromX) 
8299                       board[EP_STATUS] = toX;
8300            }
8301        }
8302
8303        for(i=0; i<nrCastlingRights; i++) {
8304            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8305               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8306              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8307        }
8308
8309     }
8310
8311   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8312   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8313        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8314          
8315   if (fromX == toX && fromY == toY) return;
8316
8317   if (fromY == DROP_RANK) {
8318         /* must be first */
8319         piece = board[toY][toX] = (ChessSquare) fromX;
8320   } else {
8321      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8322      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8323      if(gameInfo.variant == VariantKnightmate)
8324          king += (int) WhiteUnicorn - (int) WhiteKing;
8325
8326     /* Code added by Tord: */
8327     /* FRC castling assumed when king captures friendly rook. */
8328     if (board[fromY][fromX] == WhiteKing &&
8329              board[toY][toX] == WhiteRook) {
8330       board[fromY][fromX] = EmptySquare;
8331       board[toY][toX] = EmptySquare;
8332       if(toX > fromX) {
8333         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8334       } else {
8335         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8336       }
8337     } else if (board[fromY][fromX] == BlackKing &&
8338                board[toY][toX] == BlackRook) {
8339       board[fromY][fromX] = EmptySquare;
8340       board[toY][toX] = EmptySquare;
8341       if(toX > fromX) {
8342         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8343       } else {
8344         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8345       }
8346     /* End of code added by Tord */
8347
8348     } else if (board[fromY][fromX] == king
8349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8350         && toY == fromY && toX > fromX+1) {
8351         board[fromY][fromX] = EmptySquare;
8352         board[toY][toX] = king;
8353         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8354         board[fromY][BOARD_RGHT-1] = EmptySquare;
8355     } else if (board[fromY][fromX] == king
8356         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8357                && toY == fromY && toX < fromX-1) {
8358         board[fromY][fromX] = EmptySquare;
8359         board[toY][toX] = king;
8360         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8361         board[fromY][BOARD_LEFT] = EmptySquare;
8362     } else if (board[fromY][fromX] == WhitePawn
8363                && toY >= BOARD_HEIGHT-promoRank
8364                && gameInfo.variant != VariantXiangqi
8365                ) {
8366         /* white pawn promotion */
8367         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8368         if (board[toY][toX] == EmptySquare) {
8369             board[toY][toX] = WhiteQueen;
8370         }
8371         if(gameInfo.variant==VariantBughouse ||
8372            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8373             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8374         board[fromY][fromX] = EmptySquare;
8375     } else if ((fromY == BOARD_HEIGHT-4)
8376                && (toX != fromX)
8377                && gameInfo.variant != VariantXiangqi
8378                && gameInfo.variant != VariantBerolina
8379                && (board[fromY][fromX] == WhitePawn)
8380                && (board[toY][toX] == EmptySquare)) {
8381         board[fromY][fromX] = EmptySquare;
8382         board[toY][toX] = WhitePawn;
8383         captured = board[toY - 1][toX];
8384         board[toY - 1][toX] = EmptySquare;
8385     } else if ((fromY == BOARD_HEIGHT-4)
8386                && (toX == fromX)
8387                && gameInfo.variant == VariantBerolina
8388                && (board[fromY][fromX] == WhitePawn)
8389                && (board[toY][toX] == EmptySquare)) {
8390         board[fromY][fromX] = EmptySquare;
8391         board[toY][toX] = WhitePawn;
8392         if(oldEP & EP_BEROLIN_A) {
8393                 captured = board[fromY][fromX-1];
8394                 board[fromY][fromX-1] = EmptySquare;
8395         }else{  captured = board[fromY][fromX+1];
8396                 board[fromY][fromX+1] = EmptySquare;
8397         }
8398     } else if (board[fromY][fromX] == king
8399         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8400                && toY == fromY && toX > fromX+1) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = king;
8403         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8404         board[fromY][BOARD_RGHT-1] = EmptySquare;
8405     } else if (board[fromY][fromX] == king
8406         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8407                && toY == fromY && toX < fromX-1) {
8408         board[fromY][fromX] = EmptySquare;
8409         board[toY][toX] = king;
8410         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8411         board[fromY][BOARD_LEFT] = EmptySquare;
8412     } else if (fromY == 7 && fromX == 3
8413                && board[fromY][fromX] == BlackKing
8414                && toY == 7 && toX == 5) {
8415         board[fromY][fromX] = EmptySquare;
8416         board[toY][toX] = BlackKing;
8417         board[fromY][7] = EmptySquare;
8418         board[toY][4] = BlackRook;
8419     } else if (fromY == 7 && fromX == 3
8420                && board[fromY][fromX] == BlackKing
8421                && toY == 7 && toX == 1) {
8422         board[fromY][fromX] = EmptySquare;
8423         board[toY][toX] = BlackKing;
8424         board[fromY][0] = EmptySquare;
8425         board[toY][2] = BlackRook;
8426     } else if (board[fromY][fromX] == BlackPawn
8427                && toY < promoRank
8428                && gameInfo.variant != VariantXiangqi
8429                ) {
8430         /* black pawn promotion */
8431         board[toY][toX] = CharToPiece(ToLower(promoChar));
8432         if (board[toY][toX] == EmptySquare) {
8433             board[toY][toX] = BlackQueen;
8434         }
8435         if(gameInfo.variant==VariantBughouse ||
8436            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8437             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8438         board[fromY][fromX] = EmptySquare;
8439     } else if ((fromY == 3)
8440                && (toX != fromX)
8441                && gameInfo.variant != VariantXiangqi
8442                && gameInfo.variant != VariantBerolina
8443                && (board[fromY][fromX] == BlackPawn)
8444                && (board[toY][toX] == EmptySquare)) {
8445         board[fromY][fromX] = EmptySquare;
8446         board[toY][toX] = BlackPawn;
8447         captured = board[toY + 1][toX];
8448         board[toY + 1][toX] = EmptySquare;
8449     } else if ((fromY == 3)
8450                && (toX == fromX)
8451                && gameInfo.variant == VariantBerolina
8452                && (board[fromY][fromX] == BlackPawn)
8453                && (board[toY][toX] == EmptySquare)) {
8454         board[fromY][fromX] = EmptySquare;
8455         board[toY][toX] = BlackPawn;
8456         if(oldEP & EP_BEROLIN_A) {
8457                 captured = board[fromY][fromX-1];
8458                 board[fromY][fromX-1] = EmptySquare;
8459         }else{  captured = board[fromY][fromX+1];
8460                 board[fromY][fromX+1] = EmptySquare;
8461         }
8462     } else {
8463         board[toY][toX] = board[fromY][fromX];
8464         board[fromY][fromX] = EmptySquare;
8465     }
8466
8467     /* [HGM] now we promote for Shogi, if needed */
8468     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8469         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8470   }
8471
8472     if (gameInfo.holdingsWidth != 0) {
8473
8474       /* !!A lot more code needs to be written to support holdings  */
8475       /* [HGM] OK, so I have written it. Holdings are stored in the */
8476       /* penultimate board files, so they are automaticlly stored   */
8477       /* in the game history.                                       */
8478       if (fromY == DROP_RANK) {
8479         /* Delete from holdings, by decreasing count */
8480         /* and erasing image if necessary            */
8481         p = (int) fromX;
8482         if(p < (int) BlackPawn) { /* white drop */
8483              p -= (int)WhitePawn;
8484                  p = PieceToNumber((ChessSquare)p);
8485              if(p >= gameInfo.holdingsSize) p = 0;
8486              if(--board[p][BOARD_WIDTH-2] <= 0)
8487                   board[p][BOARD_WIDTH-1] = EmptySquare;
8488              if((int)board[p][BOARD_WIDTH-2] < 0)
8489                         board[p][BOARD_WIDTH-2] = 0;
8490         } else {                  /* black drop */
8491              p -= (int)BlackPawn;
8492                  p = PieceToNumber((ChessSquare)p);
8493              if(p >= gameInfo.holdingsSize) p = 0;
8494              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8495                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8496              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8497                         board[BOARD_HEIGHT-1-p][1] = 0;
8498         }
8499       }
8500       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8501           && gameInfo.variant != VariantBughouse        ) {
8502         /* [HGM] holdings: Add to holdings, if holdings exist */
8503         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8504                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8505                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8506         }
8507         p = (int) captured;
8508         if (p >= (int) BlackPawn) {
8509           p -= (int)BlackPawn;
8510           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8511                   /* in Shogi restore piece to its original  first */
8512                   captured = (ChessSquare) (DEMOTED captured);
8513                   p = DEMOTED p;
8514           }
8515           p = PieceToNumber((ChessSquare)p);
8516           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8517           board[p][BOARD_WIDTH-2]++;
8518           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8519         } else {
8520           p -= (int)WhitePawn;
8521           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8522                   captured = (ChessSquare) (DEMOTED captured);
8523                   p = DEMOTED p;
8524           }
8525           p = PieceToNumber((ChessSquare)p);
8526           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8527           board[BOARD_HEIGHT-1-p][1]++;
8528           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8529         }
8530       }
8531     } else if (gameInfo.variant == VariantAtomic) {
8532       if (captured != EmptySquare) {
8533         int y, x;
8534         for (y = toY-1; y <= toY+1; y++) {
8535           for (x = toX-1; x <= toX+1; x++) {
8536             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8537                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8538               board[y][x] = EmptySquare;
8539             }
8540           }
8541         }
8542         board[toY][toX] = EmptySquare;
8543       }
8544     }
8545     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8546         /* [HGM] Shogi promotions */
8547         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8548     }
8549
8550     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8551                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8552         // [HGM] superchess: take promotion piece out of holdings
8553         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8554         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8555             if(!--board[k][BOARD_WIDTH-2])
8556                 board[k][BOARD_WIDTH-1] = EmptySquare;
8557         } else {
8558             if(!--board[BOARD_HEIGHT-1-k][1])
8559                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8560         }
8561     }
8562
8563 }
8564
8565 /* Updates forwardMostMove */
8566 void
8567 MakeMove(fromX, fromY, toX, toY, promoChar)
8568      int fromX, fromY, toX, toY;
8569      int promoChar;
8570 {
8571 //    forwardMostMove++; // [HGM] bare: moved downstream
8572
8573     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8574         int timeLeft; static int lastLoadFlag=0; int king, piece;
8575         piece = boards[forwardMostMove][fromY][fromX];
8576         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8577         if(gameInfo.variant == VariantKnightmate)
8578             king += (int) WhiteUnicorn - (int) WhiteKing;
8579         if(forwardMostMove == 0) {
8580             if(blackPlaysFirst) 
8581                 fprintf(serverMoves, "%s;", second.tidy);
8582             fprintf(serverMoves, "%s;", first.tidy);
8583             if(!blackPlaysFirst) 
8584                 fprintf(serverMoves, "%s;", second.tidy);
8585         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8586         lastLoadFlag = loadFlag;
8587         // print base move
8588         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8589         // print castling suffix
8590         if( toY == fromY && piece == king ) {
8591             if(toX-fromX > 1)
8592                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8593             if(fromX-toX >1)
8594                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8595         }
8596         // e.p. suffix
8597         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8598              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8599              boards[forwardMostMove][toY][toX] == EmptySquare
8600              && fromX != toX && fromY != toY)
8601                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8602         // promotion suffix
8603         if(promoChar != NULLCHAR)
8604                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8605         if(!loadFlag) {
8606             fprintf(serverMoves, "/%d/%d",
8607                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8608             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8609             else                      timeLeft = blackTimeRemaining/1000;
8610             fprintf(serverMoves, "/%d", timeLeft);
8611         }
8612         fflush(serverMoves);
8613     }
8614
8615     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8616       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8617                         0, 1);
8618       return;
8619     }
8620     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8621     if (commentList[forwardMostMove+1] != NULL) {
8622         free(commentList[forwardMostMove+1]);
8623         commentList[forwardMostMove+1] = NULL;
8624     }
8625     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8626     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8627     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8628     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8629     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8630     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8631     gameInfo.result = GameUnfinished;
8632     if (gameInfo.resultDetails != NULL) {
8633         free(gameInfo.resultDetails);
8634         gameInfo.resultDetails = NULL;
8635     }
8636     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8637                               moveList[forwardMostMove - 1]);
8638     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8639                              PosFlags(forwardMostMove - 1),
8640                              fromY, fromX, toY, toX, promoChar,
8641                              parseList[forwardMostMove - 1]);
8642     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8643       case MT_NONE:
8644       case MT_STALEMATE:
8645       default:
8646         break;
8647       case MT_CHECK:
8648         if(gameInfo.variant != VariantShogi)
8649             strcat(parseList[forwardMostMove - 1], "+");
8650         break;
8651       case MT_CHECKMATE:
8652       case MT_STAINMATE:
8653         strcat(parseList[forwardMostMove - 1], "#");
8654         break;
8655     }
8656     if (appData.debugMode) {
8657         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8658     }
8659
8660 }
8661
8662 /* Updates currentMove if not pausing */
8663 void
8664 ShowMove(fromX, fromY, toX, toY)
8665 {
8666     int instant = (gameMode == PlayFromGameFile) ?
8667         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8668     if(appData.noGUI) return;
8669     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8670         if (!instant) {
8671             if (forwardMostMove == currentMove + 1) {
8672                 AnimateMove(boards[forwardMostMove - 1],
8673                             fromX, fromY, toX, toY);
8674             }
8675             if (appData.highlightLastMove) {
8676                 SetHighlights(fromX, fromY, toX, toY);
8677             }
8678         }
8679         currentMove = forwardMostMove;
8680     }
8681
8682     if (instant) return;
8683
8684     DisplayMove(currentMove - 1);
8685     DrawPosition(FALSE, boards[currentMove]);
8686     DisplayBothClocks();
8687     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8688 }
8689
8690 void SendEgtPath(ChessProgramState *cps)
8691 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8692         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8693
8694         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8695
8696         while(*p) {
8697             char c, *q = name+1, *r, *s;
8698
8699             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8700             while(*p && *p != ',') *q++ = *p++;
8701             *q++ = ':'; *q = 0;
8702             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8703                 strcmp(name, ",nalimov:") == 0 ) {
8704                 // take nalimov path from the menu-changeable option first, if it is defined
8705                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8706                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8707             } else
8708             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8709                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8710                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8711                 s = r = StrStr(s, ":") + 1; // beginning of path info
8712                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8713                 c = *r; *r = 0;             // temporarily null-terminate path info
8714                     *--q = 0;               // strip of trailig ':' from name
8715                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8716                 *r = c;
8717                 SendToProgram(buf,cps);     // send egtbpath command for this format
8718             }
8719             if(*p == ',') p++; // read away comma to position for next format name
8720         }
8721 }
8722
8723 void
8724 InitChessProgram(cps, setup)
8725      ChessProgramState *cps;
8726      int setup; /* [HGM] needed to setup FRC opening position */
8727 {
8728     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8729     if (appData.noChessProgram) return;
8730     hintRequested = FALSE;
8731     bookRequested = FALSE;
8732
8733     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8734     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8735     if(cps->memSize) { /* [HGM] memory */
8736         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8737         SendToProgram(buf, cps);
8738     }
8739     SendEgtPath(cps); /* [HGM] EGT */
8740     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8741         sprintf(buf, "cores %d\n", appData.smpCores);
8742         SendToProgram(buf, cps);
8743     }
8744
8745     SendToProgram(cps->initString, cps);
8746     if (gameInfo.variant != VariantNormal &&
8747         gameInfo.variant != VariantLoadable
8748         /* [HGM] also send variant if board size non-standard */
8749         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8750                                             ) {
8751       char *v = VariantName(gameInfo.variant);
8752       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8753         /* [HGM] in protocol 1 we have to assume all variants valid */
8754         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8755         DisplayFatalError(buf, 0, 1);
8756         return;
8757       }
8758
8759       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8760       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8761       if( gameInfo.variant == VariantXiangqi )
8762            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8763       if( gameInfo.variant == VariantShogi )
8764            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8765       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8766            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8767       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8768                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8769            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8770       if( gameInfo.variant == VariantCourier )
8771            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8772       if( gameInfo.variant == VariantSuper )
8773            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8774       if( gameInfo.variant == VariantGreat )
8775            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8776
8777       if(overruled) {
8778            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8779                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8780            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8781            if(StrStr(cps->variants, b) == NULL) { 
8782                // specific sized variant not known, check if general sizing allowed
8783                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8784                    if(StrStr(cps->variants, "boardsize") == NULL) {
8785                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8786                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8787                        DisplayFatalError(buf, 0, 1);
8788                        return;
8789                    }
8790                    /* [HGM] here we really should compare with the maximum supported board size */
8791                }
8792            }
8793       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8794       sprintf(buf, "variant %s\n", b);
8795       SendToProgram(buf, cps);
8796     }
8797     currentlyInitializedVariant = gameInfo.variant;
8798
8799     /* [HGM] send opening position in FRC to first engine */
8800     if(setup) {
8801           SendToProgram("force\n", cps);
8802           SendBoard(cps, 0);
8803           /* engine is now in force mode! Set flag to wake it up after first move. */
8804           setboardSpoiledMachineBlack = 1;
8805     }
8806
8807     if (cps->sendICS) {
8808       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8809       SendToProgram(buf, cps);
8810     }
8811     cps->maybeThinking = FALSE;
8812     cps->offeredDraw = 0;
8813     if (!appData.icsActive) {
8814         SendTimeControl(cps, movesPerSession, timeControl,
8815                         timeIncrement, appData.searchDepth,
8816                         searchTime);
8817     }
8818     if (appData.showThinking 
8819         // [HGM] thinking: four options require thinking output to be sent
8820         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8821                                 ) {
8822         SendToProgram("post\n", cps);
8823     }
8824     SendToProgram("hard\n", cps);
8825     if (!appData.ponderNextMove) {
8826         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8827            it without being sure what state we are in first.  "hard"
8828            is not a toggle, so that one is OK.
8829          */
8830         SendToProgram("easy\n", cps);
8831     }
8832     if (cps->usePing) {
8833       sprintf(buf, "ping %d\n", ++cps->lastPing);
8834       SendToProgram(buf, cps);
8835     }
8836     cps->initDone = TRUE;
8837 }   
8838
8839
8840 void
8841 StartChessProgram(cps)
8842      ChessProgramState *cps;
8843 {
8844     char buf[MSG_SIZ];
8845     int err;
8846
8847     if (appData.noChessProgram) return;
8848     cps->initDone = FALSE;
8849
8850     if (strcmp(cps->host, "localhost") == 0) {
8851         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8852     } else if (*appData.remoteShell == NULLCHAR) {
8853         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8854     } else {
8855         if (*appData.remoteUser == NULLCHAR) {
8856           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8857                     cps->program);
8858         } else {
8859           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8860                     cps->host, appData.remoteUser, cps->program);
8861         }
8862         err = StartChildProcess(buf, "", &cps->pr);
8863     }
8864     
8865     if (err != 0) {
8866         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8867         DisplayFatalError(buf, err, 1);
8868         cps->pr = NoProc;
8869         cps->isr = NULL;
8870         return;
8871     }
8872     
8873     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8874     if (cps->protocolVersion > 1) {
8875       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8876       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8877       cps->comboCnt = 0;  //                and values of combo boxes
8878       SendToProgram(buf, cps);
8879     } else {
8880       SendToProgram("xboard\n", cps);
8881     }
8882 }
8883
8884
8885 void
8886 TwoMachinesEventIfReady P((void))
8887 {
8888   if (first.lastPing != first.lastPong) {
8889     DisplayMessage("", _("Waiting for first chess program"));
8890     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8891     return;
8892   }
8893   if (second.lastPing != second.lastPong) {
8894     DisplayMessage("", _("Waiting for second chess program"));
8895     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8896     return;
8897   }
8898   ThawUI();
8899   TwoMachinesEvent();
8900 }
8901
8902 void
8903 NextMatchGame P((void))
8904 {
8905     int index; /* [HGM] autoinc: step load index during match */
8906     Reset(FALSE, TRUE);
8907     if (*appData.loadGameFile != NULLCHAR) {
8908         index = appData.loadGameIndex;
8909         if(index < 0) { // [HGM] autoinc
8910             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8911             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8912         } 
8913         LoadGameFromFile(appData.loadGameFile,
8914                          index,
8915                          appData.loadGameFile, FALSE);
8916     } else if (*appData.loadPositionFile != NULLCHAR) {
8917         index = appData.loadPositionIndex;
8918         if(index < 0) { // [HGM] autoinc
8919             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8920             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8921         } 
8922         LoadPositionFromFile(appData.loadPositionFile,
8923                              index,
8924                              appData.loadPositionFile);
8925     }
8926     TwoMachinesEventIfReady();
8927 }
8928
8929 void UserAdjudicationEvent( int result )
8930 {
8931     ChessMove gameResult = GameIsDrawn;
8932
8933     if( result > 0 ) {
8934         gameResult = WhiteWins;
8935     }
8936     else if( result < 0 ) {
8937         gameResult = BlackWins;
8938     }
8939
8940     if( gameMode == TwoMachinesPlay ) {
8941         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8942     }
8943 }
8944
8945
8946 // [HGM] save: calculate checksum of game to make games easily identifiable
8947 int StringCheckSum(char *s)
8948 {
8949         int i = 0;
8950         if(s==NULL) return 0;
8951         while(*s) i = i*259 + *s++;
8952         return i;
8953 }
8954
8955 int GameCheckSum()
8956 {
8957         int i, sum=0;
8958         for(i=backwardMostMove; i<forwardMostMove; i++) {
8959                 sum += pvInfoList[i].depth;
8960                 sum += StringCheckSum(parseList[i]);
8961                 sum += StringCheckSum(commentList[i]);
8962                 sum *= 261;
8963         }
8964         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8965         return sum + StringCheckSum(commentList[i]);
8966 } // end of save patch
8967
8968 void
8969 GameEnds(result, resultDetails, whosays)
8970      ChessMove result;
8971      char *resultDetails;
8972      int whosays;
8973 {
8974     GameMode nextGameMode;
8975     int isIcsGame;
8976     char buf[MSG_SIZ];
8977
8978     if(endingGame) return; /* [HGM] crash: forbid recursion */
8979     endingGame = 1;
8980     if(twoBoards) { // [HGM] dual: switch back to one board
8981         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8982         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8983     }
8984     if (appData.debugMode) {
8985       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8986               result, resultDetails ? resultDetails : "(null)", whosays);
8987     }
8988
8989     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8990
8991     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8992         /* If we are playing on ICS, the server decides when the
8993            game is over, but the engine can offer to draw, claim 
8994            a draw, or resign. 
8995          */
8996 #if ZIPPY
8997         if (appData.zippyPlay && first.initDone) {
8998             if (result == GameIsDrawn) {
8999                 /* In case draw still needs to be claimed */
9000                 SendToICS(ics_prefix);
9001                 SendToICS("draw\n");
9002             } else if (StrCaseStr(resultDetails, "resign")) {
9003                 SendToICS(ics_prefix);
9004                 SendToICS("resign\n");
9005             }
9006         }
9007 #endif
9008         endingGame = 0; /* [HGM] crash */
9009         return;
9010     }
9011
9012     /* If we're loading the game from a file, stop */
9013     if (whosays == GE_FILE) {
9014       (void) StopLoadGameTimer();
9015       gameFileFP = NULL;
9016     }
9017
9018     /* Cancel draw offers */
9019     first.offeredDraw = second.offeredDraw = 0;
9020
9021     /* If this is an ICS game, only ICS can really say it's done;
9022        if not, anyone can. */
9023     isIcsGame = (gameMode == IcsPlayingWhite || 
9024                  gameMode == IcsPlayingBlack || 
9025                  gameMode == IcsObserving    || 
9026                  gameMode == IcsExamining);
9027
9028     if (!isIcsGame || whosays == GE_ICS) {
9029         /* OK -- not an ICS game, or ICS said it was done */
9030         StopClocks();
9031         if (!isIcsGame && !appData.noChessProgram) 
9032           SetUserThinkingEnables();
9033     
9034         /* [HGM] if a machine claims the game end we verify this claim */
9035         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9036             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9037                 char claimer;
9038                 ChessMove trueResult = (ChessMove) -1;
9039
9040                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9041                                             first.twoMachinesColor[0] :
9042                                             second.twoMachinesColor[0] ;
9043
9044                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9045                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9046                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9047                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9048                 } else
9049                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9050                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9051                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9052                 } else
9053                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9054                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9055                 }
9056
9057                 // now verify win claims, but not in drop games, as we don't understand those yet
9058                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9059                                                  || gameInfo.variant == VariantGreat) &&
9060                     (result == WhiteWins && claimer == 'w' ||
9061                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9062                       if (appData.debugMode) {
9063                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9064                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9065                       }
9066                       if(result != trueResult) {
9067                               sprintf(buf, "False win claim: '%s'", resultDetails);
9068                               result = claimer == 'w' ? BlackWins : WhiteWins;
9069                               resultDetails = buf;
9070                       }
9071                 } else
9072                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9073                     && (forwardMostMove <= backwardMostMove ||
9074                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9075                         (claimer=='b')==(forwardMostMove&1))
9076                                                                                   ) {
9077                       /* [HGM] verify: draws that were not flagged are false claims */
9078                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9079                       result = claimer == 'w' ? BlackWins : WhiteWins;
9080                       resultDetails = buf;
9081                 }
9082                 /* (Claiming a loss is accepted no questions asked!) */
9083             }
9084             /* [HGM] bare: don't allow bare King to win */
9085             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9086                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9087                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9088                && result != GameIsDrawn)
9089             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9090                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9091                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9092                         if(p >= 0 && p <= (int)WhiteKing) k++;
9093                 }
9094                 if (appData.debugMode) {
9095                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9096                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9097                 }
9098                 if(k <= 1) {
9099                         result = GameIsDrawn;
9100                         sprintf(buf, "%s but bare king", resultDetails);
9101                         resultDetails = buf;
9102                 }
9103             }
9104         }
9105
9106
9107         if(serverMoves != NULL && !loadFlag) { char c = '=';
9108             if(result==WhiteWins) c = '+';
9109             if(result==BlackWins) c = '-';
9110             if(resultDetails != NULL)
9111                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9112         }
9113         if (resultDetails != NULL) {
9114             gameInfo.result = result;
9115             gameInfo.resultDetails = StrSave(resultDetails);
9116
9117             /* display last move only if game was not loaded from file */
9118             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9119                 DisplayMove(currentMove - 1);
9120     
9121             if (forwardMostMove != 0) {
9122                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9123                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9124                                                                 ) {
9125                     if (*appData.saveGameFile != NULLCHAR) {
9126                         SaveGameToFile(appData.saveGameFile, TRUE);
9127                     } else if (appData.autoSaveGames) {
9128                         AutoSaveGame();
9129                     }
9130                     if (*appData.savePositionFile != NULLCHAR) {
9131                         SavePositionToFile(appData.savePositionFile);
9132                     }
9133                 }
9134             }
9135
9136             /* Tell program how game ended in case it is learning */
9137             /* [HGM] Moved this to after saving the PGN, just in case */
9138             /* engine died and we got here through time loss. In that */
9139             /* case we will get a fatal error writing the pipe, which */
9140             /* would otherwise lose us the PGN.                       */
9141             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9142             /* output during GameEnds should never be fatal anymore   */
9143             if (gameMode == MachinePlaysWhite ||
9144                 gameMode == MachinePlaysBlack ||
9145                 gameMode == TwoMachinesPlay ||
9146                 gameMode == IcsPlayingWhite ||
9147                 gameMode == IcsPlayingBlack ||
9148                 gameMode == BeginningOfGame) {
9149                 char buf[MSG_SIZ];
9150                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9151                         resultDetails);
9152                 if (first.pr != NoProc) {
9153                     SendToProgram(buf, &first);
9154                 }
9155                 if (second.pr != NoProc &&
9156                     gameMode == TwoMachinesPlay) {
9157                     SendToProgram(buf, &second);
9158                 }
9159             }
9160         }
9161
9162         if (appData.icsActive) {
9163             if (appData.quietPlay &&
9164                 (gameMode == IcsPlayingWhite ||
9165                  gameMode == IcsPlayingBlack)) {
9166                 SendToICS(ics_prefix);
9167                 SendToICS("set shout 1\n");
9168             }
9169             nextGameMode = IcsIdle;
9170             ics_user_moved = FALSE;
9171             /* clean up premove.  It's ugly when the game has ended and the
9172              * premove highlights are still on the board.
9173              */
9174             if (gotPremove) {
9175               gotPremove = FALSE;
9176               ClearPremoveHighlights();
9177               DrawPosition(FALSE, boards[currentMove]);
9178             }
9179             if (whosays == GE_ICS) {
9180                 switch (result) {
9181                 case WhiteWins:
9182                     if (gameMode == IcsPlayingWhite)
9183                         PlayIcsWinSound();
9184                     else if(gameMode == IcsPlayingBlack)
9185                         PlayIcsLossSound();
9186                     break;
9187                 case BlackWins:
9188                     if (gameMode == IcsPlayingBlack)
9189                         PlayIcsWinSound();
9190                     else if(gameMode == IcsPlayingWhite)
9191                         PlayIcsLossSound();
9192                     break;
9193                 case GameIsDrawn:
9194                     PlayIcsDrawSound();
9195                     break;
9196                 default:
9197                     PlayIcsUnfinishedSound();
9198                 }
9199             }
9200         } else if (gameMode == EditGame ||
9201                    gameMode == PlayFromGameFile || 
9202                    gameMode == AnalyzeMode || 
9203                    gameMode == AnalyzeFile) {
9204             nextGameMode = gameMode;
9205         } else {
9206             nextGameMode = EndOfGame;
9207         }
9208         pausing = FALSE;
9209         ModeHighlight();
9210     } else {
9211         nextGameMode = gameMode;
9212     }
9213
9214     if (appData.noChessProgram) {
9215         gameMode = nextGameMode;
9216         ModeHighlight();
9217         endingGame = 0; /* [HGM] crash */
9218         return;
9219     }
9220
9221     if (first.reuse) {
9222         /* Put first chess program into idle state */
9223         if (first.pr != NoProc &&
9224             (gameMode == MachinePlaysWhite ||
9225              gameMode == MachinePlaysBlack ||
9226              gameMode == TwoMachinesPlay ||
9227              gameMode == IcsPlayingWhite ||
9228              gameMode == IcsPlayingBlack ||
9229              gameMode == BeginningOfGame)) {
9230             SendToProgram("force\n", &first);
9231             if (first.usePing) {
9232               char buf[MSG_SIZ];
9233               sprintf(buf, "ping %d\n", ++first.lastPing);
9234               SendToProgram(buf, &first);
9235             }
9236         }
9237     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9238         /* Kill off first chess program */
9239         if (first.isr != NULL)
9240           RemoveInputSource(first.isr);
9241         first.isr = NULL;
9242     
9243         if (first.pr != NoProc) {
9244             ExitAnalyzeMode();
9245             DoSleep( appData.delayBeforeQuit );
9246             SendToProgram("quit\n", &first);
9247             DoSleep( appData.delayAfterQuit );
9248             DestroyChildProcess(first.pr, first.useSigterm);
9249         }
9250         first.pr = NoProc;
9251     }
9252     if (second.reuse) {
9253         /* Put second chess program into idle state */
9254         if (second.pr != NoProc &&
9255             gameMode == TwoMachinesPlay) {
9256             SendToProgram("force\n", &second);
9257             if (second.usePing) {
9258               char buf[MSG_SIZ];
9259               sprintf(buf, "ping %d\n", ++second.lastPing);
9260               SendToProgram(buf, &second);
9261             }
9262         }
9263     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9264         /* Kill off second chess program */
9265         if (second.isr != NULL)
9266           RemoveInputSource(second.isr);
9267         second.isr = NULL;
9268     
9269         if (second.pr != NoProc) {
9270             DoSleep( appData.delayBeforeQuit );
9271             SendToProgram("quit\n", &second);
9272             DoSleep( appData.delayAfterQuit );
9273             DestroyChildProcess(second.pr, second.useSigterm);
9274         }
9275         second.pr = NoProc;
9276     }
9277
9278     if (matchMode && gameMode == TwoMachinesPlay) {
9279         switch (result) {
9280         case WhiteWins:
9281           if (first.twoMachinesColor[0] == 'w') {
9282             first.matchWins++;
9283           } else {
9284             second.matchWins++;
9285           }
9286           break;
9287         case BlackWins:
9288           if (first.twoMachinesColor[0] == 'b') {
9289             first.matchWins++;
9290           } else {
9291             second.matchWins++;
9292           }
9293           break;
9294         default:
9295           break;
9296         }
9297         if (matchGame < appData.matchGames) {
9298             char *tmp;
9299             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9300                 tmp = first.twoMachinesColor;
9301                 first.twoMachinesColor = second.twoMachinesColor;
9302                 second.twoMachinesColor = tmp;
9303             }
9304             gameMode = nextGameMode;
9305             matchGame++;
9306             if(appData.matchPause>10000 || appData.matchPause<10)
9307                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9308             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9309             endingGame = 0; /* [HGM] crash */
9310             return;
9311         } else {
9312             char buf[MSG_SIZ];
9313             gameMode = nextGameMode;
9314             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9315                     first.tidy, second.tidy,
9316                     first.matchWins, second.matchWins,
9317                     appData.matchGames - (first.matchWins + second.matchWins));
9318             DisplayFatalError(buf, 0, 0);
9319         }
9320     }
9321     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9322         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9323       ExitAnalyzeMode();
9324     gameMode = nextGameMode;
9325     ModeHighlight();
9326     endingGame = 0;  /* [HGM] crash */
9327 }
9328
9329 /* Assumes program was just initialized (initString sent).
9330    Leaves program in force mode. */
9331 void
9332 FeedMovesToProgram(cps, upto) 
9333      ChessProgramState *cps;
9334      int upto;
9335 {
9336     int i;
9337     
9338     if (appData.debugMode)
9339       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9340               startedFromSetupPosition ? "position and " : "",
9341               backwardMostMove, upto, cps->which);
9342     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9343         // [HGM] variantswitch: make engine aware of new variant
9344         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9345                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9346         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9347         SendToProgram(buf, cps);
9348         currentlyInitializedVariant = gameInfo.variant;
9349     }
9350     SendToProgram("force\n", cps);
9351     if (startedFromSetupPosition) {
9352         SendBoard(cps, backwardMostMove);
9353     if (appData.debugMode) {
9354         fprintf(debugFP, "feedMoves\n");
9355     }
9356     }
9357     for (i = backwardMostMove; i < upto; i++) {
9358         SendMoveToProgram(i, cps);
9359     }
9360 }
9361
9362
9363 void
9364 ResurrectChessProgram()
9365 {
9366      /* The chess program may have exited.
9367         If so, restart it and feed it all the moves made so far. */
9368
9369     if (appData.noChessProgram || first.pr != NoProc) return;
9370     
9371     StartChessProgram(&first);
9372     InitChessProgram(&first, FALSE);
9373     FeedMovesToProgram(&first, currentMove);
9374
9375     if (!first.sendTime) {
9376         /* can't tell gnuchess what its clock should read,
9377            so we bow to its notion. */
9378         ResetClocks();
9379         timeRemaining[0][currentMove] = whiteTimeRemaining;
9380         timeRemaining[1][currentMove] = blackTimeRemaining;
9381     }
9382
9383     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9384                 appData.icsEngineAnalyze) && first.analysisSupport) {
9385       SendToProgram("analyze\n", &first);
9386       first.analyzing = TRUE;
9387     }
9388 }
9389
9390 /*
9391  * Button procedures
9392  */
9393 void
9394 Reset(redraw, init)
9395      int redraw, init;
9396 {
9397     int i;
9398
9399     if (appData.debugMode) {
9400         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9401                 redraw, init, gameMode);
9402     }
9403     CleanupTail(); // [HGM] vari: delete any stored variations
9404     pausing = pauseExamInvalid = FALSE;
9405     startedFromSetupPosition = blackPlaysFirst = FALSE;
9406     firstMove = TRUE;
9407     whiteFlag = blackFlag = FALSE;
9408     userOfferedDraw = FALSE;
9409     hintRequested = bookRequested = FALSE;
9410     first.maybeThinking = FALSE;
9411     second.maybeThinking = FALSE;
9412     first.bookSuspend = FALSE; // [HGM] book
9413     second.bookSuspend = FALSE;
9414     thinkOutput[0] = NULLCHAR;
9415     lastHint[0] = NULLCHAR;
9416     ClearGameInfo(&gameInfo);
9417     gameInfo.variant = StringToVariant(appData.variant);
9418     ics_user_moved = ics_clock_paused = FALSE;
9419     ics_getting_history = H_FALSE;
9420     ics_gamenum = -1;
9421     white_holding[0] = black_holding[0] = NULLCHAR;
9422     ClearProgramStats();
9423     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9424     
9425     ResetFrontEnd();
9426     ClearHighlights();
9427     flipView = appData.flipView;
9428     ClearPremoveHighlights();
9429     gotPremove = FALSE;
9430     alarmSounded = FALSE;
9431
9432     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9433     if(appData.serverMovesName != NULL) {
9434         /* [HGM] prepare to make moves file for broadcasting */
9435         clock_t t = clock();
9436         if(serverMoves != NULL) fclose(serverMoves);
9437         serverMoves = fopen(appData.serverMovesName, "r");
9438         if(serverMoves != NULL) {
9439             fclose(serverMoves);
9440             /* delay 15 sec before overwriting, so all clients can see end */
9441             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9442         }
9443         serverMoves = fopen(appData.serverMovesName, "w");
9444     }
9445
9446     ExitAnalyzeMode();
9447     gameMode = BeginningOfGame;
9448     ModeHighlight();
9449     if(appData.icsActive) gameInfo.variant = VariantNormal;
9450     currentMove = forwardMostMove = backwardMostMove = 0;
9451     InitPosition(redraw);
9452     for (i = 0; i < MAX_MOVES; i++) {
9453         if (commentList[i] != NULL) {
9454             free(commentList[i]);
9455             commentList[i] = NULL;
9456         }
9457     }
9458     ResetClocks();
9459     timeRemaining[0][0] = whiteTimeRemaining;
9460     timeRemaining[1][0] = blackTimeRemaining;
9461     if (first.pr == NULL) {
9462         StartChessProgram(&first);
9463     }
9464     if (init) {
9465             InitChessProgram(&first, startedFromSetupPosition);
9466     }
9467     DisplayTitle("");
9468     DisplayMessage("", "");
9469     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9470     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9471 }
9472
9473 void
9474 AutoPlayGameLoop()
9475 {
9476     for (;;) {
9477         if (!AutoPlayOneMove())
9478           return;
9479         if (matchMode || appData.timeDelay == 0)
9480           continue;
9481         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9482           return;
9483         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9484         break;
9485     }
9486 }
9487
9488
9489 int
9490 AutoPlayOneMove()
9491 {
9492     int fromX, fromY, toX, toY;
9493
9494     if (appData.debugMode) {
9495       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9496     }
9497
9498     if (gameMode != PlayFromGameFile)
9499       return FALSE;
9500
9501     if (currentMove >= forwardMostMove) {
9502       gameMode = EditGame;
9503       ModeHighlight();
9504
9505       /* [AS] Clear current move marker at the end of a game */
9506       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9507
9508       return FALSE;
9509     }
9510     
9511     toX = moveList[currentMove][2] - AAA;
9512     toY = moveList[currentMove][3] - ONE;
9513
9514     if (moveList[currentMove][1] == '@') {
9515         if (appData.highlightLastMove) {
9516             SetHighlights(-1, -1, toX, toY);
9517         }
9518     } else {
9519         fromX = moveList[currentMove][0] - AAA;
9520         fromY = moveList[currentMove][1] - ONE;
9521
9522         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9523
9524         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9525
9526         if (appData.highlightLastMove) {
9527             SetHighlights(fromX, fromY, toX, toY);
9528         }
9529     }
9530     DisplayMove(currentMove);
9531     SendMoveToProgram(currentMove++, &first);
9532     DisplayBothClocks();
9533     DrawPosition(FALSE, boards[currentMove]);
9534     // [HGM] PV info: always display, routine tests if empty
9535     DisplayComment(currentMove - 1, commentList[currentMove]);
9536     return TRUE;
9537 }
9538
9539
9540 int
9541 LoadGameOneMove(readAhead)
9542      ChessMove readAhead;
9543 {
9544     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9545     char promoChar = NULLCHAR;
9546     ChessMove moveType;
9547     char move[MSG_SIZ];
9548     char *p, *q;
9549     
9550     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9551         gameMode != AnalyzeMode && gameMode != Training) {
9552         gameFileFP = NULL;
9553         return FALSE;
9554     }
9555     
9556     yyboardindex = forwardMostMove;
9557     if (readAhead != (ChessMove)0) {
9558       moveType = readAhead;
9559     } else {
9560       if (gameFileFP == NULL)
9561           return FALSE;
9562       moveType = (ChessMove) yylex();
9563     }
9564     
9565     done = FALSE;
9566     switch (moveType) {
9567       case Comment:
9568         if (appData.debugMode) 
9569           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9570         p = yy_text;
9571
9572         /* append the comment but don't display it */
9573         AppendComment(currentMove, p, FALSE);
9574         return TRUE;
9575
9576       case WhiteCapturesEnPassant:
9577       case BlackCapturesEnPassant:
9578       case WhitePromotionChancellor:
9579       case BlackPromotionChancellor:
9580       case WhitePromotionArchbishop:
9581       case BlackPromotionArchbishop:
9582       case WhitePromotionCentaur:
9583       case BlackPromotionCentaur:
9584       case WhitePromotionQueen:
9585       case BlackPromotionQueen:
9586       case WhitePromotionRook:
9587       case BlackPromotionRook:
9588       case WhitePromotionBishop:
9589       case BlackPromotionBishop:
9590       case WhitePromotionKnight:
9591       case BlackPromotionKnight:
9592       case WhitePromotionKing:
9593       case BlackPromotionKing:
9594       case NormalMove:
9595       case WhiteKingSideCastle:
9596       case WhiteQueenSideCastle:
9597       case BlackKingSideCastle:
9598       case BlackQueenSideCastle:
9599       case WhiteKingSideCastleWild:
9600       case WhiteQueenSideCastleWild:
9601       case BlackKingSideCastleWild:
9602       case BlackQueenSideCastleWild:
9603       /* PUSH Fabien */
9604       case WhiteHSideCastleFR:
9605       case WhiteASideCastleFR:
9606       case BlackHSideCastleFR:
9607       case BlackASideCastleFR:
9608       /* POP Fabien */
9609         if (appData.debugMode)
9610           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9611         fromX = currentMoveString[0] - AAA;
9612         fromY = currentMoveString[1] - ONE;
9613         toX = currentMoveString[2] - AAA;
9614         toY = currentMoveString[3] - ONE;
9615         promoChar = currentMoveString[4];
9616         break;
9617
9618       case WhiteDrop:
9619       case BlackDrop:
9620         if (appData.debugMode)
9621           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9622         fromX = moveType == WhiteDrop ?
9623           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9624         (int) CharToPiece(ToLower(currentMoveString[0]));
9625         fromY = DROP_RANK;
9626         toX = currentMoveString[2] - AAA;
9627         toY = currentMoveString[3] - ONE;
9628         break;
9629
9630       case WhiteWins:
9631       case BlackWins:
9632       case GameIsDrawn:
9633       case GameUnfinished:
9634         if (appData.debugMode)
9635           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9636         p = strchr(yy_text, '{');
9637         if (p == NULL) p = strchr(yy_text, '(');
9638         if (p == NULL) {
9639             p = yy_text;
9640             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9641         } else {
9642             q = strchr(p, *p == '{' ? '}' : ')');
9643             if (q != NULL) *q = NULLCHAR;
9644             p++;
9645         }
9646         GameEnds(moveType, p, GE_FILE);
9647         done = TRUE;
9648         if (cmailMsgLoaded) {
9649             ClearHighlights();
9650             flipView = WhiteOnMove(currentMove);
9651             if (moveType == GameUnfinished) flipView = !flipView;
9652             if (appData.debugMode)
9653               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9654         }
9655         break;
9656
9657       case (ChessMove) 0:       /* end of file */
9658         if (appData.debugMode)
9659           fprintf(debugFP, "Parser hit end of file\n");
9660         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9661           case MT_NONE:
9662           case MT_CHECK:
9663             break;
9664           case MT_CHECKMATE:
9665           case MT_STAINMATE:
9666             if (WhiteOnMove(currentMove)) {
9667                 GameEnds(BlackWins, "Black mates", GE_FILE);
9668             } else {
9669                 GameEnds(WhiteWins, "White mates", GE_FILE);
9670             }
9671             break;
9672           case MT_STALEMATE:
9673             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9674             break;
9675         }
9676         done = TRUE;
9677         break;
9678
9679       case MoveNumberOne:
9680         if (lastLoadGameStart == GNUChessGame) {
9681             /* GNUChessGames have numbers, but they aren't move numbers */
9682             if (appData.debugMode)
9683               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9684                       yy_text, (int) moveType);
9685             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9686         }
9687         /* else fall thru */
9688
9689       case XBoardGame:
9690       case GNUChessGame:
9691       case PGNTag:
9692         /* Reached start of next game in file */
9693         if (appData.debugMode)
9694           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9695         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9696           case MT_NONE:
9697           case MT_CHECK:
9698             break;
9699           case MT_CHECKMATE:
9700           case MT_STAINMATE:
9701             if (WhiteOnMove(currentMove)) {
9702                 GameEnds(BlackWins, "Black mates", GE_FILE);
9703             } else {
9704                 GameEnds(WhiteWins, "White mates", GE_FILE);
9705             }
9706             break;
9707           case MT_STALEMATE:
9708             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9709             break;
9710         }
9711         done = TRUE;
9712         break;
9713
9714       case PositionDiagram:     /* should not happen; ignore */
9715       case ElapsedTime:         /* ignore */
9716       case NAG:                 /* ignore */
9717         if (appData.debugMode)
9718           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9719                   yy_text, (int) moveType);
9720         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9721
9722       case IllegalMove:
9723         if (appData.testLegality) {
9724             if (appData.debugMode)
9725               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9726             sprintf(move, _("Illegal move: %d.%s%s"),
9727                     (forwardMostMove / 2) + 1,
9728                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9729             DisplayError(move, 0);
9730             done = TRUE;
9731         } else {
9732             if (appData.debugMode)
9733               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9734                       yy_text, currentMoveString);
9735             fromX = currentMoveString[0] - AAA;
9736             fromY = currentMoveString[1] - ONE;
9737             toX = currentMoveString[2] - AAA;
9738             toY = currentMoveString[3] - ONE;
9739             promoChar = currentMoveString[4];
9740         }
9741         break;
9742
9743       case AmbiguousMove:
9744         if (appData.debugMode)
9745           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9746         sprintf(move, _("Ambiguous move: %d.%s%s"),
9747                 (forwardMostMove / 2) + 1,
9748                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9749         DisplayError(move, 0);
9750         done = TRUE;
9751         break;
9752
9753       default:
9754       case ImpossibleMove:
9755         if (appData.debugMode)
9756           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9757         sprintf(move, _("Illegal move: %d.%s%s"),
9758                 (forwardMostMove / 2) + 1,
9759                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9760         DisplayError(move, 0);
9761         done = TRUE;
9762         break;
9763     }
9764
9765     if (done) {
9766         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9767             DrawPosition(FALSE, boards[currentMove]);
9768             DisplayBothClocks();
9769             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9770               DisplayComment(currentMove - 1, commentList[currentMove]);
9771         }
9772         (void) StopLoadGameTimer();
9773         gameFileFP = NULL;
9774         cmailOldMove = forwardMostMove;
9775         return FALSE;
9776     } else {
9777         /* currentMoveString is set as a side-effect of yylex */
9778         strcat(currentMoveString, "\n");
9779         strcpy(moveList[forwardMostMove], currentMoveString);
9780         
9781         thinkOutput[0] = NULLCHAR;
9782         MakeMove(fromX, fromY, toX, toY, promoChar);
9783         currentMove = forwardMostMove;
9784         return TRUE;
9785     }
9786 }
9787
9788 /* Load the nth game from the given file */
9789 int
9790 LoadGameFromFile(filename, n, title, useList)
9791      char *filename;
9792      int n;
9793      char *title;
9794      /*Boolean*/ int useList;
9795 {
9796     FILE *f;
9797     char buf[MSG_SIZ];
9798
9799     if (strcmp(filename, "-") == 0) {
9800         f = stdin;
9801         title = "stdin";
9802     } else {
9803         f = fopen(filename, "rb");
9804         if (f == NULL) {
9805           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9806             DisplayError(buf, errno);
9807             return FALSE;
9808         }
9809     }
9810     if (fseek(f, 0, 0) == -1) {
9811         /* f is not seekable; probably a pipe */
9812         useList = FALSE;
9813     }
9814     if (useList && n == 0) {
9815         int error = GameListBuild(f);
9816         if (error) {
9817             DisplayError(_("Cannot build game list"), error);
9818         } else if (!ListEmpty(&gameList) &&
9819                    ((ListGame *) gameList.tailPred)->number > 1) {
9820             GameListPopUp(f, title);
9821             return TRUE;
9822         }
9823         GameListDestroy();
9824         n = 1;
9825     }
9826     if (n == 0) n = 1;
9827     return LoadGame(f, n, title, FALSE);
9828 }
9829
9830
9831 void
9832 MakeRegisteredMove()
9833 {
9834     int fromX, fromY, toX, toY;
9835     char promoChar;
9836     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9837         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9838           case CMAIL_MOVE:
9839           case CMAIL_DRAW:
9840             if (appData.debugMode)
9841               fprintf(debugFP, "Restoring %s for game %d\n",
9842                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9843     
9844             thinkOutput[0] = NULLCHAR;
9845             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9846             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9847             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9848             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9849             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9850             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9851             MakeMove(fromX, fromY, toX, toY, promoChar);
9852             ShowMove(fromX, fromY, toX, toY);
9853               
9854             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9855               case MT_NONE:
9856               case MT_CHECK:
9857                 break;
9858                 
9859               case MT_CHECKMATE:
9860               case MT_STAINMATE:
9861                 if (WhiteOnMove(currentMove)) {
9862                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9863                 } else {
9864                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9865                 }
9866                 break;
9867                 
9868               case MT_STALEMATE:
9869                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9870                 break;
9871             }
9872
9873             break;
9874             
9875           case CMAIL_RESIGN:
9876             if (WhiteOnMove(currentMove)) {
9877                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9878             } else {
9879                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9880             }
9881             break;
9882             
9883           case CMAIL_ACCEPT:
9884             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9885             break;
9886               
9887           default:
9888             break;
9889         }
9890     }
9891
9892     return;
9893 }
9894
9895 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9896 int
9897 CmailLoadGame(f, gameNumber, title, useList)
9898      FILE *f;
9899      int gameNumber;
9900      char *title;
9901      int useList;
9902 {
9903     int retVal;
9904
9905     if (gameNumber > nCmailGames) {
9906         DisplayError(_("No more games in this message"), 0);
9907         return FALSE;
9908     }
9909     if (f == lastLoadGameFP) {
9910         int offset = gameNumber - lastLoadGameNumber;
9911         if (offset == 0) {
9912             cmailMsg[0] = NULLCHAR;
9913             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9914                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9915                 nCmailMovesRegistered--;
9916             }
9917             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9918             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9919                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9920             }
9921         } else {
9922             if (! RegisterMove()) return FALSE;
9923         }
9924     }
9925
9926     retVal = LoadGame(f, gameNumber, title, useList);
9927
9928     /* Make move registered during previous look at this game, if any */
9929     MakeRegisteredMove();
9930
9931     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9932         commentList[currentMove]
9933           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9934         DisplayComment(currentMove - 1, commentList[currentMove]);
9935     }
9936
9937     return retVal;
9938 }
9939
9940 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9941 int
9942 ReloadGame(offset)
9943      int offset;
9944 {
9945     int gameNumber = lastLoadGameNumber + offset;
9946     if (lastLoadGameFP == NULL) {
9947         DisplayError(_("No game has been loaded yet"), 0);
9948         return FALSE;
9949     }
9950     if (gameNumber <= 0) {
9951         DisplayError(_("Can't back up any further"), 0);
9952         return FALSE;
9953     }
9954     if (cmailMsgLoaded) {
9955         return CmailLoadGame(lastLoadGameFP, gameNumber,
9956                              lastLoadGameTitle, lastLoadGameUseList);
9957     } else {
9958         return LoadGame(lastLoadGameFP, gameNumber,
9959                         lastLoadGameTitle, lastLoadGameUseList);
9960     }
9961 }
9962
9963
9964
9965 /* Load the nth game from open file f */
9966 int
9967 LoadGame(f, gameNumber, title, useList)
9968      FILE *f;
9969      int gameNumber;
9970      char *title;
9971      int useList;
9972 {
9973     ChessMove cm;
9974     char buf[MSG_SIZ];
9975     int gn = gameNumber;
9976     ListGame *lg = NULL;
9977     int numPGNTags = 0;
9978     int err;
9979     GameMode oldGameMode;
9980     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9981
9982     if (appData.debugMode) 
9983         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9984
9985     if (gameMode == Training )
9986         SetTrainingModeOff();
9987
9988     oldGameMode = gameMode;
9989     if (gameMode != BeginningOfGame) {
9990       Reset(FALSE, TRUE);
9991     }
9992
9993     gameFileFP = f;
9994     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9995         fclose(lastLoadGameFP);
9996     }
9997
9998     if (useList) {
9999         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10000         
10001         if (lg) {
10002             fseek(f, lg->offset, 0);
10003             GameListHighlight(gameNumber);
10004             gn = 1;
10005         }
10006         else {
10007             DisplayError(_("Game number out of range"), 0);
10008             return FALSE;
10009         }
10010     } else {
10011         GameListDestroy();
10012         if (fseek(f, 0, 0) == -1) {
10013             if (f == lastLoadGameFP ?
10014                 gameNumber == lastLoadGameNumber + 1 :
10015                 gameNumber == 1) {
10016                 gn = 1;
10017             } else {
10018                 DisplayError(_("Can't seek on game file"), 0);
10019                 return FALSE;
10020             }
10021         }
10022     }
10023     lastLoadGameFP = f;
10024     lastLoadGameNumber = gameNumber;
10025     strcpy(lastLoadGameTitle, title);
10026     lastLoadGameUseList = useList;
10027
10028     yynewfile(f);
10029
10030     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10031       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10032                 lg->gameInfo.black);
10033             DisplayTitle(buf);
10034     } else if (*title != NULLCHAR) {
10035         if (gameNumber > 1) {
10036             sprintf(buf, "%s %d", title, gameNumber);
10037             DisplayTitle(buf);
10038         } else {
10039             DisplayTitle(title);
10040         }
10041     }
10042
10043     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10044         gameMode = PlayFromGameFile;
10045         ModeHighlight();
10046     }
10047
10048     currentMove = forwardMostMove = backwardMostMove = 0;
10049     CopyBoard(boards[0], initialPosition);
10050     StopClocks();
10051
10052     /*
10053      * Skip the first gn-1 games in the file.
10054      * Also skip over anything that precedes an identifiable 
10055      * start of game marker, to avoid being confused by 
10056      * garbage at the start of the file.  Currently 
10057      * recognized start of game markers are the move number "1",
10058      * the pattern "gnuchess .* game", the pattern
10059      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10060      * A game that starts with one of the latter two patterns
10061      * will also have a move number 1, possibly
10062      * following a position diagram.
10063      * 5-4-02: Let's try being more lenient and allowing a game to
10064      * start with an unnumbered move.  Does that break anything?
10065      */
10066     cm = lastLoadGameStart = (ChessMove) 0;
10067     while (gn > 0) {
10068         yyboardindex = forwardMostMove;
10069         cm = (ChessMove) yylex();
10070         switch (cm) {
10071           case (ChessMove) 0:
10072             if (cmailMsgLoaded) {
10073                 nCmailGames = CMAIL_MAX_GAMES - gn;
10074             } else {
10075                 Reset(TRUE, TRUE);
10076                 DisplayError(_("Game not found in file"), 0);
10077             }
10078             return FALSE;
10079
10080           case GNUChessGame:
10081           case XBoardGame:
10082             gn--;
10083             lastLoadGameStart = cm;
10084             break;
10085             
10086           case MoveNumberOne:
10087             switch (lastLoadGameStart) {
10088               case GNUChessGame:
10089               case XBoardGame:
10090               case PGNTag:
10091                 break;
10092               case MoveNumberOne:
10093               case (ChessMove) 0:
10094                 gn--;           /* count this game */
10095                 lastLoadGameStart = cm;
10096                 break;
10097               default:
10098                 /* impossible */
10099                 break;
10100             }
10101             break;
10102
10103           case PGNTag:
10104             switch (lastLoadGameStart) {
10105               case GNUChessGame:
10106               case PGNTag:
10107               case MoveNumberOne:
10108               case (ChessMove) 0:
10109                 gn--;           /* count this game */
10110                 lastLoadGameStart = cm;
10111                 break;
10112               case XBoardGame:
10113                 lastLoadGameStart = cm; /* game counted already */
10114                 break;
10115               default:
10116                 /* impossible */
10117                 break;
10118             }
10119             if (gn > 0) {
10120                 do {
10121                     yyboardindex = forwardMostMove;
10122                     cm = (ChessMove) yylex();
10123                 } while (cm == PGNTag || cm == Comment);
10124             }
10125             break;
10126
10127           case WhiteWins:
10128           case BlackWins:
10129           case GameIsDrawn:
10130             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10131                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10132                     != CMAIL_OLD_RESULT) {
10133                     nCmailResults ++ ;
10134                     cmailResult[  CMAIL_MAX_GAMES
10135                                 - gn - 1] = CMAIL_OLD_RESULT;
10136                 }
10137             }
10138             break;
10139
10140           case NormalMove:
10141             /* Only a NormalMove can be at the start of a game
10142              * without a position diagram. */
10143             if (lastLoadGameStart == (ChessMove) 0) {
10144               gn--;
10145               lastLoadGameStart = MoveNumberOne;
10146             }
10147             break;
10148
10149           default:
10150             break;
10151         }
10152     }
10153     
10154     if (appData.debugMode)
10155       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10156
10157     if (cm == XBoardGame) {
10158         /* Skip any header junk before position diagram and/or move 1 */
10159         for (;;) {
10160             yyboardindex = forwardMostMove;
10161             cm = (ChessMove) yylex();
10162
10163             if (cm == (ChessMove) 0 ||
10164                 cm == GNUChessGame || cm == XBoardGame) {
10165                 /* Empty game; pretend end-of-file and handle later */
10166                 cm = (ChessMove) 0;
10167                 break;
10168             }
10169
10170             if (cm == MoveNumberOne || cm == PositionDiagram ||
10171                 cm == PGNTag || cm == Comment)
10172               break;
10173         }
10174     } else if (cm == GNUChessGame) {
10175         if (gameInfo.event != NULL) {
10176             free(gameInfo.event);
10177         }
10178         gameInfo.event = StrSave(yy_text);
10179     }   
10180
10181     startedFromSetupPosition = FALSE;
10182     while (cm == PGNTag) {
10183         if (appData.debugMode) 
10184           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10185         err = ParsePGNTag(yy_text, &gameInfo);
10186         if (!err) numPGNTags++;
10187
10188         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10189         if(gameInfo.variant != oldVariant) {
10190             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10191             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10192             InitPosition(TRUE);
10193             oldVariant = gameInfo.variant;
10194             if (appData.debugMode) 
10195               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10196         }
10197
10198
10199         if (gameInfo.fen != NULL) {
10200           Board initial_position;
10201           startedFromSetupPosition = TRUE;
10202           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10203             Reset(TRUE, TRUE);
10204             DisplayError(_("Bad FEN position in file"), 0);
10205             return FALSE;
10206           }
10207           CopyBoard(boards[0], initial_position);
10208           if (blackPlaysFirst) {
10209             currentMove = forwardMostMove = backwardMostMove = 1;
10210             CopyBoard(boards[1], initial_position);
10211             strcpy(moveList[0], "");
10212             strcpy(parseList[0], "");
10213             timeRemaining[0][1] = whiteTimeRemaining;
10214             timeRemaining[1][1] = blackTimeRemaining;
10215             if (commentList[0] != NULL) {
10216               commentList[1] = commentList[0];
10217               commentList[0] = NULL;
10218             }
10219           } else {
10220             currentMove = forwardMostMove = backwardMostMove = 0;
10221           }
10222           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10223           {   int i;
10224               initialRulePlies = FENrulePlies;
10225               for( i=0; i< nrCastlingRights; i++ )
10226                   initialRights[i] = initial_position[CASTLING][i];
10227           }
10228           yyboardindex = forwardMostMove;
10229           free(gameInfo.fen);
10230           gameInfo.fen = NULL;
10231         }
10232
10233         yyboardindex = forwardMostMove;
10234         cm = (ChessMove) yylex();
10235
10236         /* Handle comments interspersed among the tags */
10237         while (cm == Comment) {
10238             char *p;
10239             if (appData.debugMode) 
10240               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10241             p = yy_text;
10242             AppendComment(currentMove, p, FALSE);
10243             yyboardindex = forwardMostMove;
10244             cm = (ChessMove) yylex();
10245         }
10246     }
10247
10248     /* don't rely on existence of Event tag since if game was
10249      * pasted from clipboard the Event tag may not exist
10250      */
10251     if (numPGNTags > 0){
10252         char *tags;
10253         if (gameInfo.variant == VariantNormal) {
10254           VariantClass v = StringToVariant(gameInfo.event);
10255           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10256           if(v < VariantShogi) gameInfo.variant = v;
10257         }
10258         if (!matchMode) {
10259           if( appData.autoDisplayTags ) {
10260             tags = PGNTags(&gameInfo);
10261             TagsPopUp(tags, CmailMsg());
10262             free(tags);
10263           }
10264         }
10265     } else {
10266         /* Make something up, but don't display it now */
10267         SetGameInfo();
10268         TagsPopDown();
10269     }
10270
10271     if (cm == PositionDiagram) {
10272         int i, j;
10273         char *p;
10274         Board initial_position;
10275
10276         if (appData.debugMode)
10277           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10278
10279         if (!startedFromSetupPosition) {
10280             p = yy_text;
10281             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10282               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10283                 switch (*p) {
10284                   case '[':
10285                   case '-':
10286                   case ' ':
10287                   case '\t':
10288                   case '\n':
10289                   case '\r':
10290                     break;
10291                   default:
10292                     initial_position[i][j++] = CharToPiece(*p);
10293                     break;
10294                 }
10295             while (*p == ' ' || *p == '\t' ||
10296                    *p == '\n' || *p == '\r') p++;
10297         
10298             if (strncmp(p, "black", strlen("black"))==0)
10299               blackPlaysFirst = TRUE;
10300             else
10301               blackPlaysFirst = FALSE;
10302             startedFromSetupPosition = TRUE;
10303         
10304             CopyBoard(boards[0], initial_position);
10305             if (blackPlaysFirst) {
10306                 currentMove = forwardMostMove = backwardMostMove = 1;
10307                 CopyBoard(boards[1], initial_position);
10308                 strcpy(moveList[0], "");
10309                 strcpy(parseList[0], "");
10310                 timeRemaining[0][1] = whiteTimeRemaining;
10311                 timeRemaining[1][1] = blackTimeRemaining;
10312                 if (commentList[0] != NULL) {
10313                     commentList[1] = commentList[0];
10314                     commentList[0] = NULL;
10315                 }
10316             } else {
10317                 currentMove = forwardMostMove = backwardMostMove = 0;
10318             }
10319         }
10320         yyboardindex = forwardMostMove;
10321         cm = (ChessMove) yylex();
10322     }
10323
10324     if (first.pr == NoProc) {
10325         StartChessProgram(&first);
10326     }
10327     InitChessProgram(&first, FALSE);
10328     SendToProgram("force\n", &first);
10329     if (startedFromSetupPosition) {
10330         SendBoard(&first, forwardMostMove);
10331     if (appData.debugMode) {
10332         fprintf(debugFP, "Load Game\n");
10333     }
10334         DisplayBothClocks();
10335     }      
10336
10337     /* [HGM] server: flag to write setup moves in broadcast file as one */
10338     loadFlag = appData.suppressLoadMoves;
10339
10340     while (cm == Comment) {
10341         char *p;
10342         if (appData.debugMode) 
10343           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10344         p = yy_text;
10345         AppendComment(currentMove, p, FALSE);
10346         yyboardindex = forwardMostMove;
10347         cm = (ChessMove) yylex();
10348     }
10349
10350     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10351         cm == WhiteWins || cm == BlackWins ||
10352         cm == GameIsDrawn || cm == GameUnfinished) {
10353         DisplayMessage("", _("No moves in game"));
10354         if (cmailMsgLoaded) {
10355             if (appData.debugMode)
10356               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10357             ClearHighlights();
10358             flipView = FALSE;
10359         }
10360         DrawPosition(FALSE, boards[currentMove]);
10361         DisplayBothClocks();
10362         gameMode = EditGame;
10363         ModeHighlight();
10364         gameFileFP = NULL;
10365         cmailOldMove = 0;
10366         return TRUE;
10367     }
10368
10369     // [HGM] PV info: routine tests if comment empty
10370     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10371         DisplayComment(currentMove - 1, commentList[currentMove]);
10372     }
10373     if (!matchMode && appData.timeDelay != 0) 
10374       DrawPosition(FALSE, boards[currentMove]);
10375
10376     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10377       programStats.ok_to_send = 1;
10378     }
10379
10380     /* if the first token after the PGN tags is a move
10381      * and not move number 1, retrieve it from the parser 
10382      */
10383     if (cm != MoveNumberOne)
10384         LoadGameOneMove(cm);
10385
10386     /* load the remaining moves from the file */
10387     while (LoadGameOneMove((ChessMove)0)) {
10388       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10389       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10390     }
10391
10392     /* rewind to the start of the game */
10393     currentMove = backwardMostMove;
10394
10395     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10396
10397     if (oldGameMode == AnalyzeFile ||
10398         oldGameMode == AnalyzeMode) {
10399       AnalyzeFileEvent();
10400     }
10401
10402     if (matchMode || appData.timeDelay == 0) {
10403       ToEndEvent();
10404       gameMode = EditGame;
10405       ModeHighlight();
10406     } else if (appData.timeDelay > 0) {
10407       AutoPlayGameLoop();
10408     }
10409
10410     if (appData.debugMode) 
10411         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10412
10413     loadFlag = 0; /* [HGM] true game starts */
10414     return TRUE;
10415 }
10416
10417 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10418 int
10419 ReloadPosition(offset)
10420      int offset;
10421 {
10422     int positionNumber = lastLoadPositionNumber + offset;
10423     if (lastLoadPositionFP == NULL) {
10424         DisplayError(_("No position has been loaded yet"), 0);
10425         return FALSE;
10426     }
10427     if (positionNumber <= 0) {
10428         DisplayError(_("Can't back up any further"), 0);
10429         return FALSE;
10430     }
10431     return LoadPosition(lastLoadPositionFP, positionNumber,
10432                         lastLoadPositionTitle);
10433 }
10434
10435 /* Load the nth position from the given file */
10436 int
10437 LoadPositionFromFile(filename, n, title)
10438      char *filename;
10439      int n;
10440      char *title;
10441 {
10442     FILE *f;
10443     char buf[MSG_SIZ];
10444
10445     if (strcmp(filename, "-") == 0) {
10446         return LoadPosition(stdin, n, "stdin");
10447     } else {
10448         f = fopen(filename, "rb");
10449         if (f == NULL) {
10450             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10451             DisplayError(buf, errno);
10452             return FALSE;
10453         } else {
10454             return LoadPosition(f, n, title);
10455         }
10456     }
10457 }
10458
10459 /* Load the nth position from the given open file, and close it */
10460 int
10461 LoadPosition(f, positionNumber, title)
10462      FILE *f;
10463      int positionNumber;
10464      char *title;
10465 {
10466     char *p, line[MSG_SIZ];
10467     Board initial_position;
10468     int i, j, fenMode, pn;
10469     
10470     if (gameMode == Training )
10471         SetTrainingModeOff();
10472
10473     if (gameMode != BeginningOfGame) {
10474         Reset(FALSE, TRUE);
10475     }
10476     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10477         fclose(lastLoadPositionFP);
10478     }
10479     if (positionNumber == 0) positionNumber = 1;
10480     lastLoadPositionFP = f;
10481     lastLoadPositionNumber = positionNumber;
10482     strcpy(lastLoadPositionTitle, title);
10483     if (first.pr == NoProc) {
10484       StartChessProgram(&first);
10485       InitChessProgram(&first, FALSE);
10486     }    
10487     pn = positionNumber;
10488     if (positionNumber < 0) {
10489         /* Negative position number means to seek to that byte offset */
10490         if (fseek(f, -positionNumber, 0) == -1) {
10491             DisplayError(_("Can't seek on position file"), 0);
10492             return FALSE;
10493         };
10494         pn = 1;
10495     } else {
10496         if (fseek(f, 0, 0) == -1) {
10497             if (f == lastLoadPositionFP ?
10498                 positionNumber == lastLoadPositionNumber + 1 :
10499                 positionNumber == 1) {
10500                 pn = 1;
10501             } else {
10502                 DisplayError(_("Can't seek on position file"), 0);
10503                 return FALSE;
10504             }
10505         }
10506     }
10507     /* See if this file is FEN or old-style xboard */
10508     if (fgets(line, MSG_SIZ, f) == NULL) {
10509         DisplayError(_("Position not found in file"), 0);
10510         return FALSE;
10511     }
10512     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10513     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10514
10515     if (pn >= 2) {
10516         if (fenMode || line[0] == '#') pn--;
10517         while (pn > 0) {
10518             /* skip positions before number pn */
10519             if (fgets(line, MSG_SIZ, f) == NULL) {
10520                 Reset(TRUE, TRUE);
10521                 DisplayError(_("Position not found in file"), 0);
10522                 return FALSE;
10523             }
10524             if (fenMode || line[0] == '#') pn--;
10525         }
10526     }
10527
10528     if (fenMode) {
10529         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10530             DisplayError(_("Bad FEN position in file"), 0);
10531             return FALSE;
10532         }
10533     } else {
10534         (void) fgets(line, MSG_SIZ, f);
10535         (void) fgets(line, MSG_SIZ, f);
10536     
10537         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10538             (void) fgets(line, MSG_SIZ, f);
10539             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10540                 if (*p == ' ')
10541                   continue;
10542                 initial_position[i][j++] = CharToPiece(*p);
10543             }
10544         }
10545     
10546         blackPlaysFirst = FALSE;
10547         if (!feof(f)) {
10548             (void) fgets(line, MSG_SIZ, f);
10549             if (strncmp(line, "black", strlen("black"))==0)
10550               blackPlaysFirst = TRUE;
10551         }
10552     }
10553     startedFromSetupPosition = TRUE;
10554     
10555     SendToProgram("force\n", &first);
10556     CopyBoard(boards[0], initial_position);
10557     if (blackPlaysFirst) {
10558         currentMove = forwardMostMove = backwardMostMove = 1;
10559         strcpy(moveList[0], "");
10560         strcpy(parseList[0], "");
10561         CopyBoard(boards[1], initial_position);
10562         DisplayMessage("", _("Black to play"));
10563     } else {
10564         currentMove = forwardMostMove = backwardMostMove = 0;
10565         DisplayMessage("", _("White to play"));
10566     }
10567     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10568     SendBoard(&first, forwardMostMove);
10569     if (appData.debugMode) {
10570 int i, j;
10571   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10572   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10573         fprintf(debugFP, "Load Position\n");
10574     }
10575
10576     if (positionNumber > 1) {
10577         sprintf(line, "%s %d", title, positionNumber);
10578         DisplayTitle(line);
10579     } else {
10580         DisplayTitle(title);
10581     }
10582     gameMode = EditGame;
10583     ModeHighlight();
10584     ResetClocks();
10585     timeRemaining[0][1] = whiteTimeRemaining;
10586     timeRemaining[1][1] = blackTimeRemaining;
10587     DrawPosition(FALSE, boards[currentMove]);
10588    
10589     return TRUE;
10590 }
10591
10592
10593 void
10594 CopyPlayerNameIntoFileName(dest, src)
10595      char **dest, *src;
10596 {
10597     while (*src != NULLCHAR && *src != ',') {
10598         if (*src == ' ') {
10599             *(*dest)++ = '_';
10600             src++;
10601         } else {
10602             *(*dest)++ = *src++;
10603         }
10604     }
10605 }
10606
10607 char *DefaultFileName(ext)
10608      char *ext;
10609 {
10610     static char def[MSG_SIZ];
10611     char *p;
10612
10613     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10614         p = def;
10615         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10616         *p++ = '-';
10617         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10618         *p++ = '.';
10619         strcpy(p, ext);
10620     } else {
10621         def[0] = NULLCHAR;
10622     }
10623     return def;
10624 }
10625
10626 /* Save the current game to the given file */
10627 int
10628 SaveGameToFile(filename, append)
10629      char *filename;
10630      int append;
10631 {
10632     FILE *f;
10633     char buf[MSG_SIZ];
10634
10635     if (strcmp(filename, "-") == 0) {
10636         return SaveGame(stdout, 0, NULL);
10637     } else {
10638         f = fopen(filename, append ? "a" : "w");
10639         if (f == NULL) {
10640             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10641             DisplayError(buf, errno);
10642             return FALSE;
10643         } else {
10644             return SaveGame(f, 0, NULL);
10645         }
10646     }
10647 }
10648
10649 char *
10650 SavePart(str)
10651      char *str;
10652 {
10653     static char buf[MSG_SIZ];
10654     char *p;
10655     
10656     p = strchr(str, ' ');
10657     if (p == NULL) return str;
10658     strncpy(buf, str, p - str);
10659     buf[p - str] = NULLCHAR;
10660     return buf;
10661 }
10662
10663 #define PGN_MAX_LINE 75
10664
10665 #define PGN_SIDE_WHITE  0
10666 #define PGN_SIDE_BLACK  1
10667
10668 /* [AS] */
10669 static int FindFirstMoveOutOfBook( int side )
10670 {
10671     int result = -1;
10672
10673     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10674         int index = backwardMostMove;
10675         int has_book_hit = 0;
10676
10677         if( (index % 2) != side ) {
10678             index++;
10679         }
10680
10681         while( index < forwardMostMove ) {
10682             /* Check to see if engine is in book */
10683             int depth = pvInfoList[index].depth;
10684             int score = pvInfoList[index].score;
10685             int in_book = 0;
10686
10687             if( depth <= 2 ) {
10688                 in_book = 1;
10689             }
10690             else if( score == 0 && depth == 63 ) {
10691                 in_book = 1; /* Zappa */
10692             }
10693             else if( score == 2 && depth == 99 ) {
10694                 in_book = 1; /* Abrok */
10695             }
10696
10697             has_book_hit += in_book;
10698
10699             if( ! in_book ) {
10700                 result = index;
10701
10702                 break;
10703             }
10704
10705             index += 2;
10706         }
10707     }
10708
10709     return result;
10710 }
10711
10712 /* [AS] */
10713 void GetOutOfBookInfo( char * buf )
10714 {
10715     int oob[2];
10716     int i;
10717     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10718
10719     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10720     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10721
10722     *buf = '\0';
10723
10724     if( oob[0] >= 0 || oob[1] >= 0 ) {
10725         for( i=0; i<2; i++ ) {
10726             int idx = oob[i];
10727
10728             if( idx >= 0 ) {
10729                 if( i > 0 && oob[0] >= 0 ) {
10730                     strcat( buf, "   " );
10731                 }
10732
10733                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10734                 sprintf( buf+strlen(buf), "%s%.2f", 
10735                     pvInfoList[idx].score >= 0 ? "+" : "",
10736                     pvInfoList[idx].score / 100.0 );
10737             }
10738         }
10739     }
10740 }
10741
10742 /* Save game in PGN style and close the file */
10743 int
10744 SaveGamePGN(f)
10745      FILE *f;
10746 {
10747     int i, offset, linelen, newblock;
10748     time_t tm;
10749 //    char *movetext;
10750     char numtext[32];
10751     int movelen, numlen, blank;
10752     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10753
10754     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10755     
10756     tm = time((time_t *) NULL);
10757     
10758     PrintPGNTags(f, &gameInfo);
10759     
10760     if (backwardMostMove > 0 || startedFromSetupPosition) {
10761         char *fen = PositionToFEN(backwardMostMove, NULL);
10762         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10763         fprintf(f, "\n{--------------\n");
10764         PrintPosition(f, backwardMostMove);
10765         fprintf(f, "--------------}\n");
10766         free(fen);
10767     }
10768     else {
10769         /* [AS] Out of book annotation */
10770         if( appData.saveOutOfBookInfo ) {
10771             char buf[64];
10772
10773             GetOutOfBookInfo( buf );
10774
10775             if( buf[0] != '\0' ) {
10776                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10777             }
10778         }
10779
10780         fprintf(f, "\n");
10781     }
10782
10783     i = backwardMostMove;
10784     linelen = 0;
10785     newblock = TRUE;
10786
10787     while (i < forwardMostMove) {
10788         /* Print comments preceding this move */
10789         if (commentList[i] != NULL) {
10790             if (linelen > 0) fprintf(f, "\n");
10791             fprintf(f, "%s", commentList[i]);
10792             linelen = 0;
10793             newblock = TRUE;
10794         }
10795
10796         /* Format move number */
10797         if ((i % 2) == 0) {
10798             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10799         } else {
10800             if (newblock) {
10801                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10802             } else {
10803                 numtext[0] = NULLCHAR;
10804             }
10805         }
10806         numlen = strlen(numtext);
10807         newblock = FALSE;
10808
10809         /* Print move number */
10810         blank = linelen > 0 && numlen > 0;
10811         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10812             fprintf(f, "\n");
10813             linelen = 0;
10814             blank = 0;
10815         }
10816         if (blank) {
10817             fprintf(f, " ");
10818             linelen++;
10819         }
10820         fprintf(f, "%s", numtext);
10821         linelen += numlen;
10822
10823         /* Get move */
10824         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10825         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10826
10827         /* Print move */
10828         blank = linelen > 0 && movelen > 0;
10829         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10830             fprintf(f, "\n");
10831             linelen = 0;
10832             blank = 0;
10833         }
10834         if (blank) {
10835             fprintf(f, " ");
10836             linelen++;
10837         }
10838         fprintf(f, "%s", move_buffer);
10839         linelen += movelen;
10840
10841         /* [AS] Add PV info if present */
10842         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10843             /* [HGM] add time */
10844             char buf[MSG_SIZ]; int seconds;
10845
10846             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10847
10848             if( seconds <= 0) buf[0] = 0; else
10849             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10850                 seconds = (seconds + 4)/10; // round to full seconds
10851                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10852                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10853             }
10854
10855             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10856                 pvInfoList[i].score >= 0 ? "+" : "",
10857                 pvInfoList[i].score / 100.0,
10858                 pvInfoList[i].depth,
10859                 buf );
10860
10861             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10862
10863             /* Print score/depth */
10864             blank = linelen > 0 && movelen > 0;
10865             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10866                 fprintf(f, "\n");
10867                 linelen = 0;
10868                 blank = 0;
10869             }
10870             if (blank) {
10871                 fprintf(f, " ");
10872                 linelen++;
10873             }
10874             fprintf(f, "%s", move_buffer);
10875             linelen += movelen;
10876         }
10877
10878         i++;
10879     }
10880     
10881     /* Start a new line */
10882     if (linelen > 0) fprintf(f, "\n");
10883
10884     /* Print comments after last move */
10885     if (commentList[i] != NULL) {
10886         fprintf(f, "%s\n", commentList[i]);
10887     }
10888
10889     /* Print result */
10890     if (gameInfo.resultDetails != NULL &&
10891         gameInfo.resultDetails[0] != NULLCHAR) {
10892         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10893                 PGNResult(gameInfo.result));
10894     } else {
10895         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10896     }
10897
10898     fclose(f);
10899     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10900     return TRUE;
10901 }
10902
10903 /* Save game in old style and close the file */
10904 int
10905 SaveGameOldStyle(f)
10906      FILE *f;
10907 {
10908     int i, offset;
10909     time_t tm;
10910     
10911     tm = time((time_t *) NULL);
10912     
10913     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10914     PrintOpponents(f);
10915     
10916     if (backwardMostMove > 0 || startedFromSetupPosition) {
10917         fprintf(f, "\n[--------------\n");
10918         PrintPosition(f, backwardMostMove);
10919         fprintf(f, "--------------]\n");
10920     } else {
10921         fprintf(f, "\n");
10922     }
10923
10924     i = backwardMostMove;
10925     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10926
10927     while (i < forwardMostMove) {
10928         if (commentList[i] != NULL) {
10929             fprintf(f, "[%s]\n", commentList[i]);
10930         }
10931
10932         if ((i % 2) == 1) {
10933             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10934             i++;
10935         } else {
10936             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10937             i++;
10938             if (commentList[i] != NULL) {
10939                 fprintf(f, "\n");
10940                 continue;
10941             }
10942             if (i >= forwardMostMove) {
10943                 fprintf(f, "\n");
10944                 break;
10945             }
10946             fprintf(f, "%s\n", parseList[i]);
10947             i++;
10948         }
10949     }
10950     
10951     if (commentList[i] != NULL) {
10952         fprintf(f, "[%s]\n", commentList[i]);
10953     }
10954
10955     /* This isn't really the old style, but it's close enough */
10956     if (gameInfo.resultDetails != NULL &&
10957         gameInfo.resultDetails[0] != NULLCHAR) {
10958         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10959                 gameInfo.resultDetails);
10960     } else {
10961         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10962     }
10963
10964     fclose(f);
10965     return TRUE;
10966 }
10967
10968 /* Save the current game to open file f and close the file */
10969 int
10970 SaveGame(f, dummy, dummy2)
10971      FILE *f;
10972      int dummy;
10973      char *dummy2;
10974 {
10975     if (gameMode == EditPosition) EditPositionDone(TRUE);
10976     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10977     if (appData.oldSaveStyle)
10978       return SaveGameOldStyle(f);
10979     else
10980       return SaveGamePGN(f);
10981 }
10982
10983 /* Save the current position to the given file */
10984 int
10985 SavePositionToFile(filename)
10986      char *filename;
10987 {
10988     FILE *f;
10989     char buf[MSG_SIZ];
10990
10991     if (strcmp(filename, "-") == 0) {
10992         return SavePosition(stdout, 0, NULL);
10993     } else {
10994         f = fopen(filename, "a");
10995         if (f == NULL) {
10996             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10997             DisplayError(buf, errno);
10998             return FALSE;
10999         } else {
11000             SavePosition(f, 0, NULL);
11001             return TRUE;
11002         }
11003     }
11004 }
11005
11006 /* Save the current position to the given open file and close the file */
11007 int
11008 SavePosition(f, dummy, dummy2)
11009      FILE *f;
11010      int dummy;
11011      char *dummy2;
11012 {
11013     time_t tm;
11014     char *fen;
11015     
11016     if (gameMode == EditPosition) EditPositionDone(TRUE);
11017     if (appData.oldSaveStyle) {
11018         tm = time((time_t *) NULL);
11019     
11020         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11021         PrintOpponents(f);
11022         fprintf(f, "[--------------\n");
11023         PrintPosition(f, currentMove);
11024         fprintf(f, "--------------]\n");
11025     } else {
11026         fen = PositionToFEN(currentMove, NULL);
11027         fprintf(f, "%s\n", fen);
11028         free(fen);
11029     }
11030     fclose(f);
11031     return TRUE;
11032 }
11033
11034 void
11035 ReloadCmailMsgEvent(unregister)
11036      int unregister;
11037 {
11038 #if !WIN32
11039     static char *inFilename = NULL;
11040     static char *outFilename;
11041     int i;
11042     struct stat inbuf, outbuf;
11043     int status;
11044     
11045     /* Any registered moves are unregistered if unregister is set, */
11046     /* i.e. invoked by the signal handler */
11047     if (unregister) {
11048         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11049             cmailMoveRegistered[i] = FALSE;
11050             if (cmailCommentList[i] != NULL) {
11051                 free(cmailCommentList[i]);
11052                 cmailCommentList[i] = NULL;
11053             }
11054         }
11055         nCmailMovesRegistered = 0;
11056     }
11057
11058     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11059         cmailResult[i] = CMAIL_NOT_RESULT;
11060     }
11061     nCmailResults = 0;
11062
11063     if (inFilename == NULL) {
11064         /* Because the filenames are static they only get malloced once  */
11065         /* and they never get freed                                      */
11066         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11067         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11068
11069         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11070         sprintf(outFilename, "%s.out", appData.cmailGameName);
11071     }
11072     
11073     status = stat(outFilename, &outbuf);
11074     if (status < 0) {
11075         cmailMailedMove = FALSE;
11076     } else {
11077         status = stat(inFilename, &inbuf);
11078         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11079     }
11080     
11081     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11082        counts the games, notes how each one terminated, etc.
11083        
11084        It would be nice to remove this kludge and instead gather all
11085        the information while building the game list.  (And to keep it
11086        in the game list nodes instead of having a bunch of fixed-size
11087        parallel arrays.)  Note this will require getting each game's
11088        termination from the PGN tags, as the game list builder does
11089        not process the game moves.  --mann
11090        */
11091     cmailMsgLoaded = TRUE;
11092     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11093     
11094     /* Load first game in the file or popup game menu */
11095     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11096
11097 #endif /* !WIN32 */
11098     return;
11099 }
11100
11101 int
11102 RegisterMove()
11103 {
11104     FILE *f;
11105     char string[MSG_SIZ];
11106
11107     if (   cmailMailedMove
11108         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11109         return TRUE;            /* Allow free viewing  */
11110     }
11111
11112     /* Unregister move to ensure that we don't leave RegisterMove        */
11113     /* with the move registered when the conditions for registering no   */
11114     /* longer hold                                                       */
11115     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11116         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11117         nCmailMovesRegistered --;
11118
11119         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11120           {
11121               free(cmailCommentList[lastLoadGameNumber - 1]);
11122               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11123           }
11124     }
11125
11126     if (cmailOldMove == -1) {
11127         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11128         return FALSE;
11129     }
11130
11131     if (currentMove > cmailOldMove + 1) {
11132         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11133         return FALSE;
11134     }
11135
11136     if (currentMove < cmailOldMove) {
11137         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11138         return FALSE;
11139     }
11140
11141     if (forwardMostMove > currentMove) {
11142         /* Silently truncate extra moves */
11143         TruncateGame();
11144     }
11145
11146     if (   (currentMove == cmailOldMove + 1)
11147         || (   (currentMove == cmailOldMove)
11148             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11149                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11150         if (gameInfo.result != GameUnfinished) {
11151             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11152         }
11153
11154         if (commentList[currentMove] != NULL) {
11155             cmailCommentList[lastLoadGameNumber - 1]
11156               = StrSave(commentList[currentMove]);
11157         }
11158         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11159
11160         if (appData.debugMode)
11161           fprintf(debugFP, "Saving %s for game %d\n",
11162                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11163
11164         sprintf(string,
11165                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11166         
11167         f = fopen(string, "w");
11168         if (appData.oldSaveStyle) {
11169             SaveGameOldStyle(f); /* also closes the file */
11170             
11171             sprintf(string, "%s.pos.out", appData.cmailGameName);
11172             f = fopen(string, "w");
11173             SavePosition(f, 0, NULL); /* also closes the file */
11174         } else {
11175             fprintf(f, "{--------------\n");
11176             PrintPosition(f, currentMove);
11177             fprintf(f, "--------------}\n\n");
11178             
11179             SaveGame(f, 0, NULL); /* also closes the file*/
11180         }
11181         
11182         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11183         nCmailMovesRegistered ++;
11184     } else if (nCmailGames == 1) {
11185         DisplayError(_("You have not made a move yet"), 0);
11186         return FALSE;
11187     }
11188
11189     return TRUE;
11190 }
11191
11192 void
11193 MailMoveEvent()
11194 {
11195 #if !WIN32
11196     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11197     FILE *commandOutput;
11198     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11199     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11200     int nBuffers;
11201     int i;
11202     int archived;
11203     char *arcDir;
11204
11205     if (! cmailMsgLoaded) {
11206         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11207         return;
11208     }
11209
11210     if (nCmailGames == nCmailResults) {
11211         DisplayError(_("No unfinished games"), 0);
11212         return;
11213     }
11214
11215 #if CMAIL_PROHIBIT_REMAIL
11216     if (cmailMailedMove) {
11217         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);
11218         DisplayError(msg, 0);
11219         return;
11220     }
11221 #endif
11222
11223     if (! (cmailMailedMove || RegisterMove())) return;
11224     
11225     if (   cmailMailedMove
11226         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11227         sprintf(string, partCommandString,
11228                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11229         commandOutput = popen(string, "r");
11230
11231         if (commandOutput == NULL) {
11232             DisplayError(_("Failed to invoke cmail"), 0);
11233         } else {
11234             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11235                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11236             }
11237             if (nBuffers > 1) {
11238                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11239                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11240                 nBytes = MSG_SIZ - 1;
11241             } else {
11242                 (void) memcpy(msg, buffer, nBytes);
11243             }
11244             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11245
11246             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11247                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11248
11249                 archived = TRUE;
11250                 for (i = 0; i < nCmailGames; i ++) {
11251                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11252                         archived = FALSE;
11253                     }
11254                 }
11255                 if (   archived
11256                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11257                         != NULL)) {
11258                     sprintf(buffer, "%s/%s.%s.archive",
11259                             arcDir,
11260                             appData.cmailGameName,
11261                             gameInfo.date);
11262                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11263                     cmailMsgLoaded = FALSE;
11264                 }
11265             }
11266
11267             DisplayInformation(msg);
11268             pclose(commandOutput);
11269         }
11270     } else {
11271         if ((*cmailMsg) != '\0') {
11272             DisplayInformation(cmailMsg);
11273         }
11274     }
11275
11276     return;
11277 #endif /* !WIN32 */
11278 }
11279
11280 char *
11281 CmailMsg()
11282 {
11283 #if WIN32
11284     return NULL;
11285 #else
11286     int  prependComma = 0;
11287     char number[5];
11288     char string[MSG_SIZ];       /* Space for game-list */
11289     int  i;
11290     
11291     if (!cmailMsgLoaded) return "";
11292
11293     if (cmailMailedMove) {
11294         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11295     } else {
11296         /* Create a list of games left */
11297         sprintf(string, "[");
11298         for (i = 0; i < nCmailGames; i ++) {
11299             if (! (   cmailMoveRegistered[i]
11300                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11301                 if (prependComma) {
11302                     sprintf(number, ",%d", i + 1);
11303                 } else {
11304                     sprintf(number, "%d", i + 1);
11305                     prependComma = 1;
11306                 }
11307                 
11308                 strcat(string, number);
11309             }
11310         }
11311         strcat(string, "]");
11312
11313         if (nCmailMovesRegistered + nCmailResults == 0) {
11314             switch (nCmailGames) {
11315               case 1:
11316                 sprintf(cmailMsg,
11317                         _("Still need to make move for game\n"));
11318                 break;
11319                 
11320               case 2:
11321                 sprintf(cmailMsg,
11322                         _("Still need to make moves for both games\n"));
11323                 break;
11324                 
11325               default:
11326                 sprintf(cmailMsg,
11327                         _("Still need to make moves for all %d games\n"),
11328                         nCmailGames);
11329                 break;
11330             }
11331         } else {
11332             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11333               case 1:
11334                 sprintf(cmailMsg,
11335                         _("Still need to make a move for game %s\n"),
11336                         string);
11337                 break;
11338                 
11339               case 0:
11340                 if (nCmailResults == nCmailGames) {
11341                     sprintf(cmailMsg, _("No unfinished games\n"));
11342                 } else {
11343                     sprintf(cmailMsg, _("Ready to send mail\n"));
11344                 }
11345                 break;
11346                 
11347               default:
11348                 sprintf(cmailMsg,
11349                         _("Still need to make moves for games %s\n"),
11350                         string);
11351             }
11352         }
11353     }
11354     return cmailMsg;
11355 #endif /* WIN32 */
11356 }
11357
11358 void
11359 ResetGameEvent()
11360 {
11361     if (gameMode == Training)
11362       SetTrainingModeOff();
11363
11364     Reset(TRUE, TRUE);
11365     cmailMsgLoaded = FALSE;
11366     if (appData.icsActive) {
11367       SendToICS(ics_prefix);
11368       SendToICS("refresh\n");
11369     }
11370 }
11371
11372 void
11373 ExitEvent(status)
11374      int status;
11375 {
11376     exiting++;
11377     if (exiting > 2) {
11378       /* Give up on clean exit */
11379       exit(status);
11380     }
11381     if (exiting > 1) {
11382       /* Keep trying for clean exit */
11383       return;
11384     }
11385
11386     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11387
11388     if (telnetISR != NULL) {
11389       RemoveInputSource(telnetISR);
11390     }
11391     if (icsPR != NoProc) {
11392       DestroyChildProcess(icsPR, TRUE);
11393     }
11394
11395     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11396     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11397
11398     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11399     /* make sure this other one finishes before killing it!                  */
11400     if(endingGame) { int count = 0;
11401         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11402         while(endingGame && count++ < 10) DoSleep(1);
11403         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11404     }
11405
11406     /* Kill off chess programs */
11407     if (first.pr != NoProc) {
11408         ExitAnalyzeMode();
11409         
11410         DoSleep( appData.delayBeforeQuit );
11411         SendToProgram("quit\n", &first);
11412         DoSleep( appData.delayAfterQuit );
11413         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11414     }
11415     if (second.pr != NoProc) {
11416         DoSleep( appData.delayBeforeQuit );
11417         SendToProgram("quit\n", &second);
11418         DoSleep( appData.delayAfterQuit );
11419         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11420     }
11421     if (first.isr != NULL) {
11422         RemoveInputSource(first.isr);
11423     }
11424     if (second.isr != NULL) {
11425         RemoveInputSource(second.isr);
11426     }
11427
11428     ShutDownFrontEnd();
11429     exit(status);
11430 }
11431
11432 void
11433 PauseEvent()
11434 {
11435     if (appData.debugMode)
11436         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11437     if (pausing) {
11438         pausing = FALSE;
11439         ModeHighlight();
11440         if (gameMode == MachinePlaysWhite ||
11441             gameMode == MachinePlaysBlack) {
11442             StartClocks();
11443         } else {
11444             DisplayBothClocks();
11445         }
11446         if (gameMode == PlayFromGameFile) {
11447             if (appData.timeDelay >= 0) 
11448                 AutoPlayGameLoop();
11449         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11450             Reset(FALSE, TRUE);
11451             SendToICS(ics_prefix);
11452             SendToICS("refresh\n");
11453         } else if (currentMove < forwardMostMove) {
11454             ForwardInner(forwardMostMove);
11455         }
11456         pauseExamInvalid = FALSE;
11457     } else {
11458         switch (gameMode) {
11459           default:
11460             return;
11461           case IcsExamining:
11462             pauseExamForwardMostMove = forwardMostMove;
11463             pauseExamInvalid = FALSE;
11464             /* fall through */
11465           case IcsObserving:
11466           case IcsPlayingWhite:
11467           case IcsPlayingBlack:
11468             pausing = TRUE;
11469             ModeHighlight();
11470             return;
11471           case PlayFromGameFile:
11472             (void) StopLoadGameTimer();
11473             pausing = TRUE;
11474             ModeHighlight();
11475             break;
11476           case BeginningOfGame:
11477             if (appData.icsActive) return;
11478             /* else fall through */
11479           case MachinePlaysWhite:
11480           case MachinePlaysBlack:
11481           case TwoMachinesPlay:
11482             if (forwardMostMove == 0)
11483               return;           /* don't pause if no one has moved */
11484             if ((gameMode == MachinePlaysWhite &&
11485                  !WhiteOnMove(forwardMostMove)) ||
11486                 (gameMode == MachinePlaysBlack &&
11487                  WhiteOnMove(forwardMostMove))) {
11488                 StopClocks();
11489             }
11490             pausing = TRUE;
11491             ModeHighlight();
11492             break;
11493         }
11494     }
11495 }
11496
11497 void
11498 EditCommentEvent()
11499 {
11500     char title[MSG_SIZ];
11501
11502     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11503         strcpy(title, _("Edit comment"));
11504     } else {
11505         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11506                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11507                 parseList[currentMove - 1]);
11508     }
11509
11510     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11511 }
11512
11513
11514 void
11515 EditTagsEvent()
11516 {
11517     char *tags = PGNTags(&gameInfo);
11518     EditTagsPopUp(tags);
11519     free(tags);
11520 }
11521
11522 void
11523 AnalyzeModeEvent()
11524 {
11525     if (appData.noChessProgram || gameMode == AnalyzeMode)
11526       return;
11527
11528     if (gameMode != AnalyzeFile) {
11529         if (!appData.icsEngineAnalyze) {
11530                EditGameEvent();
11531                if (gameMode != EditGame) return;
11532         }
11533         ResurrectChessProgram();
11534         SendToProgram("analyze\n", &first);
11535         first.analyzing = TRUE;
11536         /*first.maybeThinking = TRUE;*/
11537         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11538         EngineOutputPopUp();
11539     }
11540     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11541     pausing = FALSE;
11542     ModeHighlight();
11543     SetGameInfo();
11544
11545     StartAnalysisClock();
11546     GetTimeMark(&lastNodeCountTime);
11547     lastNodeCount = 0;
11548 }
11549
11550 void
11551 AnalyzeFileEvent()
11552 {
11553     if (appData.noChessProgram || gameMode == AnalyzeFile)
11554       return;
11555
11556     if (gameMode != AnalyzeMode) {
11557         EditGameEvent();
11558         if (gameMode != EditGame) return;
11559         ResurrectChessProgram();
11560         SendToProgram("analyze\n", &first);
11561         first.analyzing = TRUE;
11562         /*first.maybeThinking = TRUE;*/
11563         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11564         EngineOutputPopUp();
11565     }
11566     gameMode = AnalyzeFile;
11567     pausing = FALSE;
11568     ModeHighlight();
11569     SetGameInfo();
11570
11571     StartAnalysisClock();
11572     GetTimeMark(&lastNodeCountTime);
11573     lastNodeCount = 0;
11574 }
11575
11576 void
11577 MachineWhiteEvent()
11578 {
11579     char buf[MSG_SIZ];
11580     char *bookHit = NULL;
11581
11582     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11583       return;
11584
11585
11586     if (gameMode == PlayFromGameFile || 
11587         gameMode == TwoMachinesPlay  || 
11588         gameMode == Training         || 
11589         gameMode == AnalyzeMode      || 
11590         gameMode == EndOfGame)
11591         EditGameEvent();
11592
11593     if (gameMode == EditPosition) 
11594         EditPositionDone(TRUE);
11595
11596     if (!WhiteOnMove(currentMove)) {
11597         DisplayError(_("It is not White's turn"), 0);
11598         return;
11599     }
11600   
11601     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11602       ExitAnalyzeMode();
11603
11604     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11605         gameMode == AnalyzeFile)
11606         TruncateGame();
11607
11608     ResurrectChessProgram();    /* in case it isn't running */
11609     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11610         gameMode = MachinePlaysWhite;
11611         ResetClocks();
11612     } else
11613     gameMode = MachinePlaysWhite;
11614     pausing = FALSE;
11615     ModeHighlight();
11616     SetGameInfo();
11617     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11618     DisplayTitle(buf);
11619     if (first.sendName) {
11620       sprintf(buf, "name %s\n", gameInfo.black);
11621       SendToProgram(buf, &first);
11622     }
11623     if (first.sendTime) {
11624       if (first.useColors) {
11625         SendToProgram("black\n", &first); /*gnu kludge*/
11626       }
11627       SendTimeRemaining(&first, TRUE);
11628     }
11629     if (first.useColors) {
11630       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11631     }
11632     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11633     SetMachineThinkingEnables();
11634     first.maybeThinking = TRUE;
11635     StartClocks();
11636     firstMove = FALSE;
11637
11638     if (appData.autoFlipView && !flipView) {
11639       flipView = !flipView;
11640       DrawPosition(FALSE, NULL);
11641       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11642     }
11643
11644     if(bookHit) { // [HGM] book: simulate book reply
11645         static char bookMove[MSG_SIZ]; // a bit generous?
11646
11647         programStats.nodes = programStats.depth = programStats.time = 
11648         programStats.score = programStats.got_only_move = 0;
11649         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11650
11651         strcpy(bookMove, "move ");
11652         strcat(bookMove, bookHit);
11653         HandleMachineMove(bookMove, &first);
11654     }
11655 }
11656
11657 void
11658 MachineBlackEvent()
11659 {
11660     char buf[MSG_SIZ];
11661    char *bookHit = NULL;
11662
11663     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11664         return;
11665
11666
11667     if (gameMode == PlayFromGameFile || 
11668         gameMode == TwoMachinesPlay  || 
11669         gameMode == Training         || 
11670         gameMode == AnalyzeMode      || 
11671         gameMode == EndOfGame)
11672         EditGameEvent();
11673
11674     if (gameMode == EditPosition) 
11675         EditPositionDone(TRUE);
11676
11677     if (WhiteOnMove(currentMove)) {
11678         DisplayError(_("It is not Black's turn"), 0);
11679         return;
11680     }
11681     
11682     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11683       ExitAnalyzeMode();
11684
11685     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11686         gameMode == AnalyzeFile)
11687         TruncateGame();
11688
11689     ResurrectChessProgram();    /* in case it isn't running */
11690     gameMode = MachinePlaysBlack;
11691     pausing = FALSE;
11692     ModeHighlight();
11693     SetGameInfo();
11694     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11695     DisplayTitle(buf);
11696     if (first.sendName) {
11697       sprintf(buf, "name %s\n", gameInfo.white);
11698       SendToProgram(buf, &first);
11699     }
11700     if (first.sendTime) {
11701       if (first.useColors) {
11702         SendToProgram("white\n", &first); /*gnu kludge*/
11703       }
11704       SendTimeRemaining(&first, FALSE);
11705     }
11706     if (first.useColors) {
11707       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11708     }
11709     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11710     SetMachineThinkingEnables();
11711     first.maybeThinking = TRUE;
11712     StartClocks();
11713
11714     if (appData.autoFlipView && flipView) {
11715       flipView = !flipView;
11716       DrawPosition(FALSE, NULL);
11717       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11718     }
11719     if(bookHit) { // [HGM] book: simulate book reply
11720         static char bookMove[MSG_SIZ]; // a bit generous?
11721
11722         programStats.nodes = programStats.depth = programStats.time = 
11723         programStats.score = programStats.got_only_move = 0;
11724         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11725
11726         strcpy(bookMove, "move ");
11727         strcat(bookMove, bookHit);
11728         HandleMachineMove(bookMove, &first);
11729     }
11730 }
11731
11732
11733 void
11734 DisplayTwoMachinesTitle()
11735 {
11736     char buf[MSG_SIZ];
11737     if (appData.matchGames > 0) {
11738         if (first.twoMachinesColor[0] == 'w') {
11739             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11740                     gameInfo.white, gameInfo.black,
11741                     first.matchWins, second.matchWins,
11742                     matchGame - 1 - (first.matchWins + second.matchWins));
11743         } else {
11744             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11745                     gameInfo.white, gameInfo.black,
11746                     second.matchWins, first.matchWins,
11747                     matchGame - 1 - (first.matchWins + second.matchWins));
11748         }
11749     } else {
11750         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11751     }
11752     DisplayTitle(buf);
11753 }
11754
11755 void
11756 TwoMachinesEvent P((void))
11757 {
11758     int i;
11759     char buf[MSG_SIZ];
11760     ChessProgramState *onmove;
11761     char *bookHit = NULL;
11762     
11763     if (appData.noChessProgram) return;
11764
11765     switch (gameMode) {
11766       case TwoMachinesPlay:
11767         return;
11768       case MachinePlaysWhite:
11769       case MachinePlaysBlack:
11770         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11771             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11772             return;
11773         }
11774         /* fall through */
11775       case BeginningOfGame:
11776       case PlayFromGameFile:
11777       case EndOfGame:
11778         EditGameEvent();
11779         if (gameMode != EditGame) return;
11780         break;
11781       case EditPosition:
11782         EditPositionDone(TRUE);
11783         break;
11784       case AnalyzeMode:
11785       case AnalyzeFile:
11786         ExitAnalyzeMode();
11787         break;
11788       case EditGame:
11789       default:
11790         break;
11791     }
11792
11793 //    forwardMostMove = currentMove;
11794     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11795     ResurrectChessProgram();    /* in case first program isn't running */
11796
11797     if (second.pr == NULL) {
11798         StartChessProgram(&second);
11799         if (second.protocolVersion == 1) {
11800           TwoMachinesEventIfReady();
11801         } else {
11802           /* kludge: allow timeout for initial "feature" command */
11803           FreezeUI();
11804           DisplayMessage("", _("Starting second chess program"));
11805           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11806         }
11807         return;
11808     }
11809     DisplayMessage("", "");
11810     InitChessProgram(&second, FALSE);
11811     SendToProgram("force\n", &second);
11812     if (startedFromSetupPosition) {
11813         SendBoard(&second, backwardMostMove);
11814     if (appData.debugMode) {
11815         fprintf(debugFP, "Two Machines\n");
11816     }
11817     }
11818     for (i = backwardMostMove; i < forwardMostMove; i++) {
11819         SendMoveToProgram(i, &second);
11820     }
11821
11822     gameMode = TwoMachinesPlay;
11823     pausing = FALSE;
11824     ModeHighlight();
11825     SetGameInfo();
11826     DisplayTwoMachinesTitle();
11827     firstMove = TRUE;
11828     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11829         onmove = &first;
11830     } else {
11831         onmove = &second;
11832     }
11833
11834     SendToProgram(first.computerString, &first);
11835     if (first.sendName) {
11836       sprintf(buf, "name %s\n", second.tidy);
11837       SendToProgram(buf, &first);
11838     }
11839     SendToProgram(second.computerString, &second);
11840     if (second.sendName) {
11841       sprintf(buf, "name %s\n", first.tidy);
11842       SendToProgram(buf, &second);
11843     }
11844
11845     ResetClocks();
11846     if (!first.sendTime || !second.sendTime) {
11847         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11848         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11849     }
11850     if (onmove->sendTime) {
11851       if (onmove->useColors) {
11852         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11853       }
11854       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11855     }
11856     if (onmove->useColors) {
11857       SendToProgram(onmove->twoMachinesColor, onmove);
11858     }
11859     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11860 //    SendToProgram("go\n", onmove);
11861     onmove->maybeThinking = TRUE;
11862     SetMachineThinkingEnables();
11863
11864     StartClocks();
11865
11866     if(bookHit) { // [HGM] book: simulate book reply
11867         static char bookMove[MSG_SIZ]; // a bit generous?
11868
11869         programStats.nodes = programStats.depth = programStats.time = 
11870         programStats.score = programStats.got_only_move = 0;
11871         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11872
11873         strcpy(bookMove, "move ");
11874         strcat(bookMove, bookHit);
11875         savedMessage = bookMove; // args for deferred call
11876         savedState = onmove;
11877         ScheduleDelayedEvent(DeferredBookMove, 1);
11878     }
11879 }
11880
11881 void
11882 TrainingEvent()
11883 {
11884     if (gameMode == Training) {
11885       SetTrainingModeOff();
11886       gameMode = PlayFromGameFile;
11887       DisplayMessage("", _("Training mode off"));
11888     } else {
11889       gameMode = Training;
11890       animateTraining = appData.animate;
11891
11892       /* make sure we are not already at the end of the game */
11893       if (currentMove < forwardMostMove) {
11894         SetTrainingModeOn();
11895         DisplayMessage("", _("Training mode on"));
11896       } else {
11897         gameMode = PlayFromGameFile;
11898         DisplayError(_("Already at end of game"), 0);
11899       }
11900     }
11901     ModeHighlight();
11902 }
11903
11904 void
11905 IcsClientEvent()
11906 {
11907     if (!appData.icsActive) return;
11908     switch (gameMode) {
11909       case IcsPlayingWhite:
11910       case IcsPlayingBlack:
11911       case IcsObserving:
11912       case IcsIdle:
11913       case BeginningOfGame:
11914       case IcsExamining:
11915         return;
11916
11917       case EditGame:
11918         break;
11919
11920       case EditPosition:
11921         EditPositionDone(TRUE);
11922         break;
11923
11924       case AnalyzeMode:
11925       case AnalyzeFile:
11926         ExitAnalyzeMode();
11927         break;
11928         
11929       default:
11930         EditGameEvent();
11931         break;
11932     }
11933
11934     gameMode = IcsIdle;
11935     ModeHighlight();
11936     return;
11937 }
11938
11939
11940 void
11941 EditGameEvent()
11942 {
11943     int i;
11944
11945     switch (gameMode) {
11946       case Training:
11947         SetTrainingModeOff();
11948         break;
11949       case MachinePlaysWhite:
11950       case MachinePlaysBlack:
11951       case BeginningOfGame:
11952         SendToProgram("force\n", &first);
11953         SetUserThinkingEnables();
11954         break;
11955       case PlayFromGameFile:
11956         (void) StopLoadGameTimer();
11957         if (gameFileFP != NULL) {
11958             gameFileFP = NULL;
11959         }
11960         break;
11961       case EditPosition:
11962         EditPositionDone(TRUE);
11963         break;
11964       case AnalyzeMode:
11965       case AnalyzeFile:
11966         ExitAnalyzeMode();
11967         SendToProgram("force\n", &first);
11968         break;
11969       case TwoMachinesPlay:
11970         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11971         ResurrectChessProgram();
11972         SetUserThinkingEnables();
11973         break;
11974       case EndOfGame:
11975         ResurrectChessProgram();
11976         break;
11977       case IcsPlayingBlack:
11978       case IcsPlayingWhite:
11979         DisplayError(_("Warning: You are still playing a game"), 0);
11980         break;
11981       case IcsObserving:
11982         DisplayError(_("Warning: You are still observing a game"), 0);
11983         break;
11984       case IcsExamining:
11985         DisplayError(_("Warning: You are still examining a game"), 0);
11986         break;
11987       case IcsIdle:
11988         break;
11989       case EditGame:
11990       default:
11991         return;
11992     }
11993     
11994     pausing = FALSE;
11995     StopClocks();
11996     first.offeredDraw = second.offeredDraw = 0;
11997
11998     if (gameMode == PlayFromGameFile) {
11999         whiteTimeRemaining = timeRemaining[0][currentMove];
12000         blackTimeRemaining = timeRemaining[1][currentMove];
12001         DisplayTitle("");
12002     }
12003
12004     if (gameMode == MachinePlaysWhite ||
12005         gameMode == MachinePlaysBlack ||
12006         gameMode == TwoMachinesPlay ||
12007         gameMode == EndOfGame) {
12008         i = forwardMostMove;
12009         while (i > currentMove) {
12010             SendToProgram("undo\n", &first);
12011             i--;
12012         }
12013         whiteTimeRemaining = timeRemaining[0][currentMove];
12014         blackTimeRemaining = timeRemaining[1][currentMove];
12015         DisplayBothClocks();
12016         if (whiteFlag || blackFlag) {
12017             whiteFlag = blackFlag = 0;
12018         }
12019         DisplayTitle("");
12020     }           
12021     
12022     gameMode = EditGame;
12023     ModeHighlight();
12024     SetGameInfo();
12025 }
12026
12027
12028 void
12029 EditPositionEvent()
12030 {
12031     if (gameMode == EditPosition) {
12032         EditGameEvent();
12033         return;
12034     }
12035     
12036     EditGameEvent();
12037     if (gameMode != EditGame) return;
12038     
12039     gameMode = EditPosition;
12040     ModeHighlight();
12041     SetGameInfo();
12042     if (currentMove > 0)
12043       CopyBoard(boards[0], boards[currentMove]);
12044     
12045     blackPlaysFirst = !WhiteOnMove(currentMove);
12046     ResetClocks();
12047     currentMove = forwardMostMove = backwardMostMove = 0;
12048     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12049     DisplayMove(-1);
12050 }
12051
12052 void
12053 ExitAnalyzeMode()
12054 {
12055     /* [DM] icsEngineAnalyze - possible call from other functions */
12056     if (appData.icsEngineAnalyze) {
12057         appData.icsEngineAnalyze = FALSE;
12058
12059         DisplayMessage("",_("Close ICS engine analyze..."));
12060     }
12061     if (first.analysisSupport && first.analyzing) {
12062       SendToProgram("exit\n", &first);
12063       first.analyzing = FALSE;
12064     }
12065     thinkOutput[0] = NULLCHAR;
12066 }
12067
12068 void
12069 EditPositionDone(Boolean fakeRights)
12070 {
12071     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12072
12073     startedFromSetupPosition = TRUE;
12074     InitChessProgram(&first, FALSE);
12075     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12076       boards[0][EP_STATUS] = EP_NONE;
12077       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12078     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12079         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12080         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12081       } else boards[0][CASTLING][2] = NoRights;
12082     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12083         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12084         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12085       } else boards[0][CASTLING][5] = NoRights;
12086     }
12087     SendToProgram("force\n", &first);
12088     if (blackPlaysFirst) {
12089         strcpy(moveList[0], "");
12090         strcpy(parseList[0], "");
12091         currentMove = forwardMostMove = backwardMostMove = 1;
12092         CopyBoard(boards[1], boards[0]);
12093     } else {
12094         currentMove = forwardMostMove = backwardMostMove = 0;
12095     }
12096     SendBoard(&first, forwardMostMove);
12097     if (appData.debugMode) {
12098         fprintf(debugFP, "EditPosDone\n");
12099     }
12100     DisplayTitle("");
12101     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12102     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12103     gameMode = EditGame;
12104     ModeHighlight();
12105     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12106     ClearHighlights(); /* [AS] */
12107 }
12108
12109 /* Pause for `ms' milliseconds */
12110 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12111 void
12112 TimeDelay(ms)
12113      long ms;
12114 {
12115     TimeMark m1, m2;
12116
12117     GetTimeMark(&m1);
12118     do {
12119         GetTimeMark(&m2);
12120     } while (SubtractTimeMarks(&m2, &m1) < ms);
12121 }
12122
12123 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12124 void
12125 SendMultiLineToICS(buf)
12126      char *buf;
12127 {
12128     char temp[MSG_SIZ+1], *p;
12129     int len;
12130
12131     len = strlen(buf);
12132     if (len > MSG_SIZ)
12133       len = MSG_SIZ;
12134   
12135     strncpy(temp, buf, len);
12136     temp[len] = 0;
12137
12138     p = temp;
12139     while (*p) {
12140         if (*p == '\n' || *p == '\r')
12141           *p = ' ';
12142         ++p;
12143     }
12144
12145     strcat(temp, "\n");
12146     SendToICS(temp);
12147     SendToPlayer(temp, strlen(temp));
12148 }
12149
12150 void
12151 SetWhiteToPlayEvent()
12152 {
12153     if (gameMode == EditPosition) {
12154         blackPlaysFirst = FALSE;
12155         DisplayBothClocks();    /* works because currentMove is 0 */
12156     } else if (gameMode == IcsExamining) {
12157         SendToICS(ics_prefix);
12158         SendToICS("tomove white\n");
12159     }
12160 }
12161
12162 void
12163 SetBlackToPlayEvent()
12164 {
12165     if (gameMode == EditPosition) {
12166         blackPlaysFirst = TRUE;
12167         currentMove = 1;        /* kludge */
12168         DisplayBothClocks();
12169         currentMove = 0;
12170     } else if (gameMode == IcsExamining) {
12171         SendToICS(ics_prefix);
12172         SendToICS("tomove black\n");
12173     }
12174 }
12175
12176 void
12177 EditPositionMenuEvent(selection, x, y)
12178      ChessSquare selection;
12179      int x, y;
12180 {
12181     char buf[MSG_SIZ];
12182     ChessSquare piece = boards[0][y][x];
12183
12184     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12185
12186     switch (selection) {
12187       case ClearBoard:
12188         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12189             SendToICS(ics_prefix);
12190             SendToICS("bsetup clear\n");
12191         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12192             SendToICS(ics_prefix);
12193             SendToICS("clearboard\n");
12194         } else {
12195             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12196                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12197                 for (y = 0; y < BOARD_HEIGHT; y++) {
12198                     if (gameMode == IcsExamining) {
12199                         if (boards[currentMove][y][x] != EmptySquare) {
12200                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12201                                     AAA + x, ONE + y);
12202                             SendToICS(buf);
12203                         }
12204                     } else {
12205                         boards[0][y][x] = p;
12206                     }
12207                 }
12208             }
12209         }
12210         if (gameMode == EditPosition) {
12211             DrawPosition(FALSE, boards[0]);
12212         }
12213         break;
12214
12215       case WhitePlay:
12216         SetWhiteToPlayEvent();
12217         break;
12218
12219       case BlackPlay:
12220         SetBlackToPlayEvent();
12221         break;
12222
12223       case EmptySquare:
12224         if (gameMode == IcsExamining) {
12225             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12226             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12227             SendToICS(buf);
12228         } else {
12229             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12230                 if(x == BOARD_LEFT-2) {
12231                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12232                     boards[0][y][1] = 0;
12233                 } else
12234                 if(x == BOARD_RGHT+1) {
12235                     if(y >= gameInfo.holdingsSize) break;
12236                     boards[0][y][BOARD_WIDTH-2] = 0;
12237                 } else break;
12238             }
12239             boards[0][y][x] = EmptySquare;
12240             DrawPosition(FALSE, boards[0]);
12241         }
12242         break;
12243
12244       case PromotePiece:
12245         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12246            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12247             selection = (ChessSquare) (PROMOTED piece);
12248         } else if(piece == EmptySquare) selection = WhiteSilver;
12249         else selection = (ChessSquare)((int)piece - 1);
12250         goto defaultlabel;
12251
12252       case DemotePiece:
12253         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12254            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12255             selection = (ChessSquare) (DEMOTED piece);
12256         } else if(piece == EmptySquare) selection = BlackSilver;
12257         else selection = (ChessSquare)((int)piece + 1);       
12258         goto defaultlabel;
12259
12260       case WhiteQueen:
12261       case BlackQueen:
12262         if(gameInfo.variant == VariantShatranj ||
12263            gameInfo.variant == VariantXiangqi  ||
12264            gameInfo.variant == VariantCourier  ||
12265            gameInfo.variant == VariantMakruk     )
12266             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12267         goto defaultlabel;
12268
12269       case WhiteKing:
12270       case BlackKing:
12271         if(gameInfo.variant == VariantXiangqi)
12272             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12273         if(gameInfo.variant == VariantKnightmate)
12274             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12275       default:
12276         defaultlabel:
12277         if (gameMode == IcsExamining) {
12278             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12279             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12280                     PieceToChar(selection), AAA + x, ONE + y);
12281             SendToICS(buf);
12282         } else {
12283             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12284                 int n;
12285                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12286                     n = PieceToNumber(selection - BlackPawn);
12287                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12288                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12289                     boards[0][BOARD_HEIGHT-1-n][1]++;
12290                 } else
12291                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12292                     n = PieceToNumber(selection);
12293                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12294                     boards[0][n][BOARD_WIDTH-1] = selection;
12295                     boards[0][n][BOARD_WIDTH-2]++;
12296                 }
12297             } else
12298             boards[0][y][x] = selection;
12299             DrawPosition(TRUE, boards[0]);
12300         }
12301         break;
12302     }
12303 }
12304
12305
12306 void
12307 DropMenuEvent(selection, x, y)
12308      ChessSquare selection;
12309      int x, y;
12310 {
12311     ChessMove moveType;
12312
12313     switch (gameMode) {
12314       case IcsPlayingWhite:
12315       case MachinePlaysBlack:
12316         if (!WhiteOnMove(currentMove)) {
12317             DisplayMoveError(_("It is Black's turn"));
12318             return;
12319         }
12320         moveType = WhiteDrop;
12321         break;
12322       case IcsPlayingBlack:
12323       case MachinePlaysWhite:
12324         if (WhiteOnMove(currentMove)) {
12325             DisplayMoveError(_("It is White's turn"));
12326             return;
12327         }
12328         moveType = BlackDrop;
12329         break;
12330       case EditGame:
12331         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12332         break;
12333       default:
12334         return;
12335     }
12336
12337     if (moveType == BlackDrop && selection < BlackPawn) {
12338       selection = (ChessSquare) ((int) selection
12339                                  + (int) BlackPawn - (int) WhitePawn);
12340     }
12341     if (boards[currentMove][y][x] != EmptySquare) {
12342         DisplayMoveError(_("That square is occupied"));
12343         return;
12344     }
12345
12346     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12347 }
12348
12349 void
12350 AcceptEvent()
12351 {
12352     /* Accept a pending offer of any kind from opponent */
12353     
12354     if (appData.icsActive) {
12355         SendToICS(ics_prefix);
12356         SendToICS("accept\n");
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             TruncateGame();
12363             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12364             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12365         } else {
12366             DisplayError(_("There is no pending offer on this move"), 0);
12367             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12368         }
12369     } else {
12370         /* Not used for offers from chess program */
12371     }
12372 }
12373
12374 void
12375 DeclineEvent()
12376 {
12377     /* Decline a pending offer of any kind from opponent */
12378     
12379     if (appData.icsActive) {
12380         SendToICS(ics_prefix);
12381         SendToICS("decline\n");
12382     } else if (cmailMsgLoaded) {
12383         if (currentMove == cmailOldMove &&
12384             commentList[cmailOldMove] != NULL &&
12385             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12386                    "Black offers a draw" : "White offers a draw")) {
12387 #ifdef NOTDEF
12388             AppendComment(cmailOldMove, "Draw declined", TRUE);
12389             DisplayComment(cmailOldMove - 1, "Draw declined");
12390 #endif /*NOTDEF*/
12391         } else {
12392             DisplayError(_("There is no pending offer on this move"), 0);
12393         }
12394     } else {
12395         /* Not used for offers from chess program */
12396     }
12397 }
12398
12399 void
12400 RematchEvent()
12401 {
12402     /* Issue ICS rematch command */
12403     if (appData.icsActive) {
12404         SendToICS(ics_prefix);
12405         SendToICS("rematch\n");
12406     }
12407 }
12408
12409 void
12410 CallFlagEvent()
12411 {
12412     /* Call your opponent's flag (claim a win on time) */
12413     if (appData.icsActive) {
12414         SendToICS(ics_prefix);
12415         SendToICS("flag\n");
12416     } else {
12417         switch (gameMode) {
12418           default:
12419             return;
12420           case MachinePlaysWhite:
12421             if (whiteFlag) {
12422                 if (blackFlag)
12423                   GameEnds(GameIsDrawn, "Both players ran out of time",
12424                            GE_PLAYER);
12425                 else
12426                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12427             } else {
12428                 DisplayError(_("Your opponent is not out of time"), 0);
12429             }
12430             break;
12431           case MachinePlaysBlack:
12432             if (blackFlag) {
12433                 if (whiteFlag)
12434                   GameEnds(GameIsDrawn, "Both players ran out of time",
12435                            GE_PLAYER);
12436                 else
12437                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12438             } else {
12439                 DisplayError(_("Your opponent is not out of time"), 0);
12440             }
12441             break;
12442         }
12443     }
12444 }
12445
12446 void
12447 DrawEvent()
12448 {
12449     /* Offer draw or accept pending draw offer from opponent */
12450     
12451     if (appData.icsActive) {
12452         /* Note: tournament rules require draw offers to be
12453            made after you make your move but before you punch
12454            your clock.  Currently ICS doesn't let you do that;
12455            instead, you immediately punch your clock after making
12456            a move, but you can offer a draw at any time. */
12457         
12458         SendToICS(ics_prefix);
12459         SendToICS("draw\n");
12460         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12461     } else if (cmailMsgLoaded) {
12462         if (currentMove == cmailOldMove &&
12463             commentList[cmailOldMove] != NULL &&
12464             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12465                    "Black offers a draw" : "White offers a draw")) {
12466             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12467             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12468         } else if (currentMove == cmailOldMove + 1) {
12469             char *offer = WhiteOnMove(cmailOldMove) ?
12470               "White offers a draw" : "Black offers a draw";
12471             AppendComment(currentMove, offer, TRUE);
12472             DisplayComment(currentMove - 1, offer);
12473             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12474         } else {
12475             DisplayError(_("You must make your move before offering a draw"), 0);
12476             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12477         }
12478     } else if (first.offeredDraw) {
12479         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12480     } else {
12481         if (first.sendDrawOffers) {
12482             SendToProgram("draw\n", &first);
12483             userOfferedDraw = TRUE;
12484         }
12485     }
12486 }
12487
12488 void
12489 AdjournEvent()
12490 {
12491     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12492     
12493     if (appData.icsActive) {
12494         SendToICS(ics_prefix);
12495         SendToICS("adjourn\n");
12496     } else {
12497         /* Currently GNU Chess doesn't offer or accept Adjourns */
12498     }
12499 }
12500
12501
12502 void
12503 AbortEvent()
12504 {
12505     /* Offer Abort or accept pending Abort offer from opponent */
12506     
12507     if (appData.icsActive) {
12508         SendToICS(ics_prefix);
12509         SendToICS("abort\n");
12510     } else {
12511         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12512     }
12513 }
12514
12515 void
12516 ResignEvent()
12517 {
12518     /* Resign.  You can do this even if it's not your turn. */
12519     
12520     if (appData.icsActive) {
12521         SendToICS(ics_prefix);
12522         SendToICS("resign\n");
12523     } else {
12524         switch (gameMode) {
12525           case MachinePlaysWhite:
12526             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12527             break;
12528           case MachinePlaysBlack:
12529             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12530             break;
12531           case EditGame:
12532             if (cmailMsgLoaded) {
12533                 TruncateGame();
12534                 if (WhiteOnMove(cmailOldMove)) {
12535                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12536                 } else {
12537                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12538                 }
12539                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12540             }
12541             break;
12542           default:
12543             break;
12544         }
12545     }
12546 }
12547
12548
12549 void
12550 StopObservingEvent()
12551 {
12552     /* Stop observing current games */
12553     SendToICS(ics_prefix);
12554     SendToICS("unobserve\n");
12555 }
12556
12557 void
12558 StopExaminingEvent()
12559 {
12560     /* Stop observing current game */
12561     SendToICS(ics_prefix);
12562     SendToICS("unexamine\n");
12563 }
12564
12565 void
12566 ForwardInner(target)
12567      int target;
12568 {
12569     int limit;
12570
12571     if (appData.debugMode)
12572         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12573                 target, currentMove, forwardMostMove);
12574
12575     if (gameMode == EditPosition)
12576       return;
12577
12578     if (gameMode == PlayFromGameFile && !pausing)
12579       PauseEvent();
12580     
12581     if (gameMode == IcsExamining && pausing)
12582       limit = pauseExamForwardMostMove;
12583     else
12584       limit = forwardMostMove;
12585     
12586     if (target > limit) target = limit;
12587
12588     if (target > 0 && moveList[target - 1][0]) {
12589         int fromX, fromY, toX, toY;
12590         toX = moveList[target - 1][2] - AAA;
12591         toY = moveList[target - 1][3] - ONE;
12592         if (moveList[target - 1][1] == '@') {
12593             if (appData.highlightLastMove) {
12594                 SetHighlights(-1, -1, toX, toY);
12595             }
12596         } else {
12597             fromX = moveList[target - 1][0] - AAA;
12598             fromY = moveList[target - 1][1] - ONE;
12599             if (target == currentMove + 1) {
12600                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12601             }
12602             if (appData.highlightLastMove) {
12603                 SetHighlights(fromX, fromY, toX, toY);
12604             }
12605         }
12606     }
12607     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12608         gameMode == Training || gameMode == PlayFromGameFile || 
12609         gameMode == AnalyzeFile) {
12610         while (currentMove < target) {
12611             SendMoveToProgram(currentMove++, &first);
12612         }
12613     } else {
12614         currentMove = target;
12615     }
12616     
12617     if (gameMode == EditGame || gameMode == EndOfGame) {
12618         whiteTimeRemaining = timeRemaining[0][currentMove];
12619         blackTimeRemaining = timeRemaining[1][currentMove];
12620     }
12621     DisplayBothClocks();
12622     DisplayMove(currentMove - 1);
12623     DrawPosition(FALSE, boards[currentMove]);
12624     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12625     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12626         DisplayComment(currentMove - 1, commentList[currentMove]);
12627     }
12628 }
12629
12630
12631 void
12632 ForwardEvent()
12633 {
12634     if (gameMode == IcsExamining && !pausing) {
12635         SendToICS(ics_prefix);
12636         SendToICS("forward\n");
12637     } else {
12638         ForwardInner(currentMove + 1);
12639     }
12640 }
12641
12642 void
12643 ToEndEvent()
12644 {
12645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12646         /* to optimze, we temporarily turn off analysis mode while we feed
12647          * the remaining moves to the engine. Otherwise we get analysis output
12648          * after each move.
12649          */ 
12650         if (first.analysisSupport) {
12651           SendToProgram("exit\nforce\n", &first);
12652           first.analyzing = FALSE;
12653         }
12654     }
12655         
12656     if (gameMode == IcsExamining && !pausing) {
12657         SendToICS(ics_prefix);
12658         SendToICS("forward 999999\n");
12659     } else {
12660         ForwardInner(forwardMostMove);
12661     }
12662
12663     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12664         /* we have fed all the moves, so reactivate analysis mode */
12665         SendToProgram("analyze\n", &first);
12666         first.analyzing = TRUE;
12667         /*first.maybeThinking = TRUE;*/
12668         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12669     }
12670 }
12671
12672 void
12673 BackwardInner(target)
12674      int target;
12675 {
12676     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12677
12678     if (appData.debugMode)
12679         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12680                 target, currentMove, forwardMostMove);
12681
12682     if (gameMode == EditPosition) return;
12683     if (currentMove <= backwardMostMove) {
12684         ClearHighlights();
12685         DrawPosition(full_redraw, boards[currentMove]);
12686         return;
12687     }
12688     if (gameMode == PlayFromGameFile && !pausing)
12689       PauseEvent();
12690     
12691     if (moveList[target][0]) {
12692         int fromX, fromY, toX, toY;
12693         toX = moveList[target][2] - AAA;
12694         toY = moveList[target][3] - ONE;
12695         if (moveList[target][1] == '@') {
12696             if (appData.highlightLastMove) {
12697                 SetHighlights(-1, -1, toX, toY);
12698             }
12699         } else {
12700             fromX = moveList[target][0] - AAA;
12701             fromY = moveList[target][1] - ONE;
12702             if (target == currentMove - 1) {
12703                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12704             }
12705             if (appData.highlightLastMove) {
12706                 SetHighlights(fromX, fromY, toX, toY);
12707             }
12708         }
12709     }
12710     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12711         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12712         while (currentMove > target) {
12713             SendToProgram("undo\n", &first);
12714             currentMove--;
12715         }
12716     } else {
12717         currentMove = target;
12718     }
12719     
12720     if (gameMode == EditGame || gameMode == EndOfGame) {
12721         whiteTimeRemaining = timeRemaining[0][currentMove];
12722         blackTimeRemaining = timeRemaining[1][currentMove];
12723     }
12724     DisplayBothClocks();
12725     DisplayMove(currentMove - 1);
12726     DrawPosition(full_redraw, boards[currentMove]);
12727     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12728     // [HGM] PV info: routine tests if comment empty
12729     DisplayComment(currentMove - 1, commentList[currentMove]);
12730 }
12731
12732 void
12733 BackwardEvent()
12734 {
12735     if (gameMode == IcsExamining && !pausing) {
12736         SendToICS(ics_prefix);
12737         SendToICS("backward\n");
12738     } else {
12739         BackwardInner(currentMove - 1);
12740     }
12741 }
12742
12743 void
12744 ToStartEvent()
12745 {
12746     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12747         /* to optimize, we temporarily turn off analysis mode while we undo
12748          * all the moves. Otherwise we get analysis output after each undo.
12749          */ 
12750         if (first.analysisSupport) {
12751           SendToProgram("exit\nforce\n", &first);
12752           first.analyzing = FALSE;
12753         }
12754     }
12755
12756     if (gameMode == IcsExamining && !pausing) {
12757         SendToICS(ics_prefix);
12758         SendToICS("backward 999999\n");
12759     } else {
12760         BackwardInner(backwardMostMove);
12761     }
12762
12763     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12764         /* we have fed all the moves, so reactivate analysis mode */
12765         SendToProgram("analyze\n", &first);
12766         first.analyzing = TRUE;
12767         /*first.maybeThinking = TRUE;*/
12768         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12769     }
12770 }
12771
12772 void
12773 ToNrEvent(int to)
12774 {
12775   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12776   if (to >= forwardMostMove) to = forwardMostMove;
12777   if (to <= backwardMostMove) to = backwardMostMove;
12778   if (to < currentMove) {
12779     BackwardInner(to);
12780   } else {
12781     ForwardInner(to);
12782   }
12783 }
12784
12785 void
12786 RevertEvent(Boolean annotate)
12787 {
12788     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12789         return;
12790     }
12791     if (gameMode != IcsExamining) {
12792         DisplayError(_("You are not examining a game"), 0);
12793         return;
12794     }
12795     if (pausing) {
12796         DisplayError(_("You can't revert while pausing"), 0);
12797         return;
12798     }
12799     SendToICS(ics_prefix);
12800     SendToICS("revert\n");
12801 }
12802
12803 void
12804 RetractMoveEvent()
12805 {
12806     switch (gameMode) {
12807       case MachinePlaysWhite:
12808       case MachinePlaysBlack:
12809         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12810             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12811             return;
12812         }
12813         if (forwardMostMove < 2) return;
12814         currentMove = forwardMostMove = forwardMostMove - 2;
12815         whiteTimeRemaining = timeRemaining[0][currentMove];
12816         blackTimeRemaining = timeRemaining[1][currentMove];
12817         DisplayBothClocks();
12818         DisplayMove(currentMove - 1);
12819         ClearHighlights();/*!! could figure this out*/
12820         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12821         SendToProgram("remove\n", &first);
12822         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12823         break;
12824
12825       case BeginningOfGame:
12826       default:
12827         break;
12828
12829       case IcsPlayingWhite:
12830       case IcsPlayingBlack:
12831         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12832             SendToICS(ics_prefix);
12833             SendToICS("takeback 2\n");
12834         } else {
12835             SendToICS(ics_prefix);
12836             SendToICS("takeback 1\n");
12837         }
12838         break;
12839     }
12840 }
12841
12842 void
12843 MoveNowEvent()
12844 {
12845     ChessProgramState *cps;
12846
12847     switch (gameMode) {
12848       case MachinePlaysWhite:
12849         if (!WhiteOnMove(forwardMostMove)) {
12850             DisplayError(_("It is your turn"), 0);
12851             return;
12852         }
12853         cps = &first;
12854         break;
12855       case MachinePlaysBlack:
12856         if (WhiteOnMove(forwardMostMove)) {
12857             DisplayError(_("It is your turn"), 0);
12858             return;
12859         }
12860         cps = &first;
12861         break;
12862       case TwoMachinesPlay:
12863         if (WhiteOnMove(forwardMostMove) ==
12864             (first.twoMachinesColor[0] == 'w')) {
12865             cps = &first;
12866         } else {
12867             cps = &second;
12868         }
12869         break;
12870       case BeginningOfGame:
12871       default:
12872         return;
12873     }
12874     SendToProgram("?\n", cps);
12875 }
12876
12877 void
12878 TruncateGameEvent()
12879 {
12880     EditGameEvent();
12881     if (gameMode != EditGame) return;
12882     TruncateGame();
12883 }
12884
12885 void
12886 TruncateGame()
12887 {
12888     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12889     if (forwardMostMove > currentMove) {
12890         if (gameInfo.resultDetails != NULL) {
12891             free(gameInfo.resultDetails);
12892             gameInfo.resultDetails = NULL;
12893             gameInfo.result = GameUnfinished;
12894         }
12895         forwardMostMove = currentMove;
12896         HistorySet(parseList, backwardMostMove, forwardMostMove,
12897                    currentMove-1);
12898     }
12899 }
12900
12901 void
12902 HintEvent()
12903 {
12904     if (appData.noChessProgram) return;
12905     switch (gameMode) {
12906       case MachinePlaysWhite:
12907         if (WhiteOnMove(forwardMostMove)) {
12908             DisplayError(_("Wait until your turn"), 0);
12909             return;
12910         }
12911         break;
12912       case BeginningOfGame:
12913       case MachinePlaysBlack:
12914         if (!WhiteOnMove(forwardMostMove)) {
12915             DisplayError(_("Wait until your turn"), 0);
12916             return;
12917         }
12918         break;
12919       default:
12920         DisplayError(_("No hint available"), 0);
12921         return;
12922     }
12923     SendToProgram("hint\n", &first);
12924     hintRequested = TRUE;
12925 }
12926
12927 void
12928 BookEvent()
12929 {
12930     if (appData.noChessProgram) return;
12931     switch (gameMode) {
12932       case MachinePlaysWhite:
12933         if (WhiteOnMove(forwardMostMove)) {
12934             DisplayError(_("Wait until your turn"), 0);
12935             return;
12936         }
12937         break;
12938       case BeginningOfGame:
12939       case MachinePlaysBlack:
12940         if (!WhiteOnMove(forwardMostMove)) {
12941             DisplayError(_("Wait until your turn"), 0);
12942             return;
12943         }
12944         break;
12945       case EditPosition:
12946         EditPositionDone(TRUE);
12947         break;
12948       case TwoMachinesPlay:
12949         return;
12950       default:
12951         break;
12952     }
12953     SendToProgram("bk\n", &first);
12954     bookOutput[0] = NULLCHAR;
12955     bookRequested = TRUE;
12956 }
12957
12958 void
12959 AboutGameEvent()
12960 {
12961     char *tags = PGNTags(&gameInfo);
12962     TagsPopUp(tags, CmailMsg());
12963     free(tags);
12964 }
12965
12966 /* end button procedures */
12967
12968 void
12969 PrintPosition(fp, move)
12970      FILE *fp;
12971      int move;
12972 {
12973     int i, j;
12974     
12975     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12976         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12977             char c = PieceToChar(boards[move][i][j]);
12978             fputc(c == 'x' ? '.' : c, fp);
12979             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12980         }
12981     }
12982     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12983       fprintf(fp, "white to play\n");
12984     else
12985       fprintf(fp, "black to play\n");
12986 }
12987
12988 void
12989 PrintOpponents(fp)
12990      FILE *fp;
12991 {
12992     if (gameInfo.white != NULL) {
12993         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12994     } else {
12995         fprintf(fp, "\n");
12996     }
12997 }
12998
12999 /* Find last component of program's own name, using some heuristics */
13000 void
13001 TidyProgramName(prog, host, buf)
13002      char *prog, *host, buf[MSG_SIZ];
13003 {
13004     char *p, *q;
13005     int local = (strcmp(host, "localhost") == 0);
13006     while (!local && (p = strchr(prog, ';')) != NULL) {
13007         p++;
13008         while (*p == ' ') p++;
13009         prog = p;
13010     }
13011     if (*prog == '"' || *prog == '\'') {
13012         q = strchr(prog + 1, *prog);
13013     } else {
13014         q = strchr(prog, ' ');
13015     }
13016     if (q == NULL) q = prog + strlen(prog);
13017     p = q;
13018     while (p >= prog && *p != '/' && *p != '\\') p--;
13019     p++;
13020     if(p == prog && *p == '"') p++;
13021     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13022     memcpy(buf, p, q - p);
13023     buf[q - p] = NULLCHAR;
13024     if (!local) {
13025         strcat(buf, "@");
13026         strcat(buf, host);
13027     }
13028 }
13029
13030 char *
13031 TimeControlTagValue()
13032 {
13033     char buf[MSG_SIZ];
13034     if (!appData.clockMode) {
13035         strcpy(buf, "-");
13036     } else if (movesPerSession > 0) {
13037         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13038     } else if (timeIncrement == 0) {
13039         sprintf(buf, "%ld", timeControl/1000);
13040     } else {
13041         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13042     }
13043     return StrSave(buf);
13044 }
13045
13046 void
13047 SetGameInfo()
13048 {
13049     /* This routine is used only for certain modes */
13050     VariantClass v = gameInfo.variant;
13051     ChessMove r = GameUnfinished;
13052     char *p = NULL;
13053
13054     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13055         r = gameInfo.result; 
13056         p = gameInfo.resultDetails; 
13057         gameInfo.resultDetails = NULL;
13058     }
13059     ClearGameInfo(&gameInfo);
13060     gameInfo.variant = v;
13061
13062     switch (gameMode) {
13063       case MachinePlaysWhite:
13064         gameInfo.event = StrSave( appData.pgnEventHeader );
13065         gameInfo.site = StrSave(HostName());
13066         gameInfo.date = PGNDate();
13067         gameInfo.round = StrSave("-");
13068         gameInfo.white = StrSave(first.tidy);
13069         gameInfo.black = StrSave(UserName());
13070         gameInfo.timeControl = TimeControlTagValue();
13071         break;
13072
13073       case MachinePlaysBlack:
13074         gameInfo.event = StrSave( appData.pgnEventHeader );
13075         gameInfo.site = StrSave(HostName());
13076         gameInfo.date = PGNDate();
13077         gameInfo.round = StrSave("-");
13078         gameInfo.white = StrSave(UserName());
13079         gameInfo.black = StrSave(first.tidy);
13080         gameInfo.timeControl = TimeControlTagValue();
13081         break;
13082
13083       case TwoMachinesPlay:
13084         gameInfo.event = StrSave( appData.pgnEventHeader );
13085         gameInfo.site = StrSave(HostName());
13086         gameInfo.date = PGNDate();
13087         if (matchGame > 0) {
13088             char buf[MSG_SIZ];
13089             sprintf(buf, "%d", matchGame);
13090             gameInfo.round = StrSave(buf);
13091         } else {
13092             gameInfo.round = StrSave("-");
13093         }
13094         if (first.twoMachinesColor[0] == 'w') {
13095             gameInfo.white = StrSave(first.tidy);
13096             gameInfo.black = StrSave(second.tidy);
13097         } else {
13098             gameInfo.white = StrSave(second.tidy);
13099             gameInfo.black = StrSave(first.tidy);
13100         }
13101         gameInfo.timeControl = TimeControlTagValue();
13102         break;
13103
13104       case EditGame:
13105         gameInfo.event = StrSave("Edited game");
13106         gameInfo.site = StrSave(HostName());
13107         gameInfo.date = PGNDate();
13108         gameInfo.round = StrSave("-");
13109         gameInfo.white = StrSave("-");
13110         gameInfo.black = StrSave("-");
13111         gameInfo.result = r;
13112         gameInfo.resultDetails = p;
13113         break;
13114
13115       case EditPosition:
13116         gameInfo.event = StrSave("Edited position");
13117         gameInfo.site = StrSave(HostName());
13118         gameInfo.date = PGNDate();
13119         gameInfo.round = StrSave("-");
13120         gameInfo.white = StrSave("-");
13121         gameInfo.black = StrSave("-");
13122         break;
13123
13124       case IcsPlayingWhite:
13125       case IcsPlayingBlack:
13126       case IcsObserving:
13127       case IcsExamining:
13128         break;
13129
13130       case PlayFromGameFile:
13131         gameInfo.event = StrSave("Game from non-PGN file");
13132         gameInfo.site = StrSave(HostName());
13133         gameInfo.date = PGNDate();
13134         gameInfo.round = StrSave("-");
13135         gameInfo.white = StrSave("?");
13136         gameInfo.black = StrSave("?");
13137         break;
13138
13139       default:
13140         break;
13141     }
13142 }
13143
13144 void
13145 ReplaceComment(index, text)
13146      int index;
13147      char *text;
13148 {
13149     int len;
13150
13151     while (*text == '\n') text++;
13152     len = strlen(text);
13153     while (len > 0 && text[len - 1] == '\n') len--;
13154
13155     if (commentList[index] != NULL)
13156       free(commentList[index]);
13157
13158     if (len == 0) {
13159         commentList[index] = NULL;
13160         return;
13161     }
13162   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13163       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13164       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13165     commentList[index] = (char *) malloc(len + 2);
13166     strncpy(commentList[index], text, len);
13167     commentList[index][len] = '\n';
13168     commentList[index][len + 1] = NULLCHAR;
13169   } else { 
13170     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13171     char *p;
13172     commentList[index] = (char *) malloc(len + 6);
13173     strcpy(commentList[index], "{\n");
13174     strncpy(commentList[index]+2, text, len);
13175     commentList[index][len+2] = NULLCHAR;
13176     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13177     strcat(commentList[index], "\n}\n");
13178   }
13179 }
13180
13181 void
13182 CrushCRs(text)
13183      char *text;
13184 {
13185   char *p = text;
13186   char *q = text;
13187   char ch;
13188
13189   do {
13190     ch = *p++;
13191     if (ch == '\r') continue;
13192     *q++ = ch;
13193   } while (ch != '\0');
13194 }
13195
13196 void
13197 AppendComment(index, text, addBraces)
13198      int index;
13199      char *text;
13200      Boolean addBraces; // [HGM] braces: tells if we should add {}
13201 {
13202     int oldlen, len;
13203     char *old;
13204
13205 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13206     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13207
13208     CrushCRs(text);
13209     while (*text == '\n') text++;
13210     len = strlen(text);
13211     while (len > 0 && text[len - 1] == '\n') len--;
13212
13213     if (len == 0) return;
13214
13215     if (commentList[index] != NULL) {
13216         old = commentList[index];
13217         oldlen = strlen(old);
13218         while(commentList[index][oldlen-1] ==  '\n')
13219           commentList[index][--oldlen] = NULLCHAR;
13220         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13221         strcpy(commentList[index], old);
13222         free(old);
13223         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13224         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13225           if(addBraces) addBraces = FALSE; else { text++; len--; }
13226           while (*text == '\n') { text++; len--; }
13227           commentList[index][--oldlen] = NULLCHAR;
13228       }
13229         if(addBraces) strcat(commentList[index], "\n{\n");
13230         else          strcat(commentList[index], "\n");
13231         strcat(commentList[index], text);
13232         if(addBraces) strcat(commentList[index], "\n}\n");
13233         else          strcat(commentList[index], "\n");
13234     } else {
13235         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13236         if(addBraces)
13237              strcpy(commentList[index], "{\n");
13238         else commentList[index][0] = NULLCHAR;
13239         strcat(commentList[index], text);
13240         strcat(commentList[index], "\n");
13241         if(addBraces) strcat(commentList[index], "}\n");
13242     }
13243 }
13244
13245 static char * FindStr( char * text, char * sub_text )
13246 {
13247     char * result = strstr( text, sub_text );
13248
13249     if( result != NULL ) {
13250         result += strlen( sub_text );
13251     }
13252
13253     return result;
13254 }
13255
13256 /* [AS] Try to extract PV info from PGN comment */
13257 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13258 char *GetInfoFromComment( int index, char * text )
13259 {
13260     char * sep = text;
13261
13262     if( text != NULL && index > 0 ) {
13263         int score = 0;
13264         int depth = 0;
13265         int time = -1, sec = 0, deci;
13266         char * s_eval = FindStr( text, "[%eval " );
13267         char * s_emt = FindStr( text, "[%emt " );
13268
13269         if( s_eval != NULL || s_emt != NULL ) {
13270             /* New style */
13271             char delim;
13272
13273             if( s_eval != NULL ) {
13274                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13275                     return text;
13276                 }
13277
13278                 if( delim != ']' ) {
13279                     return text;
13280                 }
13281             }
13282
13283             if( s_emt != NULL ) {
13284             }
13285                 return text;
13286         }
13287         else {
13288             /* We expect something like: [+|-]nnn.nn/dd */
13289             int score_lo = 0;
13290
13291             if(*text != '{') return text; // [HGM] braces: must be normal comment
13292
13293             sep = strchr( text, '/' );
13294             if( sep == NULL || sep < (text+4) ) {
13295                 return text;
13296             }
13297
13298             time = -1; sec = -1; deci = -1;
13299             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13300                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13301                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13302                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13303                 return text;
13304             }
13305
13306             if( score_lo < 0 || score_lo >= 100 ) {
13307                 return text;
13308             }
13309
13310             if(sec >= 0) time = 600*time + 10*sec; else
13311             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13312
13313             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13314
13315             /* [HGM] PV time: now locate end of PV info */
13316             while( *++sep >= '0' && *sep <= '9'); // strip depth
13317             if(time >= 0)
13318             while( *++sep >= '0' && *sep <= '9'); // strip time
13319             if(sec >= 0)
13320             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13321             if(deci >= 0)
13322             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13323             while(*sep == ' ') sep++;
13324         }
13325
13326         if( depth <= 0 ) {
13327             return text;
13328         }
13329
13330         if( time < 0 ) {
13331             time = -1;
13332         }
13333
13334         pvInfoList[index-1].depth = depth;
13335         pvInfoList[index-1].score = score;
13336         pvInfoList[index-1].time  = 10*time; // centi-sec
13337         if(*sep == '}') *sep = 0; else *--sep = '{';
13338     }
13339     return sep;
13340 }
13341
13342 void
13343 SendToProgram(message, cps)
13344      char *message;
13345      ChessProgramState *cps;
13346 {
13347     int count, outCount, error;
13348     char buf[MSG_SIZ];
13349
13350     if (cps->pr == NULL) return;
13351     Attention(cps);
13352     
13353     if (appData.debugMode) {
13354         TimeMark now;
13355         GetTimeMark(&now);
13356         fprintf(debugFP, "%ld >%-6s: %s", 
13357                 SubtractTimeMarks(&now, &programStartTime),
13358                 cps->which, message);
13359     }
13360     
13361     count = strlen(message);
13362     outCount = OutputToProcess(cps->pr, message, count, &error);
13363     if (outCount < count && !exiting 
13364                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13365         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13366         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13367             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13368                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13369                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13370             } else {
13371                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13372             }
13373             gameInfo.resultDetails = StrSave(buf);
13374         }
13375         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13376     }
13377 }
13378
13379 void
13380 ReceiveFromProgram(isr, closure, message, count, error)
13381      InputSourceRef isr;
13382      VOIDSTAR closure;
13383      char *message;
13384      int count;
13385      int error;
13386 {
13387     char *end_str;
13388     char buf[MSG_SIZ];
13389     ChessProgramState *cps = (ChessProgramState *)closure;
13390
13391     if (isr != cps->isr) return; /* Killed intentionally */
13392     if (count <= 0) {
13393         if (count == 0) {
13394             sprintf(buf,
13395                     _("Error: %s chess program (%s) exited unexpectedly"),
13396                     cps->which, cps->program);
13397         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13398                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13399                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13400                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13401                 } else {
13402                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13403                 }
13404                 gameInfo.resultDetails = StrSave(buf);
13405             }
13406             RemoveInputSource(cps->isr);
13407             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13408         } else {
13409             sprintf(buf,
13410                     _("Error reading from %s chess program (%s)"),
13411                     cps->which, cps->program);
13412             RemoveInputSource(cps->isr);
13413
13414             /* [AS] Program is misbehaving badly... kill it */
13415             if( count == -2 ) {
13416                 DestroyChildProcess( cps->pr, 9 );
13417                 cps->pr = NoProc;
13418             }
13419
13420             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13421         }
13422         return;
13423     }
13424     
13425     if ((end_str = strchr(message, '\r')) != NULL)
13426       *end_str = NULLCHAR;
13427     if ((end_str = strchr(message, '\n')) != NULL)
13428       *end_str = NULLCHAR;
13429     
13430     if (appData.debugMode) {
13431         TimeMark now; int print = 1;
13432         char *quote = ""; char c; int i;
13433
13434         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13435                 char start = message[0];
13436                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13437                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13438                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13439                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13440                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13441                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13442                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13443                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13444                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13445                     print = (appData.engineComments >= 2);
13446                 }
13447                 message[0] = start; // restore original message
13448         }
13449         if(print) {
13450                 GetTimeMark(&now);
13451                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13452                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13453                         quote,
13454                         message);
13455         }
13456     }
13457
13458     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13459     if (appData.icsEngineAnalyze) {
13460         if (strstr(message, "whisper") != NULL ||
13461              strstr(message, "kibitz") != NULL || 
13462             strstr(message, "tellics") != NULL) return;
13463     }
13464
13465     HandleMachineMove(message, cps);
13466 }
13467
13468
13469 void
13470 SendTimeControl(cps, mps, tc, inc, sd, st)
13471      ChessProgramState *cps;
13472      int mps, inc, sd, st;
13473      long tc;
13474 {
13475     char buf[MSG_SIZ];
13476     int seconds;
13477
13478     if( timeControl_2 > 0 ) {
13479         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13480             tc = timeControl_2;
13481         }
13482     }
13483     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13484     inc /= cps->timeOdds;
13485     st  /= cps->timeOdds;
13486
13487     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13488
13489     if (st > 0) {
13490       /* Set exact time per move, normally using st command */
13491       if (cps->stKludge) {
13492         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13493         seconds = st % 60;
13494         if (seconds == 0) {
13495           sprintf(buf, "level 1 %d\n", st/60);
13496         } else {
13497           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13498         }
13499       } else {
13500         sprintf(buf, "st %d\n", st);
13501       }
13502     } else {
13503       /* Set conventional or incremental time control, using level command */
13504       if (seconds == 0) {
13505         /* Note old gnuchess bug -- minutes:seconds used to not work.
13506            Fixed in later versions, but still avoid :seconds
13507            when seconds is 0. */
13508         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13509       } else {
13510         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13511                 seconds, inc/1000);
13512       }
13513     }
13514     SendToProgram(buf, cps);
13515
13516     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13517     /* Orthogonally, limit search to given depth */
13518     if (sd > 0) {
13519       if (cps->sdKludge) {
13520         sprintf(buf, "depth\n%d\n", sd);
13521       } else {
13522         sprintf(buf, "sd %d\n", sd);
13523       }
13524       SendToProgram(buf, cps);
13525     }
13526
13527     if(cps->nps > 0) { /* [HGM] nps */
13528         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13529         else {
13530                 sprintf(buf, "nps %d\n", cps->nps);
13531               SendToProgram(buf, cps);
13532         }
13533     }
13534 }
13535
13536 ChessProgramState *WhitePlayer()
13537 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13538 {
13539     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13540        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13541         return &second;
13542     return &first;
13543 }
13544
13545 void
13546 SendTimeRemaining(cps, machineWhite)
13547      ChessProgramState *cps;
13548      int /*boolean*/ machineWhite;
13549 {
13550     char message[MSG_SIZ];
13551     long time, otime;
13552
13553     /* Note: this routine must be called when the clocks are stopped
13554        or when they have *just* been set or switched; otherwise
13555        it will be off by the time since the current tick started.
13556     */
13557     if (machineWhite) {
13558         time = whiteTimeRemaining / 10;
13559         otime = blackTimeRemaining / 10;
13560     } else {
13561         time = blackTimeRemaining / 10;
13562         otime = whiteTimeRemaining / 10;
13563     }
13564     /* [HGM] translate opponent's time by time-odds factor */
13565     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13566     if (appData.debugMode) {
13567         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13568     }
13569
13570     if (time <= 0) time = 1;
13571     if (otime <= 0) otime = 1;
13572     
13573     sprintf(message, "time %ld\n", time);
13574     SendToProgram(message, cps);
13575
13576     sprintf(message, "otim %ld\n", otime);
13577     SendToProgram(message, cps);
13578 }
13579
13580 int
13581 BoolFeature(p, name, loc, cps)
13582      char **p;
13583      char *name;
13584      int *loc;
13585      ChessProgramState *cps;
13586 {
13587   char buf[MSG_SIZ];
13588   int len = strlen(name);
13589   int val;
13590   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13591     (*p) += len + 1;
13592     sscanf(*p, "%d", &val);
13593     *loc = (val != 0);
13594     while (**p && **p != ' ') (*p)++;
13595     sprintf(buf, "accepted %s\n", name);
13596     SendToProgram(buf, cps);
13597     return TRUE;
13598   }
13599   return FALSE;
13600 }
13601
13602 int
13603 IntFeature(p, name, loc, cps)
13604      char **p;
13605      char *name;
13606      int *loc;
13607      ChessProgramState *cps;
13608 {
13609   char buf[MSG_SIZ];
13610   int len = strlen(name);
13611   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13612     (*p) += len + 1;
13613     sscanf(*p, "%d", loc);
13614     while (**p && **p != ' ') (*p)++;
13615     sprintf(buf, "accepted %s\n", name);
13616     SendToProgram(buf, cps);
13617     return TRUE;
13618   }
13619   return FALSE;
13620 }
13621
13622 int
13623 StringFeature(p, name, loc, cps)
13624      char **p;
13625      char *name;
13626      char loc[];
13627      ChessProgramState *cps;
13628 {
13629   char buf[MSG_SIZ];
13630   int len = strlen(name);
13631   if (strncmp((*p), name, len) == 0
13632       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13633     (*p) += len + 2;
13634     sscanf(*p, "%[^\"]", loc);
13635     while (**p && **p != '\"') (*p)++;
13636     if (**p == '\"') (*p)++;
13637     sprintf(buf, "accepted %s\n", name);
13638     SendToProgram(buf, cps);
13639     return TRUE;
13640   }
13641   return FALSE;
13642 }
13643
13644 int 
13645 ParseOption(Option *opt, ChessProgramState *cps)
13646 // [HGM] options: process the string that defines an engine option, and determine
13647 // name, type, default value, and allowed value range
13648 {
13649         char *p, *q, buf[MSG_SIZ];
13650         int n, min = (-1)<<31, max = 1<<31, def;
13651
13652         if(p = strstr(opt->name, " -spin ")) {
13653             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13654             if(max < min) max = min; // enforce consistency
13655             if(def < min) def = min;
13656             if(def > max) def = max;
13657             opt->value = def;
13658             opt->min = min;
13659             opt->max = max;
13660             opt->type = Spin;
13661         } else if((p = strstr(opt->name, " -slider "))) {
13662             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13663             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13664             if(max < min) max = min; // enforce consistency
13665             if(def < min) def = min;
13666             if(def > max) def = max;
13667             opt->value = def;
13668             opt->min = min;
13669             opt->max = max;
13670             opt->type = Spin; // Slider;
13671         } else if((p = strstr(opt->name, " -string "))) {
13672             opt->textValue = p+9;
13673             opt->type = TextBox;
13674         } else if((p = strstr(opt->name, " -file "))) {
13675             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13676             opt->textValue = p+7;
13677             opt->type = TextBox; // FileName;
13678         } else if((p = strstr(opt->name, " -path "))) {
13679             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13680             opt->textValue = p+7;
13681             opt->type = TextBox; // PathName;
13682         } else if(p = strstr(opt->name, " -check ")) {
13683             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13684             opt->value = (def != 0);
13685             opt->type = CheckBox;
13686         } else if(p = strstr(opt->name, " -combo ")) {
13687             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13688             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13689             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13690             opt->value = n = 0;
13691             while(q = StrStr(q, " /// ")) {
13692                 n++; *q = 0;    // count choices, and null-terminate each of them
13693                 q += 5;
13694                 if(*q == '*') { // remember default, which is marked with * prefix
13695                     q++;
13696                     opt->value = n;
13697                 }
13698                 cps->comboList[cps->comboCnt++] = q;
13699             }
13700             cps->comboList[cps->comboCnt++] = NULL;
13701             opt->max = n + 1;
13702             opt->type = ComboBox;
13703         } else if(p = strstr(opt->name, " -button")) {
13704             opt->type = Button;
13705         } else if(p = strstr(opt->name, " -save")) {
13706             opt->type = SaveButton;
13707         } else return FALSE;
13708         *p = 0; // terminate option name
13709         // now look if the command-line options define a setting for this engine option.
13710         if(cps->optionSettings && cps->optionSettings[0])
13711             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13712         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13713                 sprintf(buf, "option %s", p);
13714                 if(p = strstr(buf, ",")) *p = 0;
13715                 strcat(buf, "\n");
13716                 SendToProgram(buf, cps);
13717         }
13718         return TRUE;
13719 }
13720
13721 void
13722 FeatureDone(cps, val)
13723      ChessProgramState* cps;
13724      int val;
13725 {
13726   DelayedEventCallback cb = GetDelayedEvent();
13727   if ((cb == InitBackEnd3 && cps == &first) ||
13728       (cb == TwoMachinesEventIfReady && cps == &second)) {
13729     CancelDelayedEvent();
13730     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13731   }
13732   cps->initDone = val;
13733 }
13734
13735 /* Parse feature command from engine */
13736 void
13737 ParseFeatures(args, cps)
13738      char* args;
13739      ChessProgramState *cps;  
13740 {
13741   char *p = args;
13742   char *q;
13743   int val;
13744   char buf[MSG_SIZ];
13745
13746   for (;;) {
13747     while (*p == ' ') p++;
13748     if (*p == NULLCHAR) return;
13749
13750     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13751     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13752     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13753     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13754     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13755     if (BoolFeature(&p, "reuse", &val, cps)) {
13756       /* Engine can disable reuse, but can't enable it if user said no */
13757       if (!val) cps->reuse = FALSE;
13758       continue;
13759     }
13760     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13761     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13762       if (gameMode == TwoMachinesPlay) {
13763         DisplayTwoMachinesTitle();
13764       } else {
13765         DisplayTitle("");
13766       }
13767       continue;
13768     }
13769     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13770     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13771     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13772     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13773     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13774     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13775     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13776     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13777     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13778     if (IntFeature(&p, "done", &val, cps)) {
13779       FeatureDone(cps, val);
13780       continue;
13781     }
13782     /* Added by Tord: */
13783     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13784     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13785     /* End of additions by Tord */
13786
13787     /* [HGM] added features: */
13788     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13789     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13790     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13791     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13792     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13793     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13794     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13795         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13796             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13797             SendToProgram(buf, cps);
13798             continue;
13799         }
13800         if(cps->nrOptions >= MAX_OPTIONS) {
13801             cps->nrOptions--;
13802             sprintf(buf, "%s engine has too many options\n", cps->which);
13803             DisplayError(buf, 0);
13804         }
13805         continue;
13806     }
13807     /* End of additions by HGM */
13808
13809     /* unknown feature: complain and skip */
13810     q = p;
13811     while (*q && *q != '=') q++;
13812     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13813     SendToProgram(buf, cps);
13814     p = q;
13815     if (*p == '=') {
13816       p++;
13817       if (*p == '\"') {
13818         p++;
13819         while (*p && *p != '\"') p++;
13820         if (*p == '\"') p++;
13821       } else {
13822         while (*p && *p != ' ') p++;
13823       }
13824     }
13825   }
13826
13827 }
13828
13829 void
13830 PeriodicUpdatesEvent(newState)
13831      int newState;
13832 {
13833     if (newState == appData.periodicUpdates)
13834       return;
13835
13836     appData.periodicUpdates=newState;
13837
13838     /* Display type changes, so update it now */
13839 //    DisplayAnalysis();
13840
13841     /* Get the ball rolling again... */
13842     if (newState) {
13843         AnalysisPeriodicEvent(1);
13844         StartAnalysisClock();
13845     }
13846 }
13847
13848 void
13849 PonderNextMoveEvent(newState)
13850      int newState;
13851 {
13852     if (newState == appData.ponderNextMove) return;
13853     if (gameMode == EditPosition) EditPositionDone(TRUE);
13854     if (newState) {
13855         SendToProgram("hard\n", &first);
13856         if (gameMode == TwoMachinesPlay) {
13857             SendToProgram("hard\n", &second);
13858         }
13859     } else {
13860         SendToProgram("easy\n", &first);
13861         thinkOutput[0] = NULLCHAR;
13862         if (gameMode == TwoMachinesPlay) {
13863             SendToProgram("easy\n", &second);
13864         }
13865     }
13866     appData.ponderNextMove = newState;
13867 }
13868
13869 void
13870 NewSettingEvent(option, feature, command, value)
13871      char *command;
13872      int option, value, *feature;
13873 {
13874     char buf[MSG_SIZ];
13875
13876     if (gameMode == EditPosition) EditPositionDone(TRUE);
13877     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13878     if(feature == NULL || *feature) SendToProgram(buf, &first);
13879     if (gameMode == TwoMachinesPlay) {
13880         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13881     }
13882 }
13883
13884 void
13885 ShowThinkingEvent()
13886 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13887 {
13888     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13889     int newState = appData.showThinking
13890         // [HGM] thinking: other features now need thinking output as well
13891         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13892     
13893     if (oldState == newState) return;
13894     oldState = newState;
13895     if (gameMode == EditPosition) EditPositionDone(TRUE);
13896     if (oldState) {
13897         SendToProgram("post\n", &first);
13898         if (gameMode == TwoMachinesPlay) {
13899             SendToProgram("post\n", &second);
13900         }
13901     } else {
13902         SendToProgram("nopost\n", &first);
13903         thinkOutput[0] = NULLCHAR;
13904         if (gameMode == TwoMachinesPlay) {
13905             SendToProgram("nopost\n", &second);
13906         }
13907     }
13908 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13909 }
13910
13911 void
13912 AskQuestionEvent(title, question, replyPrefix, which)
13913      char *title; char *question; char *replyPrefix; char *which;
13914 {
13915   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13916   if (pr == NoProc) return;
13917   AskQuestion(title, question, replyPrefix, pr);
13918 }
13919
13920 void
13921 DisplayMove(moveNumber)
13922      int moveNumber;
13923 {
13924     char message[MSG_SIZ];
13925     char res[MSG_SIZ];
13926     char cpThinkOutput[MSG_SIZ];
13927
13928     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13929     
13930     if (moveNumber == forwardMostMove - 1 || 
13931         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13932
13933         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13934
13935         if (strchr(cpThinkOutput, '\n')) {
13936             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13937         }
13938     } else {
13939         *cpThinkOutput = NULLCHAR;
13940     }
13941
13942     /* [AS] Hide thinking from human user */
13943     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13944         *cpThinkOutput = NULLCHAR;
13945         if( thinkOutput[0] != NULLCHAR ) {
13946             int i;
13947
13948             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13949                 cpThinkOutput[i] = '.';
13950             }
13951             cpThinkOutput[i] = NULLCHAR;
13952             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13953         }
13954     }
13955
13956     if (moveNumber == forwardMostMove - 1 &&
13957         gameInfo.resultDetails != NULL) {
13958         if (gameInfo.resultDetails[0] == NULLCHAR) {
13959             sprintf(res, " %s", PGNResult(gameInfo.result));
13960         } else {
13961             sprintf(res, " {%s} %s",
13962                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13963         }
13964     } else {
13965         res[0] = NULLCHAR;
13966     }
13967
13968     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13969         DisplayMessage(res, cpThinkOutput);
13970     } else {
13971         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13972                 WhiteOnMove(moveNumber) ? " " : ".. ",
13973                 parseList[moveNumber], res);
13974         DisplayMessage(message, cpThinkOutput);
13975     }
13976 }
13977
13978 void
13979 DisplayComment(moveNumber, text)
13980      int moveNumber;
13981      char *text;
13982 {
13983     char title[MSG_SIZ];
13984     char buf[8000]; // comment can be long!
13985     int score, depth;
13986     
13987     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13988       strcpy(title, "Comment");
13989     } else {
13990       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13991               WhiteOnMove(moveNumber) ? " " : ".. ",
13992               parseList[moveNumber]);
13993     }
13994     // [HGM] PV info: display PV info together with (or as) comment
13995     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13996       if(text == NULL) text = "";                                           
13997       score = pvInfoList[moveNumber].score;
13998       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13999               depth, (pvInfoList[moveNumber].time+50)/100, text);
14000       text = buf;
14001     }
14002     if (text != NULL && (appData.autoDisplayComment || commentUp))
14003         CommentPopUp(title, text);
14004 }
14005
14006 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14007  * might be busy thinking or pondering.  It can be omitted if your
14008  * gnuchess is configured to stop thinking immediately on any user
14009  * input.  However, that gnuchess feature depends on the FIONREAD
14010  * ioctl, which does not work properly on some flavors of Unix.
14011  */
14012 void
14013 Attention(cps)
14014      ChessProgramState *cps;
14015 {
14016 #if ATTENTION
14017     if (!cps->useSigint) return;
14018     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14019     switch (gameMode) {
14020       case MachinePlaysWhite:
14021       case MachinePlaysBlack:
14022       case TwoMachinesPlay:
14023       case IcsPlayingWhite:
14024       case IcsPlayingBlack:
14025       case AnalyzeMode:
14026       case AnalyzeFile:
14027         /* Skip if we know it isn't thinking */
14028         if (!cps->maybeThinking) return;
14029         if (appData.debugMode)
14030           fprintf(debugFP, "Interrupting %s\n", cps->which);
14031         InterruptChildProcess(cps->pr);
14032         cps->maybeThinking = FALSE;
14033         break;
14034       default:
14035         break;
14036     }
14037 #endif /*ATTENTION*/
14038 }
14039
14040 int
14041 CheckFlags()
14042 {
14043     if (whiteTimeRemaining <= 0) {
14044         if (!whiteFlag) {
14045             whiteFlag = TRUE;
14046             if (appData.icsActive) {
14047                 if (appData.autoCallFlag &&
14048                     gameMode == IcsPlayingBlack && !blackFlag) {
14049                   SendToICS(ics_prefix);
14050                   SendToICS("flag\n");
14051                 }
14052             } else {
14053                 if (blackFlag) {
14054                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14055                 } else {
14056                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14057                     if (appData.autoCallFlag) {
14058                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14059                         return TRUE;
14060                     }
14061                 }
14062             }
14063         }
14064     }
14065     if (blackTimeRemaining <= 0) {
14066         if (!blackFlag) {
14067             blackFlag = TRUE;
14068             if (appData.icsActive) {
14069                 if (appData.autoCallFlag &&
14070                     gameMode == IcsPlayingWhite && !whiteFlag) {
14071                   SendToICS(ics_prefix);
14072                   SendToICS("flag\n");
14073                 }
14074             } else {
14075                 if (whiteFlag) {
14076                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14077                 } else {
14078                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14079                     if (appData.autoCallFlag) {
14080                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14081                         return TRUE;
14082                     }
14083                 }
14084             }
14085         }
14086     }
14087     return FALSE;
14088 }
14089
14090 void
14091 CheckTimeControl()
14092 {
14093     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14094         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14095
14096     /*
14097      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14098      */
14099     if ( !WhiteOnMove(forwardMostMove) )
14100         /* White made time control */
14101         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14102         /* [HGM] time odds: correct new time quota for time odds! */
14103                                             / WhitePlayer()->timeOdds;
14104       else
14105         /* Black made time control */
14106         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14107                                             / WhitePlayer()->other->timeOdds;
14108 }
14109
14110 void
14111 DisplayBothClocks()
14112 {
14113     int wom = gameMode == EditPosition ?
14114       !blackPlaysFirst : WhiteOnMove(currentMove);
14115     DisplayWhiteClock(whiteTimeRemaining, wom);
14116     DisplayBlackClock(blackTimeRemaining, !wom);
14117 }
14118
14119
14120 /* Timekeeping seems to be a portability nightmare.  I think everyone
14121    has ftime(), but I'm really not sure, so I'm including some ifdefs
14122    to use other calls if you don't.  Clocks will be less accurate if
14123    you have neither ftime nor gettimeofday.
14124 */
14125
14126 /* VS 2008 requires the #include outside of the function */
14127 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14128 #include <sys/timeb.h>
14129 #endif
14130
14131 /* Get the current time as a TimeMark */
14132 void
14133 GetTimeMark(tm)
14134      TimeMark *tm;
14135 {
14136 #if HAVE_GETTIMEOFDAY
14137
14138     struct timeval timeVal;
14139     struct timezone timeZone;
14140
14141     gettimeofday(&timeVal, &timeZone);
14142     tm->sec = (long) timeVal.tv_sec; 
14143     tm->ms = (int) (timeVal.tv_usec / 1000L);
14144
14145 #else /*!HAVE_GETTIMEOFDAY*/
14146 #if HAVE_FTIME
14147
14148 // include <sys/timeb.h> / moved to just above start of function
14149     struct timeb timeB;
14150
14151     ftime(&timeB);
14152     tm->sec = (long) timeB.time;
14153     tm->ms = (int) timeB.millitm;
14154
14155 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14156     tm->sec = (long) time(NULL);
14157     tm->ms = 0;
14158 #endif
14159 #endif
14160 }
14161
14162 /* Return the difference in milliseconds between two
14163    time marks.  We assume the difference will fit in a long!
14164 */
14165 long
14166 SubtractTimeMarks(tm2, tm1)
14167      TimeMark *tm2, *tm1;
14168 {
14169     return 1000L*(tm2->sec - tm1->sec) +
14170            (long) (tm2->ms - tm1->ms);
14171 }
14172
14173
14174 /*
14175  * Code to manage the game clocks.
14176  *
14177  * In tournament play, black starts the clock and then white makes a move.
14178  * We give the human user a slight advantage if he is playing white---the
14179  * clocks don't run until he makes his first move, so it takes zero time.
14180  * Also, we don't account for network lag, so we could get out of sync
14181  * with GNU Chess's clock -- but then, referees are always right.  
14182  */
14183
14184 static TimeMark tickStartTM;
14185 static long intendedTickLength;
14186
14187 long
14188 NextTickLength(timeRemaining)
14189      long timeRemaining;
14190 {
14191     long nominalTickLength, nextTickLength;
14192
14193     if (timeRemaining > 0L && timeRemaining <= 10000L)
14194       nominalTickLength = 100L;
14195     else
14196       nominalTickLength = 1000L;
14197     nextTickLength = timeRemaining % nominalTickLength;
14198     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14199
14200     return nextTickLength;
14201 }
14202
14203 /* Adjust clock one minute up or down */
14204 void
14205 AdjustClock(Boolean which, int dir)
14206 {
14207     if(which) blackTimeRemaining += 60000*dir;
14208     else      whiteTimeRemaining += 60000*dir;
14209     DisplayBothClocks();
14210 }
14211
14212 /* Stop clocks and reset to a fresh time control */
14213 void
14214 ResetClocks() 
14215 {
14216     (void) StopClockTimer();
14217     if (appData.icsActive) {
14218         whiteTimeRemaining = blackTimeRemaining = 0;
14219     } else if (searchTime) {
14220         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14221         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14222     } else { /* [HGM] correct new time quote for time odds */
14223         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14224         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14225     }
14226     if (whiteFlag || blackFlag) {
14227         DisplayTitle("");
14228         whiteFlag = blackFlag = FALSE;
14229     }
14230     DisplayBothClocks();
14231 }
14232
14233 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14234
14235 /* Decrement running clock by amount of time that has passed */
14236 void
14237 DecrementClocks()
14238 {
14239     long timeRemaining;
14240     long lastTickLength, fudge;
14241     TimeMark now;
14242
14243     if (!appData.clockMode) return;
14244     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14245         
14246     GetTimeMark(&now);
14247
14248     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14249
14250     /* Fudge if we woke up a little too soon */
14251     fudge = intendedTickLength - lastTickLength;
14252     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14253
14254     if (WhiteOnMove(forwardMostMove)) {
14255         if(whiteNPS >= 0) lastTickLength = 0;
14256         timeRemaining = whiteTimeRemaining -= lastTickLength;
14257         DisplayWhiteClock(whiteTimeRemaining - fudge,
14258                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14259     } else {
14260         if(blackNPS >= 0) lastTickLength = 0;
14261         timeRemaining = blackTimeRemaining -= lastTickLength;
14262         DisplayBlackClock(blackTimeRemaining - fudge,
14263                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14264     }
14265
14266     if (CheckFlags()) return;
14267         
14268     tickStartTM = now;
14269     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14270     StartClockTimer(intendedTickLength);
14271
14272     /* if the time remaining has fallen below the alarm threshold, sound the
14273      * alarm. if the alarm has sounded and (due to a takeback or time control
14274      * with increment) the time remaining has increased to a level above the
14275      * threshold, reset the alarm so it can sound again. 
14276      */
14277     
14278     if (appData.icsActive && appData.icsAlarm) {
14279
14280         /* make sure we are dealing with the user's clock */
14281         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14282                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14283            )) return;
14284
14285         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14286             alarmSounded = FALSE;
14287         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14288             PlayAlarmSound();
14289             alarmSounded = TRUE;
14290         }
14291     }
14292 }
14293
14294
14295 /* A player has just moved, so stop the previously running
14296    clock and (if in clock mode) start the other one.
14297    We redisplay both clocks in case we're in ICS mode, because
14298    ICS gives us an update to both clocks after every move.
14299    Note that this routine is called *after* forwardMostMove
14300    is updated, so the last fractional tick must be subtracted
14301    from the color that is *not* on move now.
14302 */
14303 void
14304 SwitchClocks(int newMoveNr)
14305 {
14306     long lastTickLength;
14307     TimeMark now;
14308     int flagged = FALSE;
14309
14310     GetTimeMark(&now);
14311
14312     if (StopClockTimer() && appData.clockMode) {
14313         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14314         if (!WhiteOnMove(forwardMostMove)) {
14315             if(blackNPS >= 0) lastTickLength = 0;
14316             blackTimeRemaining -= lastTickLength;
14317            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14318 //         if(pvInfoList[forwardMostMove-1].time == -1)
14319                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14320                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14321         } else {
14322            if(whiteNPS >= 0) lastTickLength = 0;
14323            whiteTimeRemaining -= lastTickLength;
14324            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14325 //         if(pvInfoList[forwardMostMove-1].time == -1)
14326                  pvInfoList[forwardMostMove-1].time = 
14327                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14328         }
14329         flagged = CheckFlags();
14330     }
14331     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14332     CheckTimeControl();
14333
14334     if (flagged || !appData.clockMode) return;
14335
14336     switch (gameMode) {
14337       case MachinePlaysBlack:
14338       case MachinePlaysWhite:
14339       case BeginningOfGame:
14340         if (pausing) return;
14341         break;
14342
14343       case EditGame:
14344       case PlayFromGameFile:
14345       case IcsExamining:
14346         return;
14347
14348       default:
14349         break;
14350     }
14351
14352     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14353         if(WhiteOnMove(forwardMostMove))
14354              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14355         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14356     }
14357
14358     tickStartTM = now;
14359     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14360       whiteTimeRemaining : blackTimeRemaining);
14361     StartClockTimer(intendedTickLength);
14362 }
14363         
14364
14365 /* Stop both clocks */
14366 void
14367 StopClocks()
14368 {       
14369     long lastTickLength;
14370     TimeMark now;
14371
14372     if (!StopClockTimer()) return;
14373     if (!appData.clockMode) return;
14374
14375     GetTimeMark(&now);
14376
14377     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14378     if (WhiteOnMove(forwardMostMove)) {
14379         if(whiteNPS >= 0) lastTickLength = 0;
14380         whiteTimeRemaining -= lastTickLength;
14381         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14382     } else {
14383         if(blackNPS >= 0) lastTickLength = 0;
14384         blackTimeRemaining -= lastTickLength;
14385         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14386     }
14387     CheckFlags();
14388 }
14389         
14390 /* Start clock of player on move.  Time may have been reset, so
14391    if clock is already running, stop and restart it. */
14392 void
14393 StartClocks()
14394 {
14395     (void) StopClockTimer(); /* in case it was running already */
14396     DisplayBothClocks();
14397     if (CheckFlags()) return;
14398
14399     if (!appData.clockMode) return;
14400     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14401
14402     GetTimeMark(&tickStartTM);
14403     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14404       whiteTimeRemaining : blackTimeRemaining);
14405
14406    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14407     whiteNPS = blackNPS = -1; 
14408     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14409        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14410         whiteNPS = first.nps;
14411     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14412        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14413         blackNPS = first.nps;
14414     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14415         whiteNPS = second.nps;
14416     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14417         blackNPS = second.nps;
14418     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14419
14420     StartClockTimer(intendedTickLength);
14421 }
14422
14423 char *
14424 TimeString(ms)
14425      long ms;
14426 {
14427     long second, minute, hour, day;
14428     char *sign = "";
14429     static char buf[32];
14430     
14431     if (ms > 0 && ms <= 9900) {
14432       /* convert milliseconds to tenths, rounding up */
14433       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14434
14435       sprintf(buf, " %03.1f ", tenths/10.0);
14436       return buf;
14437     }
14438
14439     /* convert milliseconds to seconds, rounding up */
14440     /* use floating point to avoid strangeness of integer division
14441        with negative dividends on many machines */
14442     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14443
14444     if (second < 0) {
14445         sign = "-";
14446         second = -second;
14447     }
14448     
14449     day = second / (60 * 60 * 24);
14450     second = second % (60 * 60 * 24);
14451     hour = second / (60 * 60);
14452     second = second % (60 * 60);
14453     minute = second / 60;
14454     second = second % 60;
14455     
14456     if (day > 0)
14457       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14458               sign, day, hour, minute, second);
14459     else if (hour > 0)
14460       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14461     else
14462       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14463     
14464     return buf;
14465 }
14466
14467
14468 /*
14469  * This is necessary because some C libraries aren't ANSI C compliant yet.
14470  */
14471 char *
14472 StrStr(string, match)
14473      char *string, *match;
14474 {
14475     int i, length;
14476     
14477     length = strlen(match);
14478     
14479     for (i = strlen(string) - length; i >= 0; i--, string++)
14480       if (!strncmp(match, string, length))
14481         return string;
14482     
14483     return NULL;
14484 }
14485
14486 char *
14487 StrCaseStr(string, match)
14488      char *string, *match;
14489 {
14490     int i, j, length;
14491     
14492     length = strlen(match);
14493     
14494     for (i = strlen(string) - length; i >= 0; i--, string++) {
14495         for (j = 0; j < length; j++) {
14496             if (ToLower(match[j]) != ToLower(string[j]))
14497               break;
14498         }
14499         if (j == length) return string;
14500     }
14501
14502     return NULL;
14503 }
14504
14505 #ifndef _amigados
14506 int
14507 StrCaseCmp(s1, s2)
14508      char *s1, *s2;
14509 {
14510     char c1, c2;
14511     
14512     for (;;) {
14513         c1 = ToLower(*s1++);
14514         c2 = ToLower(*s2++);
14515         if (c1 > c2) return 1;
14516         if (c1 < c2) return -1;
14517         if (c1 == NULLCHAR) return 0;
14518     }
14519 }
14520
14521
14522 int
14523 ToLower(c)
14524      int c;
14525 {
14526     return isupper(c) ? tolower(c) : c;
14527 }
14528
14529
14530 int
14531 ToUpper(c)
14532      int c;
14533 {
14534     return islower(c) ? toupper(c) : c;
14535 }
14536 #endif /* !_amigados    */
14537
14538 char *
14539 StrSave(s)
14540      char *s;
14541 {
14542     char *ret;
14543
14544     if ((ret = (char *) malloc(strlen(s) + 1))) {
14545         strcpy(ret, s);
14546     }
14547     return ret;
14548 }
14549
14550 char *
14551 StrSavePtr(s, savePtr)
14552      char *s, **savePtr;
14553 {
14554     if (*savePtr) {
14555         free(*savePtr);
14556     }
14557     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14558         strcpy(*savePtr, s);
14559     }
14560     return(*savePtr);
14561 }
14562
14563 char *
14564 PGNDate()
14565 {
14566     time_t clock;
14567     struct tm *tm;
14568     char buf[MSG_SIZ];
14569
14570     clock = time((time_t *)NULL);
14571     tm = localtime(&clock);
14572     sprintf(buf, "%04d.%02d.%02d",
14573             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14574     return StrSave(buf);
14575 }
14576
14577
14578 char *
14579 PositionToFEN(move, overrideCastling)
14580      int move;
14581      char *overrideCastling;
14582 {
14583     int i, j, fromX, fromY, toX, toY;
14584     int whiteToPlay;
14585     char buf[128];
14586     char *p, *q;
14587     int emptycount;
14588     ChessSquare piece;
14589
14590     whiteToPlay = (gameMode == EditPosition) ?
14591       !blackPlaysFirst : (move % 2 == 0);
14592     p = buf;
14593
14594     /* Piece placement data */
14595     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14596         emptycount = 0;
14597         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14598             if (boards[move][i][j] == EmptySquare) {
14599                 emptycount++;
14600             } else { ChessSquare piece = boards[move][i][j];
14601                 if (emptycount > 0) {
14602                     if(emptycount<10) /* [HGM] can be >= 10 */
14603                         *p++ = '0' + emptycount;
14604                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14605                     emptycount = 0;
14606                 }
14607                 if(PieceToChar(piece) == '+') {
14608                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14609                     *p++ = '+';
14610                     piece = (ChessSquare)(DEMOTED piece);
14611                 } 
14612                 *p++ = PieceToChar(piece);
14613                 if(p[-1] == '~') {
14614                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14615                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14616                     *p++ = '~';
14617                 }
14618             }
14619         }
14620         if (emptycount > 0) {
14621             if(emptycount<10) /* [HGM] can be >= 10 */
14622                 *p++ = '0' + emptycount;
14623             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14624             emptycount = 0;
14625         }
14626         *p++ = '/';
14627     }
14628     *(p - 1) = ' ';
14629
14630     /* [HGM] print Crazyhouse or Shogi holdings */
14631     if( gameInfo.holdingsWidth ) {
14632         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14633         q = p;
14634         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14635             piece = boards[move][i][BOARD_WIDTH-1];
14636             if( piece != EmptySquare )
14637               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14638                   *p++ = PieceToChar(piece);
14639         }
14640         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14641             piece = boards[move][BOARD_HEIGHT-i-1][0];
14642             if( piece != EmptySquare )
14643               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14644                   *p++ = PieceToChar(piece);
14645         }
14646
14647         if( q == p ) *p++ = '-';
14648         *p++ = ']';
14649         *p++ = ' ';
14650     }
14651
14652     /* Active color */
14653     *p++ = whiteToPlay ? 'w' : 'b';
14654     *p++ = ' ';
14655
14656   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14657     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14658   } else {
14659   if(nrCastlingRights) {
14660      q = p;
14661      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14662        /* [HGM] write directly from rights */
14663            if(boards[move][CASTLING][2] != NoRights &&
14664               boards[move][CASTLING][0] != NoRights   )
14665                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14666            if(boards[move][CASTLING][2] != NoRights &&
14667               boards[move][CASTLING][1] != NoRights   )
14668                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14669            if(boards[move][CASTLING][5] != NoRights &&
14670               boards[move][CASTLING][3] != NoRights   )
14671                 *p++ = boards[move][CASTLING][3] + AAA;
14672            if(boards[move][CASTLING][5] != NoRights &&
14673               boards[move][CASTLING][4] != NoRights   )
14674                 *p++ = boards[move][CASTLING][4] + AAA;
14675      } else {
14676
14677         /* [HGM] write true castling rights */
14678         if( nrCastlingRights == 6 ) {
14679             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14680                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14681             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14682                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14683             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14684                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14685             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14686                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14687         }
14688      }
14689      if (q == p) *p++ = '-'; /* No castling rights */
14690      *p++ = ' ';
14691   }
14692
14693   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14694      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14695     /* En passant target square */
14696     if (move > backwardMostMove) {
14697         fromX = moveList[move - 1][0] - AAA;
14698         fromY = moveList[move - 1][1] - ONE;
14699         toX = moveList[move - 1][2] - AAA;
14700         toY = moveList[move - 1][3] - ONE;
14701         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14702             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14703             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14704             fromX == toX) {
14705             /* 2-square pawn move just happened */
14706             *p++ = toX + AAA;
14707             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14708         } else {
14709             *p++ = '-';
14710         }
14711     } else if(move == backwardMostMove) {
14712         // [HGM] perhaps we should always do it like this, and forget the above?
14713         if((signed char)boards[move][EP_STATUS] >= 0) {
14714             *p++ = boards[move][EP_STATUS] + AAA;
14715             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14716         } else {
14717             *p++ = '-';
14718         }
14719     } else {
14720         *p++ = '-';
14721     }
14722     *p++ = ' ';
14723   }
14724   }
14725
14726     /* [HGM] find reversible plies */
14727     {   int i = 0, j=move;
14728
14729         if (appData.debugMode) { int k;
14730             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14731             for(k=backwardMostMove; k<=forwardMostMove; k++)
14732                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14733
14734         }
14735
14736         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14737         if( j == backwardMostMove ) i += initialRulePlies;
14738         sprintf(p, "%d ", i);
14739         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14740     }
14741     /* Fullmove number */
14742     sprintf(p, "%d", (move / 2) + 1);
14743     
14744     return StrSave(buf);
14745 }
14746
14747 Boolean
14748 ParseFEN(board, blackPlaysFirst, fen)
14749     Board board;
14750      int *blackPlaysFirst;
14751      char *fen;
14752 {
14753     int i, j;
14754     char *p;
14755     int emptycount;
14756     ChessSquare piece;
14757
14758     p = fen;
14759
14760     /* [HGM] by default clear Crazyhouse holdings, if present */
14761     if(gameInfo.holdingsWidth) {
14762        for(i=0; i<BOARD_HEIGHT; i++) {
14763            board[i][0]             = EmptySquare; /* black holdings */
14764            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14765            board[i][1]             = (ChessSquare) 0; /* black counts */
14766            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14767        }
14768     }
14769
14770     /* Piece placement data */
14771     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14772         j = 0;
14773         for (;;) {
14774             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14775                 if (*p == '/') p++;
14776                 emptycount = gameInfo.boardWidth - j;
14777                 while (emptycount--)
14778                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14779                 break;
14780 #if(BOARD_FILES >= 10)
14781             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14782                 p++; emptycount=10;
14783                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14784                 while (emptycount--)
14785                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14786 #endif
14787             } else if (isdigit(*p)) {
14788                 emptycount = *p++ - '0';
14789                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14790                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14791                 while (emptycount--)
14792                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14793             } else if (*p == '+' || isalpha(*p)) {
14794                 if (j >= gameInfo.boardWidth) return FALSE;
14795                 if(*p=='+') {
14796                     piece = CharToPiece(*++p);
14797                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14798                     piece = (ChessSquare) (PROMOTED piece ); p++;
14799                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14800                 } else piece = CharToPiece(*p++);
14801
14802                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14803                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14804                     piece = (ChessSquare) (PROMOTED piece);
14805                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14806                     p++;
14807                 }
14808                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14809             } else {
14810                 return FALSE;
14811             }
14812         }
14813     }
14814     while (*p == '/' || *p == ' ') p++;
14815
14816     /* [HGM] look for Crazyhouse holdings here */
14817     while(*p==' ') p++;
14818     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14819         if(*p == '[') p++;
14820         if(*p == '-' ) *p++; /* empty holdings */ else {
14821             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14822             /* if we would allow FEN reading to set board size, we would   */
14823             /* have to add holdings and shift the board read so far here   */
14824             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14825                 *p++;
14826                 if((int) piece >= (int) BlackPawn ) {
14827                     i = (int)piece - (int)BlackPawn;
14828                     i = PieceToNumber((ChessSquare)i);
14829                     if( i >= gameInfo.holdingsSize ) return FALSE;
14830                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14831                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14832                 } else {
14833                     i = (int)piece - (int)WhitePawn;
14834                     i = PieceToNumber((ChessSquare)i);
14835                     if( i >= gameInfo.holdingsSize ) return FALSE;
14836                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14837                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14838                 }
14839             }
14840         }
14841         if(*p == ']') *p++;
14842     }
14843
14844     while(*p == ' ') p++;
14845
14846     /* Active color */
14847     switch (*p++) {
14848       case 'w':
14849         *blackPlaysFirst = FALSE;
14850         break;
14851       case 'b': 
14852         *blackPlaysFirst = TRUE;
14853         break;
14854       default:
14855         return FALSE;
14856     }
14857
14858     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14859     /* return the extra info in global variiables             */
14860
14861     /* set defaults in case FEN is incomplete */
14862     board[EP_STATUS] = EP_UNKNOWN;
14863     for(i=0; i<nrCastlingRights; i++ ) {
14864         board[CASTLING][i] =
14865             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14866     }   /* assume possible unless obviously impossible */
14867     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14868     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14869     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14870                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14871     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14872     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14873     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14874                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14875     FENrulePlies = 0;
14876
14877     while(*p==' ') p++;
14878     if(nrCastlingRights) {
14879       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14880           /* castling indicator present, so default becomes no castlings */
14881           for(i=0; i<nrCastlingRights; i++ ) {
14882                  board[CASTLING][i] = NoRights;
14883           }
14884       }
14885       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14886              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14887              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14888              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14889         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14890
14891         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14892             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14893             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14894         }
14895         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14896             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14897         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14898                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14899         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14900                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14901         switch(c) {
14902           case'K':
14903               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14904               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14905               board[CASTLING][2] = whiteKingFile;
14906               break;
14907           case'Q':
14908               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14909               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14910               board[CASTLING][2] = whiteKingFile;
14911               break;
14912           case'k':
14913               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14914               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14915               board[CASTLING][5] = blackKingFile;
14916               break;
14917           case'q':
14918               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14919               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14920               board[CASTLING][5] = blackKingFile;
14921           case '-':
14922               break;
14923           default: /* FRC castlings */
14924               if(c >= 'a') { /* black rights */
14925                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14926                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14927                   if(i == BOARD_RGHT) break;
14928                   board[CASTLING][5] = i;
14929                   c -= AAA;
14930                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14931                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14932                   if(c > i)
14933                       board[CASTLING][3] = c;
14934                   else
14935                       board[CASTLING][4] = c;
14936               } else { /* white rights */
14937                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14938                     if(board[0][i] == WhiteKing) break;
14939                   if(i == BOARD_RGHT) break;
14940                   board[CASTLING][2] = i;
14941                   c -= AAA - 'a' + 'A';
14942                   if(board[0][c] >= WhiteKing) break;
14943                   if(c > i)
14944                       board[CASTLING][0] = c;
14945                   else
14946                       board[CASTLING][1] = c;
14947               }
14948         }
14949       }
14950       for(i=0; i<nrCastlingRights; i++)
14951         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14952     if (appData.debugMode) {
14953         fprintf(debugFP, "FEN castling rights:");
14954         for(i=0; i<nrCastlingRights; i++)
14955         fprintf(debugFP, " %d", board[CASTLING][i]);
14956         fprintf(debugFP, "\n");
14957     }
14958
14959       while(*p==' ') p++;
14960     }
14961
14962     /* read e.p. field in games that know e.p. capture */
14963     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14964        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14965       if(*p=='-') {
14966         p++; board[EP_STATUS] = EP_NONE;
14967       } else {
14968          char c = *p++ - AAA;
14969
14970          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14971          if(*p >= '0' && *p <='9') *p++;
14972          board[EP_STATUS] = c;
14973       }
14974     }
14975
14976
14977     if(sscanf(p, "%d", &i) == 1) {
14978         FENrulePlies = i; /* 50-move ply counter */
14979         /* (The move number is still ignored)    */
14980     }
14981
14982     return TRUE;
14983 }
14984       
14985 void
14986 EditPositionPasteFEN(char *fen)
14987 {
14988   if (fen != NULL) {
14989     Board initial_position;
14990
14991     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14992       DisplayError(_("Bad FEN position in clipboard"), 0);
14993       return ;
14994     } else {
14995       int savedBlackPlaysFirst = blackPlaysFirst;
14996       EditPositionEvent();
14997       blackPlaysFirst = savedBlackPlaysFirst;
14998       CopyBoard(boards[0], initial_position);
14999       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15000       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15001       DisplayBothClocks();
15002       DrawPosition(FALSE, boards[currentMove]);
15003     }
15004   }
15005 }
15006
15007 static char cseq[12] = "\\   ";
15008
15009 Boolean set_cont_sequence(char *new_seq)
15010 {
15011     int len;
15012     Boolean ret;
15013
15014     // handle bad attempts to set the sequence
15015         if (!new_seq)
15016                 return 0; // acceptable error - no debug
15017
15018     len = strlen(new_seq);
15019     ret = (len > 0) && (len < sizeof(cseq));
15020     if (ret)
15021         strcpy(cseq, new_seq);
15022     else if (appData.debugMode)
15023         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15024     return ret;
15025 }
15026
15027 /*
15028     reformat a source message so words don't cross the width boundary.  internal
15029     newlines are not removed.  returns the wrapped size (no null character unless
15030     included in source message).  If dest is NULL, only calculate the size required
15031     for the dest buffer.  lp argument indicats line position upon entry, and it's
15032     passed back upon exit.
15033 */
15034 int wrap(char *dest, char *src, int count, int width, int *lp)
15035 {
15036     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15037
15038     cseq_len = strlen(cseq);
15039     old_line = line = *lp;
15040     ansi = len = clen = 0;
15041
15042     for (i=0; i < count; i++)
15043     {
15044         if (src[i] == '\033')
15045             ansi = 1;
15046
15047         // if we hit the width, back up
15048         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15049         {
15050             // store i & len in case the word is too long
15051             old_i = i, old_len = len;
15052
15053             // find the end of the last word
15054             while (i && src[i] != ' ' && src[i] != '\n')
15055             {
15056                 i--;
15057                 len--;
15058             }
15059
15060             // word too long?  restore i & len before splitting it
15061             if ((old_i-i+clen) >= width)
15062             {
15063                 i = old_i;
15064                 len = old_len;
15065             }
15066
15067             // extra space?
15068             if (i && src[i-1] == ' ')
15069                 len--;
15070
15071             if (src[i] != ' ' && src[i] != '\n')
15072             {
15073                 i--;
15074                 if (len)
15075                     len--;
15076             }
15077
15078             // now append the newline and continuation sequence
15079             if (dest)
15080                 dest[len] = '\n';
15081             len++;
15082             if (dest)
15083                 strncpy(dest+len, cseq, cseq_len);
15084             len += cseq_len;
15085             line = cseq_len;
15086             clen = cseq_len;
15087             continue;
15088         }
15089
15090         if (dest)
15091             dest[len] = src[i];
15092         len++;
15093         if (!ansi)
15094             line++;
15095         if (src[i] == '\n')
15096             line = 0;
15097         if (src[i] == 'm')
15098             ansi = 0;
15099     }
15100     if (dest && appData.debugMode)
15101     {
15102         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15103             count, width, line, len, *lp);
15104         show_bytes(debugFP, src, count);
15105         fprintf(debugFP, "\ndest: ");
15106         show_bytes(debugFP, dest, len);
15107         fprintf(debugFP, "\n");
15108     }
15109     *lp = dest ? line : old_line;
15110
15111     return len;
15112 }
15113
15114 // [HGM] vari: routines for shelving variations
15115
15116 void 
15117 PushTail(int firstMove, int lastMove)
15118 {
15119         int i, j, nrMoves = lastMove - firstMove;
15120
15121         if(appData.icsActive) { // only in local mode
15122                 forwardMostMove = currentMove; // mimic old ICS behavior
15123                 return;
15124         }
15125         if(storedGames >= MAX_VARIATIONS-1) return;
15126
15127         // push current tail of game on stack
15128         savedResult[storedGames] = gameInfo.result;
15129         savedDetails[storedGames] = gameInfo.resultDetails;
15130         gameInfo.resultDetails = NULL;
15131         savedFirst[storedGames] = firstMove;
15132         savedLast [storedGames] = lastMove;
15133         savedFramePtr[storedGames] = framePtr;
15134         framePtr -= nrMoves; // reserve space for the boards
15135         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15136             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15137             for(j=0; j<MOVE_LEN; j++)
15138                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15139             for(j=0; j<2*MOVE_LEN; j++)
15140                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15141             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15142             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15143             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15144             pvInfoList[firstMove+i-1].depth = 0;
15145             commentList[framePtr+i] = commentList[firstMove+i];
15146             commentList[firstMove+i] = NULL;
15147         }
15148
15149         storedGames++;
15150         forwardMostMove = firstMove; // truncate game so we can start variation
15151         if(storedGames == 1) GreyRevert(FALSE);
15152 }
15153
15154 Boolean
15155 PopTail(Boolean annotate)
15156 {
15157         int i, j, nrMoves;
15158         char buf[8000], moveBuf[20];
15159
15160         if(appData.icsActive) return FALSE; // only in local mode
15161         if(!storedGames) return FALSE; // sanity
15162         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15163
15164         storedGames--;
15165         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15166         nrMoves = savedLast[storedGames] - currentMove;
15167         if(annotate) {
15168                 int cnt = 10;
15169                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15170                 else strcpy(buf, "(");
15171                 for(i=currentMove; i<forwardMostMove; i++) {
15172                         if(WhiteOnMove(i))
15173                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15174                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15175                         strcat(buf, moveBuf);
15176                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15177                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15178                 }
15179                 strcat(buf, ")");
15180         }
15181         for(i=1; i<=nrMoves; i++) { // copy last variation back
15182             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15183             for(j=0; j<MOVE_LEN; j++)
15184                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15185             for(j=0; j<2*MOVE_LEN; j++)
15186                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15187             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15188             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15189             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15190             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15191             commentList[currentMove+i] = commentList[framePtr+i];
15192             commentList[framePtr+i] = NULL;
15193         }
15194         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15195         framePtr = savedFramePtr[storedGames];
15196         gameInfo.result = savedResult[storedGames];
15197         if(gameInfo.resultDetails != NULL) {
15198             free(gameInfo.resultDetails);
15199       }
15200         gameInfo.resultDetails = savedDetails[storedGames];
15201         forwardMostMove = currentMove + nrMoves;
15202         if(storedGames == 0) GreyRevert(TRUE);
15203         return TRUE;
15204 }
15205
15206 void 
15207 CleanupTail()
15208 {       // remove all shelved variations
15209         int i;
15210         for(i=0; i<storedGames; i++) {
15211             if(savedDetails[i])
15212                 free(savedDetails[i]);
15213             savedDetails[i] = NULL;
15214         }
15215         for(i=framePtr; i<MAX_MOVES; i++) {
15216                 if(commentList[i]) free(commentList[i]);
15217                 commentList[i] = NULL;
15218         }
15219         framePtr = MAX_MOVES-1;
15220         storedGames = 0;
15221 }
15222
15223 void
15224 LoadVariation(int index, char *text)
15225 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15226         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15227         int level = 0, move;
15228
15229         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15230         // first find outermost bracketing variation
15231         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15232             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15233                 if(*p == '{') wait = '}'; else
15234                 if(*p == '[') wait = ']'; else
15235                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15236                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15237             }
15238             if(*p == wait) wait = NULLCHAR; // closing ]} found
15239             p++;
15240         }
15241         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15242         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15243         end[1] = NULLCHAR; // clip off comment beyond variation
15244         ToNrEvent(currentMove-1);
15245         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15246         // kludge: use ParsePV() to append variation to game
15247         move = currentMove;
15248         ParsePV(start, TRUE);
15249         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15250         ClearPremoveHighlights();
15251         CommentPopDown();
15252         ToNrEvent(currentMove+1);
15253 }
15254