Allow escape sequences in telluser(error) messages
[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;
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);
6330                 if (appData.highlightDragging) {
6331                     SetHighlights(x, y, -1, -1);
6332                 }
6333             }
6334         }
6335         return;
6336       }
6337     }
6338
6339     /* fromX != -1 */
6340     if (clickType == Press && gameMode != EditPosition) {
6341         ChessSquare fromP;
6342         ChessSquare toP;
6343         int frc;
6344
6345         // ignore off-board to clicks
6346         if(y < 0 || x < 0) return;
6347
6348         /* Check if clicking again on the same color piece */
6349         fromP = boards[currentMove][fromY][fromX];
6350         toP = boards[currentMove][y][x];
6351         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6352         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6353              WhitePawn <= toP && toP <= WhiteKing &&
6354              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6355              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6356             (BlackPawn <= fromP && fromP <= BlackKing && 
6357              BlackPawn <= toP && toP <= BlackKing &&
6358              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6359              !(fromP == BlackKing && toP == BlackRook && frc))) {
6360             /* Clicked again on same color piece -- changed his mind */
6361             second = (x == fromX && y == fromY);
6362            if(!second || !OnlyMove(&x, &y, TRUE)) {
6363             if (appData.highlightDragging) {
6364                 SetHighlights(x, y, -1, -1);
6365             } else {
6366                 ClearHighlights();
6367             }
6368             if (OKToStartUserMove(x, y)) {
6369                 fromX = x;
6370                 fromY = y;
6371                 MarkTargetSquares(0);
6372                 DragPieceBegin(xPix, yPix);
6373             }
6374             return;
6375            }
6376         }
6377         // ignore clicks on holdings
6378         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6379     }
6380
6381     if (clickType == Release && x == fromX && y == fromY) {
6382         DragPieceEnd(xPix, yPix);
6383         if (appData.animateDragging) {
6384             /* Undo animation damage if any */
6385             DrawPosition(FALSE, NULL);
6386         }
6387         if (second) {
6388             /* Second up/down in same square; just abort move */
6389             second = 0;
6390             fromX = fromY = -1;
6391             ClearHighlights();
6392             gotPremove = 0;
6393             ClearPremoveHighlights();
6394         } else {
6395             /* First upclick in same square; start click-click mode */
6396             SetHighlights(x, y, -1, -1);
6397         }
6398         return;
6399     }
6400
6401     /* we now have a different from- and (possibly off-board) to-square */
6402     /* Completed move */
6403     toX = x;
6404     toY = y;
6405     saveAnimate = appData.animate;
6406     if (clickType == Press) {
6407         /* Finish clickclick move */
6408         if (appData.animate || appData.highlightLastMove) {
6409             SetHighlights(fromX, fromY, toX, toY);
6410         } else {
6411             ClearHighlights();
6412         }
6413     } else {
6414         /* Finish drag move */
6415         if (appData.highlightLastMove) {
6416             SetHighlights(fromX, fromY, toX, toY);
6417         } else {
6418             ClearHighlights();
6419         }
6420         DragPieceEnd(xPix, yPix);
6421         /* Don't animate move and drag both */
6422         appData.animate = FALSE;
6423     }
6424
6425     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6426     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6427         ChessSquare piece = boards[currentMove][fromY][fromX];
6428         if(gameMode == EditPosition && piece != EmptySquare &&
6429            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6430             int n;
6431              
6432             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6433                 n = PieceToNumber(piece - (int)BlackPawn);
6434                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6435                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6436                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6437             } else
6438             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6439                 n = PieceToNumber(piece);
6440                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6441                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6442                 boards[currentMove][n][BOARD_WIDTH-2]++;
6443             }
6444             boards[currentMove][fromY][fromX] = EmptySquare;
6445         }
6446         ClearHighlights();
6447         fromX = fromY = -1;
6448         DrawPosition(TRUE, boards[currentMove]);
6449         return;
6450     }
6451
6452     // off-board moves should not be highlighted
6453     if(x < 0 || x < 0) ClearHighlights();
6454
6455     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6456         SetHighlights(fromX, fromY, toX, toY);
6457         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6458             // [HGM] super: promotion to captured piece selected from holdings
6459             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6460             promotionChoice = TRUE;
6461             // kludge follows to temporarily execute move on display, without promoting yet
6462             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6463             boards[currentMove][toY][toX] = p;
6464             DrawPosition(FALSE, boards[currentMove]);
6465             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6466             boards[currentMove][toY][toX] = q;
6467             DisplayMessage("Click in holdings to choose piece", "");
6468             return;
6469         }
6470         PromotionPopUp();
6471     } else {
6472         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6473         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6474         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6475         fromX = fromY = -1;
6476     }
6477     appData.animate = saveAnimate;
6478     if (appData.animate || appData.animateDragging) {
6479         /* Undo animation damage if needed */
6480         DrawPosition(FALSE, NULL);
6481     }
6482 }
6483
6484 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6485 {   // front-end-free part taken out of PieceMenuPopup
6486     int whichMenu; int xSqr, ySqr;
6487
6488     if(seekGraphUp) { // [HGM] seekgraph
6489         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6490         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6491         return -2;
6492     }
6493
6494     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6495          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6496         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6497         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6498         if(action == Press)   {
6499             originalFlip = flipView;
6500             flipView = !flipView; // temporarily flip board to see game from partners perspective
6501             DrawPosition(TRUE, partnerBoard);
6502             DisplayMessage(partnerStatus, "");
6503             partnerUp = TRUE;
6504         } else if(action == Release) {
6505             flipView = originalFlip;
6506             DrawPosition(TRUE, boards[currentMove]);
6507             partnerUp = FALSE;
6508         }
6509         return -2;
6510     }
6511
6512     xSqr = EventToSquare(x, BOARD_WIDTH);
6513     ySqr = EventToSquare(y, BOARD_HEIGHT);
6514     if (action == Release) UnLoadPV(); // [HGM] pv
6515     if (action != Press) return -2; // return code to be ignored
6516     switch (gameMode) {
6517       case IcsExamining:
6518         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6519       case EditPosition:
6520         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6521         if (xSqr < 0 || ySqr < 0) return -1;\r
6522         whichMenu = 0; // edit-position menu
6523         break;
6524       case IcsObserving:
6525         if(!appData.icsEngineAnalyze) return -1;
6526       case IcsPlayingWhite:
6527       case IcsPlayingBlack:
6528         if(!appData.zippyPlay) goto noZip;
6529       case AnalyzeMode:
6530       case AnalyzeFile:
6531       case MachinePlaysWhite:
6532       case MachinePlaysBlack:
6533       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6534         if (!appData.dropMenu) {
6535           LoadPV(x, y);
6536           return 2; // flag front-end to grab mouse events
6537         }
6538         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6539            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6540       case EditGame:
6541       noZip:
6542         if (xSqr < 0 || ySqr < 0) return -1;
6543         if (!appData.dropMenu || appData.testLegality &&
6544             gameInfo.variant != VariantBughouse &&
6545             gameInfo.variant != VariantCrazyhouse) return -1;
6546         whichMenu = 1; // drop menu
6547         break;
6548       default:
6549         return -1;
6550     }
6551
6552     if (((*fromX = xSqr) < 0) ||
6553         ((*fromY = ySqr) < 0)) {
6554         *fromX = *fromY = -1;
6555         return -1;
6556     }
6557     if (flipView)
6558       *fromX = BOARD_WIDTH - 1 - *fromX;
6559     else
6560       *fromY = BOARD_HEIGHT - 1 - *fromY;
6561
6562     return whichMenu;
6563 }
6564
6565 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6566 {
6567 //    char * hint = lastHint;
6568     FrontEndProgramStats stats;
6569
6570     stats.which = cps == &first ? 0 : 1;
6571     stats.depth = cpstats->depth;
6572     stats.nodes = cpstats->nodes;
6573     stats.score = cpstats->score;
6574     stats.time = cpstats->time;
6575     stats.pv = cpstats->movelist;
6576     stats.hint = lastHint;
6577     stats.an_move_index = 0;
6578     stats.an_move_count = 0;
6579
6580     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6581         stats.hint = cpstats->move_name;
6582         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6583         stats.an_move_count = cpstats->nr_moves;
6584     }
6585
6586     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6587
6588     SetProgramStats( &stats );
6589 }
6590
6591 int
6592 Adjudicate(ChessProgramState *cps)
6593 {       // [HGM] some adjudications useful with buggy engines
6594         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6595         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6596         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6597         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6598         int k, count = 0; static int bare = 1;
6599         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6600         Boolean canAdjudicate = !appData.icsActive;
6601
6602         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6603         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6604             if( appData.testLegality )
6605             {   /* [HGM] Some more adjudications for obstinate engines */
6606                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6607                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6608                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6609                 static int moveCount = 6;
6610                 ChessMove result;
6611                 char *reason = NULL;
6612
6613                 /* Count what is on board. */
6614                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6615                 {   ChessSquare p = boards[forwardMostMove][i][j];
6616                     int m=i;
6617
6618                     switch((int) p)
6619                     {   /* count B,N,R and other of each side */
6620                         case WhiteKing:
6621                         case BlackKing:
6622                              NrK++; break; // [HGM] atomic: count Kings
6623                         case WhiteKnight:
6624                              NrWN++; break;
6625                         case WhiteBishop:
6626                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6627                              bishopsColor |= 1 << ((i^j)&1);
6628                              NrWB++; break;
6629                         case BlackKnight:
6630                              NrBN++; break;
6631                         case BlackBishop:
6632                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6633                              bishopsColor |= 1 << ((i^j)&1);
6634                              NrBB++; break;
6635                         case WhiteRook:
6636                              NrWR++; break;
6637                         case BlackRook:
6638                              NrBR++; break;
6639                         case WhiteQueen:
6640                              NrWQ++; break;
6641                         case BlackQueen:
6642                              NrBQ++; break;
6643                         case EmptySquare: 
6644                              break;
6645                         case BlackPawn:
6646                              m = 7-i;
6647                         case WhitePawn:
6648                              PawnAdvance += m; NrPawns++;
6649                     }
6650                     NrPieces += (p != EmptySquare);
6651                     NrW += ((int)p < (int)BlackPawn);
6652                     if(gameInfo.variant == VariantXiangqi && 
6653                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6654                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6655                         NrW -= ((int)p < (int)BlackPawn);
6656                     }
6657                 }
6658
6659                 /* Some material-based adjudications that have to be made before stalemate test */
6660                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6661                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6662                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6663                      if(canAdjudicate && appData.checkMates) {
6664                          if(engineOpponent)
6665                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6666                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6667                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6668                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6669                          return 1;
6670                      }
6671                 }
6672
6673                 /* Bare King in Shatranj (loses) or Losers (wins) */
6674                 if( NrW == 1 || NrPieces - NrW == 1) {
6675                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6676                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6677                      if(canAdjudicate && appData.checkMates) {
6678                          if(engineOpponent)
6679                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6680                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6681                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6682                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6683                          return 1;
6684                      }
6685                   } else
6686                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6687                   {    /* bare King */
6688                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6689                         if(canAdjudicate && appData.checkMates) {
6690                             /* but only adjudicate if adjudication enabled */
6691                             if(engineOpponent)
6692                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6693                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6694                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6695                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6696                             return 1;
6697                         }
6698                   }
6699                 } else bare = 1;
6700
6701
6702             // don't wait for engine to announce game end if we can judge ourselves
6703             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6704               case MT_CHECK:
6705                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6706                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6707                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6708                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6709                             checkCnt++;
6710                         if(checkCnt >= 2) {
6711                             reason = "Xboard adjudication: 3rd check";
6712                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6713                             break;
6714                         }
6715                     }
6716                 }
6717               case MT_NONE:
6718               default:
6719                 break;
6720               case MT_STALEMATE:
6721               case MT_STAINMATE:
6722                 reason = "Xboard adjudication: Stalemate";
6723                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6724                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6725                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6726                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6727                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6728                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6729                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6730                                                                         EP_CHECKMATE : EP_WINS);
6731                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6732                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6733                 }
6734                 break;
6735               case MT_CHECKMATE:
6736                 reason = "Xboard adjudication: Checkmate";
6737                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6738                 break;
6739             }
6740
6741                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6742                     case EP_STALEMATE:
6743                         result = GameIsDrawn; break;
6744                     case EP_CHECKMATE:
6745                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6746                     case EP_WINS:
6747                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6748                     default:
6749                         result = (ChessMove) 0;
6750                 }
6751                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6752                     if(engineOpponent)
6753                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6754                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6755                     GameEnds( result, reason, GE_XBOARD );
6756                     return 1;
6757                 }
6758
6759                 /* Next absolutely insufficient mating material. */
6760                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6761                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6762                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6763                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6764                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6765
6766                      /* always flag draws, for judging claims */
6767                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6768
6769                      if(canAdjudicate && appData.materialDraws) {
6770                          /* but only adjudicate them if adjudication enabled */
6771                          if(engineOpponent) {
6772                            SendToProgram("force\n", engineOpponent); // suppress reply
6773                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6774                          }
6775                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6776                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6777                          return 1;
6778                      }
6779                 }
6780
6781                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6782                 if(NrPieces == 4 && 
6783                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6784                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6785                    || NrWN==2 || NrBN==2     /* KNNK */
6786                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6787                   ) ) {
6788                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6789                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6790                           if(engineOpponent) {
6791                             SendToProgram("force\n", engineOpponent); // suppress reply
6792                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6793                           }
6794                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6796                           return 1;
6797                      }
6798                 } else moveCount = 6;
6799             }
6800         }
6801           
6802         if (appData.debugMode) { int i;
6803             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6804                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6805                     appData.drawRepeats);
6806             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6807               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6808             
6809         }
6810
6811         // Repetition draws and 50-move rule can be applied independently of legality testing
6812
6813                 /* Check for rep-draws */
6814                 count = 0;
6815                 for(k = forwardMostMove-2;
6816                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6817                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6818                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6819                     k-=2)
6820                 {   int rights=0;
6821                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6822                         /* compare castling rights */
6823                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6824                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6825                                 rights++; /* King lost rights, while rook still had them */
6826                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6827                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6828                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6829                                    rights++; /* but at least one rook lost them */
6830                         }
6831                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6832                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6833                                 rights++; 
6834                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6835                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6836                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6837                                    rights++;
6838                         }
6839                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6840                             && appData.drawRepeats > 1) {
6841                              /* adjudicate after user-specified nr of repeats */
6842                              if(engineOpponent) {
6843                                SendToProgram("force\n", engineOpponent); // suppress reply
6844                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6845                              }
6846                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6847                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6848                                 // [HGM] xiangqi: check for forbidden perpetuals
6849                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6850                                 for(m=forwardMostMove; m>k; m-=2) {
6851                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6852                                         ourPerpetual = 0; // the current mover did not always check
6853                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6854                                         hisPerpetual = 0; // the opponent did not always check
6855                                 }
6856                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6857                                                                         ourPerpetual, hisPerpetual);
6858                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6859                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6860                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6861                                     return 1;
6862                                 }
6863                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6864                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6865                                 // Now check for perpetual chases
6866                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6867                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6868                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6869                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6870                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6871                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6872                                         return 1;
6873                                     }
6874                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6875                                         break; // Abort repetition-checking loop.
6876                                 }
6877                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6878                              }
6879                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6880                              return 1;
6881                         }
6882                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6883                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6884                     }
6885                 }
6886
6887                 /* Now we test for 50-move draws. Determine ply count */
6888                 count = forwardMostMove;
6889                 /* look for last irreversble move */
6890                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6891                     count--;
6892                 /* if we hit starting position, add initial plies */
6893                 if( count == backwardMostMove )
6894                     count -= initialRulePlies;
6895                 count = forwardMostMove - count; 
6896                 if( count >= 100)
6897                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6898                          /* this is used to judge if draw claims are legal */
6899                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6900                          if(engineOpponent) {
6901                            SendToProgram("force\n", engineOpponent); // suppress reply
6902                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6903                          }
6904                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6905                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6906                          return 1;
6907                 }
6908
6909                 /* if draw offer is pending, treat it as a draw claim
6910                  * when draw condition present, to allow engines a way to
6911                  * claim draws before making their move to avoid a race
6912                  * condition occurring after their move
6913                  */
6914                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6915                          char *p = NULL;
6916                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6917                              p = "Draw claim: 50-move rule";
6918                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6919                              p = "Draw claim: 3-fold repetition";
6920                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6921                              p = "Draw claim: insufficient mating material";
6922                          if( p != NULL && canAdjudicate) {
6923                              if(engineOpponent) {
6924                                SendToProgram("force\n", engineOpponent); // suppress reply
6925                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6926                              }
6927                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6928                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6929                              return 1;
6930                          }
6931                 }
6932
6933                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6934                     if(engineOpponent) {
6935                       SendToProgram("force\n", engineOpponent); // suppress reply
6936                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6937                     }
6938                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6939                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6940                     return 1;
6941                 }
6942         return 0;
6943 }
6944
6945 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6946 {   // [HGM] book: this routine intercepts moves to simulate book replies
6947     char *bookHit = NULL;
6948
6949     //first determine if the incoming move brings opponent into his book
6950     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6951         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6952     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6953     if(bookHit != NULL && !cps->bookSuspend) {
6954         // make sure opponent is not going to reply after receiving move to book position
6955         SendToProgram("force\n", cps);
6956         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6957     }
6958     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6959     // now arrange restart after book miss
6960     if(bookHit) {
6961         // after a book hit we never send 'go', and the code after the call to this routine
6962         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6963         char buf[MSG_SIZ];
6964         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6965         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6966         SendToProgram(buf, cps);
6967         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6968     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6969         SendToProgram("go\n", cps);
6970         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6971     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6972         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6973             SendToProgram("go\n", cps); 
6974         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6975     }
6976     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6977 }
6978
6979 char *savedMessage;
6980 ChessProgramState *savedState;
6981 void DeferredBookMove(void)
6982 {
6983         if(savedState->lastPing != savedState->lastPong)
6984                     ScheduleDelayedEvent(DeferredBookMove, 10);
6985         else
6986         HandleMachineMove(savedMessage, savedState);
6987 }
6988
6989 void
6990 HandleMachineMove(message, cps)
6991      char *message;
6992      ChessProgramState *cps;
6993 {
6994     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6995     char realname[MSG_SIZ];
6996     int fromX, fromY, toX, toY;
6997     ChessMove moveType;
6998     char promoChar;
6999     char *p;
7000     int machineWhite;
7001     char *bookHit;
7002
7003     cps->userError = 0;
7004
7005 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7006     /*
7007      * Kludge to ignore BEL characters
7008      */
7009     while (*message == '\007') message++;
7010
7011     /*
7012      * [HGM] engine debug message: ignore lines starting with '#' character
7013      */
7014     if(cps->debug && *message == '#') return;
7015
7016     /*
7017      * Look for book output
7018      */
7019     if (cps == &first && bookRequested) {
7020         if (message[0] == '\t' || message[0] == ' ') {
7021             /* Part of the book output is here; append it */
7022             strcat(bookOutput, message);
7023             strcat(bookOutput, "  \n");
7024             return;
7025         } else if (bookOutput[0] != NULLCHAR) {
7026             /* All of book output has arrived; display it */
7027             char *p = bookOutput;
7028             while (*p != NULLCHAR) {
7029                 if (*p == '\t') *p = ' ';
7030                 p++;
7031             }
7032             DisplayInformation(bookOutput);
7033             bookRequested = FALSE;
7034             /* Fall through to parse the current output */
7035         }
7036     }
7037
7038     /*
7039      * Look for machine move.
7040      */
7041     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7042         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7043     {
7044         /* This method is only useful on engines that support ping */
7045         if (cps->lastPing != cps->lastPong) {
7046           if (gameMode == BeginningOfGame) {
7047             /* Extra move from before last new; ignore */
7048             if (appData.debugMode) {
7049                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7050             }
7051           } else {
7052             if (appData.debugMode) {
7053                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7054                         cps->which, gameMode);
7055             }
7056
7057             SendToProgram("undo\n", cps);
7058           }
7059           return;
7060         }
7061
7062         switch (gameMode) {
7063           case BeginningOfGame:
7064             /* Extra move from before last reset; ignore */
7065             if (appData.debugMode) {
7066                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7067             }
7068             return;
7069
7070           case EndOfGame:
7071           case IcsIdle:
7072           default:
7073             /* Extra move after we tried to stop.  The mode test is
7074                not a reliable way of detecting this problem, but it's
7075                the best we can do on engines that don't support ping.
7076             */
7077             if (appData.debugMode) {
7078                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7079                         cps->which, gameMode);
7080             }
7081             SendToProgram("undo\n", cps);
7082             return;
7083
7084           case MachinePlaysWhite:
7085           case IcsPlayingWhite:
7086             machineWhite = TRUE;
7087             break;
7088
7089           case MachinePlaysBlack:
7090           case IcsPlayingBlack:
7091             machineWhite = FALSE;
7092             break;
7093
7094           case TwoMachinesPlay:
7095             machineWhite = (cps->twoMachinesColor[0] == 'w');
7096             break;
7097         }
7098         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7099             if (appData.debugMode) {
7100                 fprintf(debugFP,
7101                         "Ignoring move out of turn by %s, gameMode %d"
7102                         ", forwardMost %d\n",
7103                         cps->which, gameMode, forwardMostMove);
7104             }
7105             return;
7106         }
7107
7108     if (appData.debugMode) { int f = forwardMostMove;
7109         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7110                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7111                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7112     }
7113         if(cps->alphaRank) AlphaRank(machineMove, 4);
7114         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7115                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7116             /* Machine move could not be parsed; ignore it. */
7117             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7118                     machineMove, cps->which);
7119             DisplayError(buf1, 0);
7120             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7121                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7122             if (gameMode == TwoMachinesPlay) {
7123               GameEnds(machineWhite ? BlackWins : WhiteWins,
7124                        buf1, GE_XBOARD);
7125             }
7126             return;
7127         }
7128
7129         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7130         /* So we have to redo legality test with true e.p. status here,  */
7131         /* to make sure an illegal e.p. capture does not slip through,   */
7132         /* to cause a forfeit on a justified illegal-move complaint      */
7133         /* of the opponent.                                              */
7134         if( gameMode==TwoMachinesPlay && appData.testLegality
7135             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7136                                                               ) {
7137            ChessMove moveType;
7138            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7139                              fromY, fromX, toY, toX, promoChar);
7140             if (appData.debugMode) {
7141                 int i;
7142                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7143                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7144                 fprintf(debugFP, "castling rights\n");
7145             }
7146             if(moveType == IllegalMove) {
7147                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7148                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7149                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7150                            buf1, GE_XBOARD);
7151                 return;
7152            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7153            /* [HGM] Kludge to handle engines that send FRC-style castling
7154               when they shouldn't (like TSCP-Gothic) */
7155            switch(moveType) {
7156              case WhiteASideCastleFR:
7157              case BlackASideCastleFR:
7158                toX+=2;
7159                currentMoveString[2]++;
7160                break;
7161              case WhiteHSideCastleFR:
7162              case BlackHSideCastleFR:
7163                toX--;
7164                currentMoveString[2]--;
7165                break;
7166              default: ; // nothing to do, but suppresses warning of pedantic compilers
7167            }
7168         }
7169         hintRequested = FALSE;
7170         lastHint[0] = NULLCHAR;
7171         bookRequested = FALSE;
7172         /* Program may be pondering now */
7173         cps->maybeThinking = TRUE;
7174         if (cps->sendTime == 2) cps->sendTime = 1;
7175         if (cps->offeredDraw) cps->offeredDraw--;
7176
7177         /* currentMoveString is set as a side-effect of ParseOneMove */
7178         strcpy(machineMove, currentMoveString);
7179         strcat(machineMove, "\n");
7180         strcpy(moveList[forwardMostMove], machineMove);
7181
7182         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7183
7184         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7185         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7186             int count = 0;
7187
7188             while( count < adjudicateLossPlies ) {
7189                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7190
7191                 if( count & 1 ) {
7192                     score = -score; /* Flip score for winning side */
7193                 }
7194
7195                 if( score > adjudicateLossThreshold ) {
7196                     break;
7197                 }
7198
7199                 count++;
7200             }
7201
7202             if( count >= adjudicateLossPlies ) {
7203                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7204
7205                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7206                     "Xboard adjudication", 
7207                     GE_XBOARD );
7208
7209                 return;
7210             }
7211         }
7212
7213         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7214
7215 #if ZIPPY
7216         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7217             first.initDone) {
7218           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7219                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7220                 SendToICS("draw ");
7221                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7222           }
7223           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7224           ics_user_moved = 1;
7225           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7226                 char buf[3*MSG_SIZ];
7227
7228                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7229                         programStats.score / 100.,
7230                         programStats.depth,
7231                         programStats.time / 100.,
7232                         (unsigned int)programStats.nodes,
7233                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7234                         programStats.movelist);
7235                 SendToICS(buf);
7236 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7237           }
7238         }
7239 #endif
7240
7241         /* [AS] Save move info and clear stats for next move */
7242         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7243         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7244         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7245         ClearProgramStats();
7246         thinkOutput[0] = NULLCHAR;
7247         hiddenThinkOutputState = 0;
7248
7249         bookHit = NULL;
7250         if (gameMode == TwoMachinesPlay) {
7251             /* [HGM] relaying draw offers moved to after reception of move */
7252             /* and interpreting offer as claim if it brings draw condition */
7253             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7254                 SendToProgram("draw\n", cps->other);
7255             }
7256             if (cps->other->sendTime) {
7257                 SendTimeRemaining(cps->other,
7258                                   cps->other->twoMachinesColor[0] == 'w');
7259             }
7260             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7261             if (firstMove && !bookHit) {
7262                 firstMove = FALSE;
7263                 if (cps->other->useColors) {
7264                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7265                 }
7266                 SendToProgram("go\n", cps->other);
7267             }
7268             cps->other->maybeThinking = TRUE;
7269         }
7270
7271         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7272         
7273         if (!pausing && appData.ringBellAfterMoves) {
7274             RingBell();
7275         }
7276
7277         /* 
7278          * Reenable menu items that were disabled while
7279          * machine was thinking
7280          */
7281         if (gameMode != TwoMachinesPlay)
7282             SetUserThinkingEnables();
7283
7284         // [HGM] book: after book hit opponent has received move and is now in force mode
7285         // force the book reply into it, and then fake that it outputted this move by jumping
7286         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7287         if(bookHit) {
7288                 static char bookMove[MSG_SIZ]; // a bit generous?
7289
7290                 strcpy(bookMove, "move ");
7291                 strcat(bookMove, bookHit);
7292                 message = bookMove;
7293                 cps = cps->other;
7294                 programStats.nodes = programStats.depth = programStats.time = 
7295                 programStats.score = programStats.got_only_move = 0;
7296                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7297
7298                 if(cps->lastPing != cps->lastPong) {
7299                     savedMessage = message; // args for deferred call
7300                     savedState = cps;
7301                     ScheduleDelayedEvent(DeferredBookMove, 10);
7302                     return;
7303                 }
7304                 goto FakeBookMove;
7305         }
7306
7307         return;
7308     }
7309
7310     /* Set special modes for chess engines.  Later something general
7311      *  could be added here; for now there is just one kludge feature,
7312      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7313      *  when "xboard" is given as an interactive command.
7314      */
7315     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7316         cps->useSigint = FALSE;
7317         cps->useSigterm = FALSE;
7318     }
7319     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7320       ParseFeatures(message+8, cps);
7321       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7322     }
7323
7324     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7325      * want this, I was asked to put it in, and obliged.
7326      */
7327     if (!strncmp(message, "setboard ", 9)) {
7328         Board initial_position;
7329
7330         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7331
7332         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7333             DisplayError(_("Bad FEN received from engine"), 0);
7334             return ;
7335         } else {
7336            Reset(TRUE, FALSE);
7337            CopyBoard(boards[0], initial_position);
7338            initialRulePlies = FENrulePlies;
7339            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7340            else gameMode = MachinePlaysBlack;                 
7341            DrawPosition(FALSE, boards[currentMove]);
7342         }
7343         return;
7344     }
7345
7346     /*
7347      * Look for communication commands
7348      */
7349     if (!strncmp(message, "telluser ", 9)) {
7350         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7351         DisplayNote(message + 9);
7352         return;
7353     }
7354     if (!strncmp(message, "tellusererror ", 14)) {
7355         cps->userError = 1;
7356         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7357         DisplayError(message + 14, 0);
7358         return;
7359     }
7360     if (!strncmp(message, "tellopponent ", 13)) {
7361       if (appData.icsActive) {
7362         if (loggedOn) {
7363           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7364           SendToICS(buf1);
7365         }
7366       } else {
7367         DisplayNote(message + 13);
7368       }
7369       return;
7370     }
7371     if (!strncmp(message, "tellothers ", 11)) {
7372       if (appData.icsActive) {
7373         if (loggedOn) {
7374           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7375           SendToICS(buf1);
7376         }
7377       }
7378       return;
7379     }
7380     if (!strncmp(message, "tellall ", 8)) {
7381       if (appData.icsActive) {
7382         if (loggedOn) {
7383           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7384           SendToICS(buf1);
7385         }
7386       } else {
7387         DisplayNote(message + 8);
7388       }
7389       return;
7390     }
7391     if (strncmp(message, "warning", 7) == 0) {
7392         /* Undocumented feature, use tellusererror in new code */
7393         DisplayError(message, 0);
7394         return;
7395     }
7396     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7397         strcpy(realname, cps->tidy);
7398         strcat(realname, " query");
7399         AskQuestion(realname, buf2, buf1, cps->pr);
7400         return;
7401     }
7402     /* Commands from the engine directly to ICS.  We don't allow these to be 
7403      *  sent until we are logged on. Crafty kibitzes have been known to 
7404      *  interfere with the login process.
7405      */
7406     if (loggedOn) {
7407         if (!strncmp(message, "tellics ", 8)) {
7408             SendToICS(message + 8);
7409             SendToICS("\n");
7410             return;
7411         }
7412         if (!strncmp(message, "tellicsnoalias ", 15)) {
7413             SendToICS(ics_prefix);
7414             SendToICS(message + 15);
7415             SendToICS("\n");
7416             return;
7417         }
7418         /* The following are for backward compatibility only */
7419         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7420             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7421             SendToICS(ics_prefix);
7422             SendToICS(message);
7423             SendToICS("\n");
7424             return;
7425         }
7426     }
7427     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7428         return;
7429     }
7430     /*
7431      * If the move is illegal, cancel it and redraw the board.
7432      * Also deal with other error cases.  Matching is rather loose
7433      * here to accommodate engines written before the spec.
7434      */
7435     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7436         strncmp(message, "Error", 5) == 0) {
7437         if (StrStr(message, "name") || 
7438             StrStr(message, "rating") || StrStr(message, "?") ||
7439             StrStr(message, "result") || StrStr(message, "board") ||
7440             StrStr(message, "bk") || StrStr(message, "computer") ||
7441             StrStr(message, "variant") || StrStr(message, "hint") ||
7442             StrStr(message, "random") || StrStr(message, "depth") ||
7443             StrStr(message, "accepted")) {
7444             return;
7445         }
7446         if (StrStr(message, "protover")) {
7447           /* Program is responding to input, so it's apparently done
7448              initializing, and this error message indicates it is
7449              protocol version 1.  So we don't need to wait any longer
7450              for it to initialize and send feature commands. */
7451           FeatureDone(cps, 1);
7452           cps->protocolVersion = 1;
7453           return;
7454         }
7455         cps->maybeThinking = FALSE;
7456
7457         if (StrStr(message, "draw")) {
7458             /* Program doesn't have "draw" command */
7459             cps->sendDrawOffers = 0;
7460             return;
7461         }
7462         if (cps->sendTime != 1 &&
7463             (StrStr(message, "time") || StrStr(message, "otim"))) {
7464           /* Program apparently doesn't have "time" or "otim" command */
7465           cps->sendTime = 0;
7466           return;
7467         }
7468         if (StrStr(message, "analyze")) {
7469             cps->analysisSupport = FALSE;
7470             cps->analyzing = FALSE;
7471             Reset(FALSE, TRUE);
7472             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7473             DisplayError(buf2, 0);
7474             return;
7475         }
7476         if (StrStr(message, "(no matching move)st")) {
7477           /* Special kludge for GNU Chess 4 only */
7478           cps->stKludge = TRUE;
7479           SendTimeControl(cps, movesPerSession, timeControl,
7480                           timeIncrement, appData.searchDepth,
7481                           searchTime);
7482           return;
7483         }
7484         if (StrStr(message, "(no matching move)sd")) {
7485           /* Special kludge for GNU Chess 4 only */
7486           cps->sdKludge = TRUE;
7487           SendTimeControl(cps, movesPerSession, timeControl,
7488                           timeIncrement, appData.searchDepth,
7489                           searchTime);
7490           return;
7491         }
7492         if (!StrStr(message, "llegal")) {
7493             return;
7494         }
7495         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7496             gameMode == IcsIdle) return;
7497         if (forwardMostMove <= backwardMostMove) return;
7498         if (pausing) PauseEvent();
7499       if(appData.forceIllegal) {
7500             // [HGM] illegal: machine refused move; force position after move into it
7501           SendToProgram("force\n", cps);
7502           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7503                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7504                 // when black is to move, while there might be nothing on a2 or black
7505                 // might already have the move. So send the board as if white has the move.
7506                 // But first we must change the stm of the engine, as it refused the last move
7507                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7508                 if(WhiteOnMove(forwardMostMove)) {
7509                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7510                     SendBoard(cps, forwardMostMove); // kludgeless board
7511                 } else {
7512                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7513                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7514                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7515                 }
7516           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7517             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7518                  gameMode == TwoMachinesPlay)
7519               SendToProgram("go\n", cps);
7520             return;
7521       } else
7522         if (gameMode == PlayFromGameFile) {
7523             /* Stop reading this game file */
7524             gameMode = EditGame;
7525             ModeHighlight();
7526         }
7527         currentMove = forwardMostMove-1;
7528         DisplayMove(currentMove-1); /* before DisplayMoveError */
7529         SwitchClocks(forwardMostMove-1); // [HGM] race
7530         DisplayBothClocks();
7531         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7532                 parseList[currentMove], cps->which);
7533         DisplayMoveError(buf1);
7534         DrawPosition(FALSE, boards[currentMove]);
7535
7536         /* [HGM] illegal-move claim should forfeit game when Xboard */
7537         /* only passes fully legal moves                            */
7538         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7539             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7540                                 "False illegal-move claim", GE_XBOARD );
7541         }
7542         return;
7543     }
7544     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7545         /* Program has a broken "time" command that
7546            outputs a string not ending in newline.
7547            Don't use it. */
7548         cps->sendTime = 0;
7549     }
7550     
7551     /*
7552      * If chess program startup fails, exit with an error message.
7553      * Attempts to recover here are futile.
7554      */
7555     if ((StrStr(message, "unknown host") != NULL)
7556         || (StrStr(message, "No remote directory") != NULL)
7557         || (StrStr(message, "not found") != NULL)
7558         || (StrStr(message, "No such file") != NULL)
7559         || (StrStr(message, "can't alloc") != NULL)
7560         || (StrStr(message, "Permission denied") != NULL)) {
7561
7562         cps->maybeThinking = FALSE;
7563         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7564                 cps->which, cps->program, cps->host, message);
7565         RemoveInputSource(cps->isr);
7566         DisplayFatalError(buf1, 0, 1);
7567         return;
7568     }
7569     
7570     /* 
7571      * Look for hint output
7572      */
7573     if (sscanf(message, "Hint: %s", buf1) == 1) {
7574         if (cps == &first && hintRequested) {
7575             hintRequested = FALSE;
7576             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7577                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7578                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7579                                     PosFlags(forwardMostMove),
7580                                     fromY, fromX, toY, toX, promoChar, buf1);
7581                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7582                 DisplayInformation(buf2);
7583             } else {
7584                 /* Hint move could not be parsed!? */
7585               snprintf(buf2, sizeof(buf2),
7586                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7587                         buf1, cps->which);
7588                 DisplayError(buf2, 0);
7589             }
7590         } else {
7591             strcpy(lastHint, buf1);
7592         }
7593         return;
7594     }
7595
7596     /*
7597      * Ignore other messages if game is not in progress
7598      */
7599     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7600         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7601
7602     /*
7603      * look for win, lose, draw, or draw offer
7604      */
7605     if (strncmp(message, "1-0", 3) == 0) {
7606         char *p, *q, *r = "";
7607         p = strchr(message, '{');
7608         if (p) {
7609             q = strchr(p, '}');
7610             if (q) {
7611                 *q = NULLCHAR;
7612                 r = p + 1;
7613             }
7614         }
7615         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7616         return;
7617     } else if (strncmp(message, "0-1", 3) == 0) {
7618         char *p, *q, *r = "";
7619         p = strchr(message, '{');
7620         if (p) {
7621             q = strchr(p, '}');
7622             if (q) {
7623                 *q = NULLCHAR;
7624                 r = p + 1;
7625             }
7626         }
7627         /* Kludge for Arasan 4.1 bug */
7628         if (strcmp(r, "Black resigns") == 0) {
7629             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7630             return;
7631         }
7632         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7633         return;
7634     } else if (strncmp(message, "1/2", 3) == 0) {
7635         char *p, *q, *r = "";
7636         p = strchr(message, '{');
7637         if (p) {
7638             q = strchr(p, '}');
7639             if (q) {
7640                 *q = NULLCHAR;
7641                 r = p + 1;
7642             }
7643         }
7644             
7645         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7646         return;
7647
7648     } else if (strncmp(message, "White resign", 12) == 0) {
7649         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7650         return;
7651     } else if (strncmp(message, "Black resign", 12) == 0) {
7652         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7653         return;
7654     } else if (strncmp(message, "White matches", 13) == 0 ||
7655                strncmp(message, "Black matches", 13) == 0   ) {
7656         /* [HGM] ignore GNUShogi noises */
7657         return;
7658     } else if (strncmp(message, "White", 5) == 0 &&
7659                message[5] != '(' &&
7660                StrStr(message, "Black") == NULL) {
7661         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7662         return;
7663     } else if (strncmp(message, "Black", 5) == 0 &&
7664                message[5] != '(') {
7665         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7666         return;
7667     } else if (strcmp(message, "resign") == 0 ||
7668                strcmp(message, "computer resigns") == 0) {
7669         switch (gameMode) {
7670           case MachinePlaysBlack:
7671           case IcsPlayingBlack:
7672             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7673             break;
7674           case MachinePlaysWhite:
7675           case IcsPlayingWhite:
7676             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7677             break;
7678           case TwoMachinesPlay:
7679             if (cps->twoMachinesColor[0] == 'w')
7680               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7681             else
7682               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7683             break;
7684           default:
7685             /* can't happen */
7686             break;
7687         }
7688         return;
7689     } else if (strncmp(message, "opponent mates", 14) == 0) {
7690         switch (gameMode) {
7691           case MachinePlaysBlack:
7692           case IcsPlayingBlack:
7693             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7694             break;
7695           case MachinePlaysWhite:
7696           case IcsPlayingWhite:
7697             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7698             break;
7699           case TwoMachinesPlay:
7700             if (cps->twoMachinesColor[0] == 'w')
7701               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7702             else
7703               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7704             break;
7705           default:
7706             /* can't happen */
7707             break;
7708         }
7709         return;
7710     } else if (strncmp(message, "computer mates", 14) == 0) {
7711         switch (gameMode) {
7712           case MachinePlaysBlack:
7713           case IcsPlayingBlack:
7714             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7715             break;
7716           case MachinePlaysWhite:
7717           case IcsPlayingWhite:
7718             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7719             break;
7720           case TwoMachinesPlay:
7721             if (cps->twoMachinesColor[0] == 'w')
7722               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7723             else
7724               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7725             break;
7726           default:
7727             /* can't happen */
7728             break;
7729         }
7730         return;
7731     } else if (strncmp(message, "checkmate", 9) == 0) {
7732         if (WhiteOnMove(forwardMostMove)) {
7733             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7734         } else {
7735             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7736         }
7737         return;
7738     } else if (strstr(message, "Draw") != NULL ||
7739                strstr(message, "game is a draw") != NULL) {
7740         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7741         return;
7742     } else if (strstr(message, "offer") != NULL &&
7743                strstr(message, "draw") != NULL) {
7744 #if ZIPPY
7745         if (appData.zippyPlay && first.initDone) {
7746             /* Relay offer to ICS */
7747             SendToICS(ics_prefix);
7748             SendToICS("draw\n");
7749         }
7750 #endif
7751         cps->offeredDraw = 2; /* valid until this engine moves twice */
7752         if (gameMode == TwoMachinesPlay) {
7753             if (cps->other->offeredDraw) {
7754                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7755             /* [HGM] in two-machine mode we delay relaying draw offer      */
7756             /* until after we also have move, to see if it is really claim */
7757             }
7758         } else if (gameMode == MachinePlaysWhite ||
7759                    gameMode == MachinePlaysBlack) {
7760           if (userOfferedDraw) {
7761             DisplayInformation(_("Machine accepts your draw offer"));
7762             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7763           } else {
7764             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7765           }
7766         }
7767     }
7768
7769     
7770     /*
7771      * Look for thinking output
7772      */
7773     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7774           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7775                                 ) {
7776         int plylev, mvleft, mvtot, curscore, time;
7777         char mvname[MOVE_LEN];
7778         u64 nodes; // [DM]
7779         char plyext;
7780         int ignore = FALSE;
7781         int prefixHint = FALSE;
7782         mvname[0] = NULLCHAR;
7783
7784         switch (gameMode) {
7785           case MachinePlaysBlack:
7786           case IcsPlayingBlack:
7787             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7788             break;
7789           case MachinePlaysWhite:
7790           case IcsPlayingWhite:
7791             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7792             break;
7793           case AnalyzeMode:
7794           case AnalyzeFile:
7795             break;
7796           case IcsObserving: /* [DM] icsEngineAnalyze */
7797             if (!appData.icsEngineAnalyze) ignore = TRUE;
7798             break;
7799           case TwoMachinesPlay:
7800             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7801                 ignore = TRUE;
7802             }
7803             break;
7804           default:
7805             ignore = TRUE;
7806             break;
7807         }
7808
7809         if (!ignore) {
7810             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7811             buf1[0] = NULLCHAR;
7812             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7813                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7814
7815                 if (plyext != ' ' && plyext != '\t') {
7816                     time *= 100;
7817                 }
7818
7819                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7820                 if( cps->scoreIsAbsolute && 
7821                     ( gameMode == MachinePlaysBlack ||
7822                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7823                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7824                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7825                      !WhiteOnMove(currentMove)
7826                     ) )
7827                 {
7828                     curscore = -curscore;
7829                 }
7830
7831
7832                 tempStats.depth = plylev;
7833                 tempStats.nodes = nodes;
7834                 tempStats.time = time;
7835                 tempStats.score = curscore;
7836                 tempStats.got_only_move = 0;
7837
7838                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7839                         int ticklen;
7840
7841                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7842                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7843                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7844                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7845                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7846                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7847                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7848                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7849                 }
7850
7851                 /* Buffer overflow protection */
7852                 if (buf1[0] != NULLCHAR) {
7853                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7854                         && appData.debugMode) {
7855                         fprintf(debugFP,
7856                                 "PV is too long; using the first %u bytes.\n",
7857                                 (unsigned) sizeof(tempStats.movelist) - 1);
7858                     }
7859
7860                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
7861                 } else {
7862                     sprintf(tempStats.movelist, " no PV\n");
7863                 }
7864
7865                 if (tempStats.seen_stat) {
7866                     tempStats.ok_to_send = 1;
7867                 }
7868
7869                 if (strchr(tempStats.movelist, '(') != NULL) {
7870                     tempStats.line_is_book = 1;
7871                     tempStats.nr_moves = 0;
7872                     tempStats.moves_left = 0;
7873                 } else {
7874                     tempStats.line_is_book = 0;
7875                 }
7876
7877                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7878                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7879
7880                 SendProgramStatsToFrontend( cps, &tempStats );
7881
7882                 /* 
7883                     [AS] Protect the thinkOutput buffer from overflow... this
7884                     is only useful if buf1 hasn't overflowed first!
7885                 */
7886                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7887                         plylev, 
7888                         (gameMode == TwoMachinesPlay ?
7889                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7890                         ((double) curscore) / 100.0,
7891                         prefixHint ? lastHint : "",
7892                         prefixHint ? " " : "" );
7893
7894                 if( buf1[0] != NULLCHAR ) {
7895                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7896
7897                     if( strlen(buf1) > max_len ) {
7898                         if( appData.debugMode) {
7899                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7900                         }
7901                         buf1[max_len+1] = '\0';
7902                     }
7903
7904                     strcat( thinkOutput, buf1 );
7905                 }
7906
7907                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7908                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7909                     DisplayMove(currentMove - 1);
7910                 }
7911                 return;
7912
7913             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7914                 /* crafty (9.25+) says "(only move) <move>"
7915                  * if there is only 1 legal move
7916                  */
7917                 sscanf(p, "(only move) %s", buf1);
7918                 sprintf(thinkOutput, "%s (only move)", buf1);
7919                 sprintf(programStats.movelist, "%s (only move)", buf1);
7920                 programStats.depth = 1;
7921                 programStats.nr_moves = 1;
7922                 programStats.moves_left = 1;
7923                 programStats.nodes = 1;
7924                 programStats.time = 1;
7925                 programStats.got_only_move = 1;
7926
7927                 /* Not really, but we also use this member to
7928                    mean "line isn't going to change" (Crafty
7929                    isn't searching, so stats won't change) */
7930                 programStats.line_is_book = 1;
7931
7932                 SendProgramStatsToFrontend( cps, &programStats );
7933                 
7934                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7935                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7936                     DisplayMove(currentMove - 1);
7937                 }
7938                 return;
7939             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7940                               &time, &nodes, &plylev, &mvleft,
7941                               &mvtot, mvname) >= 5) {
7942                 /* The stat01: line is from Crafty (9.29+) in response
7943                    to the "." command */
7944                 programStats.seen_stat = 1;
7945                 cps->maybeThinking = TRUE;
7946
7947                 if (programStats.got_only_move || !appData.periodicUpdates)
7948                   return;
7949
7950                 programStats.depth = plylev;
7951                 programStats.time = time;
7952                 programStats.nodes = nodes;
7953                 programStats.moves_left = mvleft;
7954                 programStats.nr_moves = mvtot;
7955                 strcpy(programStats.move_name, mvname);
7956                 programStats.ok_to_send = 1;
7957                 programStats.movelist[0] = '\0';
7958
7959                 SendProgramStatsToFrontend( cps, &programStats );
7960
7961                 return;
7962
7963             } else if (strncmp(message,"++",2) == 0) {
7964                 /* Crafty 9.29+ outputs this */
7965                 programStats.got_fail = 2;
7966                 return;
7967
7968             } else if (strncmp(message,"--",2) == 0) {
7969                 /* Crafty 9.29+ outputs this */
7970                 programStats.got_fail = 1;
7971                 return;
7972
7973             } else if (thinkOutput[0] != NULLCHAR &&
7974                        strncmp(message, "    ", 4) == 0) {
7975                 unsigned message_len;
7976
7977                 p = message;
7978                 while (*p && *p == ' ') p++;
7979
7980                 message_len = strlen( p );
7981
7982                 /* [AS] Avoid buffer overflow */
7983                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7984                     strcat(thinkOutput, " ");
7985                     strcat(thinkOutput, p);
7986                 }
7987
7988                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7989                     strcat(programStats.movelist, " ");
7990                     strcat(programStats.movelist, p);
7991                 }
7992
7993                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7994                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7995                     DisplayMove(currentMove - 1);
7996                 }
7997                 return;
7998             }
7999         }
8000         else {
8001             buf1[0] = NULLCHAR;
8002
8003             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8004                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
8005             {
8006                 ChessProgramStats cpstats;
8007
8008                 if (plyext != ' ' && plyext != '\t') {
8009                     time *= 100;
8010                 }
8011
8012                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8013                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8014                     curscore = -curscore;
8015                 }
8016
8017                 cpstats.depth = plylev;
8018                 cpstats.nodes = nodes;
8019                 cpstats.time = time;
8020                 cpstats.score = curscore;
8021                 cpstats.got_only_move = 0;
8022                 cpstats.movelist[0] = '\0';
8023
8024                 if (buf1[0] != NULLCHAR) {
8025                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8026                 }
8027
8028                 cpstats.ok_to_send = 0;
8029                 cpstats.line_is_book = 0;
8030                 cpstats.nr_moves = 0;
8031                 cpstats.moves_left = 0;
8032
8033                 SendProgramStatsToFrontend( cps, &cpstats );
8034             }
8035         }
8036     }
8037 }
8038
8039
8040 /* Parse a game score from the character string "game", and
8041    record it as the history of the current game.  The game
8042    score is NOT assumed to start from the standard position. 
8043    The display is not updated in any way.
8044    */
8045 void
8046 ParseGameHistory(game)
8047      char *game;
8048 {
8049     ChessMove moveType;
8050     int fromX, fromY, toX, toY, boardIndex;
8051     char promoChar;
8052     char *p, *q;
8053     char buf[MSG_SIZ];
8054
8055     if (appData.debugMode)
8056       fprintf(debugFP, "Parsing game history: %s\n", game);
8057
8058     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8059     gameInfo.site = StrSave(appData.icsHost);
8060     gameInfo.date = PGNDate();
8061     gameInfo.round = StrSave("-");
8062
8063     /* Parse out names of players */
8064     while (*game == ' ') game++;
8065     p = buf;
8066     while (*game != ' ') *p++ = *game++;
8067     *p = NULLCHAR;
8068     gameInfo.white = StrSave(buf);
8069     while (*game == ' ') game++;
8070     p = buf;
8071     while (*game != ' ' && *game != '\n') *p++ = *game++;
8072     *p = NULLCHAR;
8073     gameInfo.black = StrSave(buf);
8074
8075     /* Parse moves */
8076     boardIndex = blackPlaysFirst ? 1 : 0;
8077     yynewstr(game);
8078     for (;;) {
8079         yyboardindex = boardIndex;
8080         moveType = (ChessMove) yylex();
8081         switch (moveType) {
8082           case IllegalMove:             /* maybe suicide chess, etc. */
8083   if (appData.debugMode) {
8084     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8085     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8086     setbuf(debugFP, NULL);
8087   }
8088           case WhitePromotionChancellor:
8089           case BlackPromotionChancellor:
8090           case WhitePromotionArchbishop:
8091           case BlackPromotionArchbishop:
8092           case WhitePromotionQueen:
8093           case BlackPromotionQueen:
8094           case WhitePromotionRook:
8095           case BlackPromotionRook:
8096           case WhitePromotionBishop:
8097           case BlackPromotionBishop:
8098           case WhitePromotionKnight:
8099           case BlackPromotionKnight:
8100           case WhitePromotionKing:
8101           case BlackPromotionKing:
8102           case NormalMove:
8103           case WhiteCapturesEnPassant:
8104           case BlackCapturesEnPassant:
8105           case WhiteKingSideCastle:
8106           case WhiteQueenSideCastle:
8107           case BlackKingSideCastle:
8108           case BlackQueenSideCastle:
8109           case WhiteKingSideCastleWild:
8110           case WhiteQueenSideCastleWild:
8111           case BlackKingSideCastleWild:
8112           case BlackQueenSideCastleWild:
8113           /* PUSH Fabien */
8114           case WhiteHSideCastleFR:
8115           case WhiteASideCastleFR:
8116           case BlackHSideCastleFR:
8117           case BlackASideCastleFR:
8118           /* POP Fabien */
8119             fromX = currentMoveString[0] - AAA;
8120             fromY = currentMoveString[1] - ONE;
8121             toX = currentMoveString[2] - AAA;
8122             toY = currentMoveString[3] - ONE;
8123             promoChar = currentMoveString[4];
8124             break;
8125           case WhiteDrop:
8126           case BlackDrop:
8127             fromX = moveType == WhiteDrop ?
8128               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8129             (int) CharToPiece(ToLower(currentMoveString[0]));
8130             fromY = DROP_RANK;
8131             toX = currentMoveString[2] - AAA;
8132             toY = currentMoveString[3] - ONE;
8133             promoChar = NULLCHAR;
8134             break;
8135           case AmbiguousMove:
8136             /* bug? */
8137             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8138   if (appData.debugMode) {
8139     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8140     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8141     setbuf(debugFP, NULL);
8142   }
8143             DisplayError(buf, 0);
8144             return;
8145           case ImpossibleMove:
8146             /* bug? */
8147             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8148   if (appData.debugMode) {
8149     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8150     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8151     setbuf(debugFP, NULL);
8152   }
8153             DisplayError(buf, 0);
8154             return;
8155           case (ChessMove) 0:   /* end of file */
8156             if (boardIndex < backwardMostMove) {
8157                 /* Oops, gap.  How did that happen? */
8158                 DisplayError(_("Gap in move list"), 0);
8159                 return;
8160             }
8161             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8162             if (boardIndex > forwardMostMove) {
8163                 forwardMostMove = boardIndex;
8164             }
8165             return;
8166           case ElapsedTime:
8167             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8168                 strcat(parseList[boardIndex-1], " ");
8169                 strcat(parseList[boardIndex-1], yy_text);
8170             }
8171             continue;
8172           case Comment:
8173           case PGNTag:
8174           case NAG:
8175           default:
8176             /* ignore */
8177             continue;
8178           case WhiteWins:
8179           case BlackWins:
8180           case GameIsDrawn:
8181           case GameUnfinished:
8182             if (gameMode == IcsExamining) {
8183                 if (boardIndex < backwardMostMove) {
8184                     /* Oops, gap.  How did that happen? */
8185                     return;
8186                 }
8187                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8188                 return;
8189             }
8190             gameInfo.result = moveType;
8191             p = strchr(yy_text, '{');
8192             if (p == NULL) p = strchr(yy_text, '(');
8193             if (p == NULL) {
8194                 p = yy_text;
8195                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8196             } else {
8197                 q = strchr(p, *p == '{' ? '}' : ')');
8198                 if (q != NULL) *q = NULLCHAR;
8199                 p++;
8200             }
8201             gameInfo.resultDetails = StrSave(p);
8202             continue;
8203         }
8204         if (boardIndex >= forwardMostMove &&
8205             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8206             backwardMostMove = blackPlaysFirst ? 1 : 0;
8207             return;
8208         }
8209         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8210                                  fromY, fromX, toY, toX, promoChar,
8211                                  parseList[boardIndex]);
8212         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8213         /* currentMoveString is set as a side-effect of yylex */
8214         strcpy(moveList[boardIndex], currentMoveString);
8215         strcat(moveList[boardIndex], "\n");
8216         boardIndex++;
8217         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8218         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8219           case MT_NONE:
8220           case MT_STALEMATE:
8221           default:
8222             break;
8223           case MT_CHECK:
8224             if(gameInfo.variant != VariantShogi)
8225                 strcat(parseList[boardIndex - 1], "+");
8226             break;
8227           case MT_CHECKMATE:
8228           case MT_STAINMATE:
8229             strcat(parseList[boardIndex - 1], "#");
8230             break;
8231         }
8232     }
8233 }
8234
8235
8236 /* Apply a move to the given board  */
8237 void
8238 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8239      int fromX, fromY, toX, toY;
8240      int promoChar;
8241      Board board;
8242 {
8243   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8244   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8245
8246     /* [HGM] compute & store e.p. status and castling rights for new position */
8247     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8248     { int i;
8249
8250       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8251       oldEP = (signed char)board[EP_STATUS];
8252       board[EP_STATUS] = EP_NONE;
8253
8254       if( board[toY][toX] != EmptySquare ) 
8255            board[EP_STATUS] = EP_CAPTURE;  
8256
8257       if( board[fromY][fromX] == WhitePawn ) {
8258            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8259                board[EP_STATUS] = EP_PAWN_MOVE;
8260            if( toY-fromY==2) {
8261                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8262                         gameInfo.variant != VariantBerolina || toX < fromX)
8263                       board[EP_STATUS] = toX | berolina;
8264                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8265                         gameInfo.variant != VariantBerolina || toX > fromX) 
8266                       board[EP_STATUS] = toX;
8267            }
8268       } else 
8269       if( board[fromY][fromX] == BlackPawn ) {
8270            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8271                board[EP_STATUS] = EP_PAWN_MOVE; 
8272            if( toY-fromY== -2) {
8273                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8274                         gameInfo.variant != VariantBerolina || toX < fromX)
8275                       board[EP_STATUS] = toX | berolina;
8276                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8277                         gameInfo.variant != VariantBerolina || toX > fromX) 
8278                       board[EP_STATUS] = toX;
8279            }
8280        }
8281
8282        for(i=0; i<nrCastlingRights; i++) {
8283            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8284               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8285              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8286        }
8287
8288     }
8289
8290   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8291   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8292        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8293          
8294   if (fromX == toX && fromY == toY) return;
8295
8296   if (fromY == DROP_RANK) {
8297         /* must be first */
8298         piece = board[toY][toX] = (ChessSquare) fromX;
8299   } else {
8300      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8301      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8302      if(gameInfo.variant == VariantKnightmate)
8303          king += (int) WhiteUnicorn - (int) WhiteKing;
8304
8305     /* Code added by Tord: */
8306     /* FRC castling assumed when king captures friendly rook. */
8307     if (board[fromY][fromX] == WhiteKing &&
8308              board[toY][toX] == WhiteRook) {
8309       board[fromY][fromX] = EmptySquare;
8310       board[toY][toX] = EmptySquare;
8311       if(toX > fromX) {
8312         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8313       } else {
8314         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8315       }
8316     } else if (board[fromY][fromX] == BlackKing &&
8317                board[toY][toX] == BlackRook) {
8318       board[fromY][fromX] = EmptySquare;
8319       board[toY][toX] = EmptySquare;
8320       if(toX > fromX) {
8321         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8322       } else {
8323         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8324       }
8325     /* End of code added by Tord */
8326
8327     } else if (board[fromY][fromX] == king
8328         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8329         && toY == fromY && toX > fromX+1) {
8330         board[fromY][fromX] = EmptySquare;
8331         board[toY][toX] = king;
8332         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8333         board[fromY][BOARD_RGHT-1] = EmptySquare;
8334     } else if (board[fromY][fromX] == king
8335         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8336                && toY == fromY && toX < fromX-1) {
8337         board[fromY][fromX] = EmptySquare;
8338         board[toY][toX] = king;
8339         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8340         board[fromY][BOARD_LEFT] = EmptySquare;
8341     } else if (board[fromY][fromX] == WhitePawn
8342                && toY >= BOARD_HEIGHT-promoRank
8343                && gameInfo.variant != VariantXiangqi
8344                ) {
8345         /* white pawn promotion */
8346         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8347         if (board[toY][toX] == EmptySquare) {
8348             board[toY][toX] = WhiteQueen;
8349         }
8350         if(gameInfo.variant==VariantBughouse ||
8351            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8352             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8353         board[fromY][fromX] = EmptySquare;
8354     } else if ((fromY == BOARD_HEIGHT-4)
8355                && (toX != fromX)
8356                && gameInfo.variant != VariantXiangqi
8357                && gameInfo.variant != VariantBerolina
8358                && (board[fromY][fromX] == WhitePawn)
8359                && (board[toY][toX] == EmptySquare)) {
8360         board[fromY][fromX] = EmptySquare;
8361         board[toY][toX] = WhitePawn;
8362         captured = board[toY - 1][toX];
8363         board[toY - 1][toX] = EmptySquare;
8364     } else if ((fromY == BOARD_HEIGHT-4)
8365                && (toX == fromX)
8366                && gameInfo.variant == VariantBerolina
8367                && (board[fromY][fromX] == WhitePawn)
8368                && (board[toY][toX] == EmptySquare)) {
8369         board[fromY][fromX] = EmptySquare;
8370         board[toY][toX] = WhitePawn;
8371         if(oldEP & EP_BEROLIN_A) {
8372                 captured = board[fromY][fromX-1];
8373                 board[fromY][fromX-1] = EmptySquare;
8374         }else{  captured = board[fromY][fromX+1];
8375                 board[fromY][fromX+1] = EmptySquare;
8376         }
8377     } else if (board[fromY][fromX] == king
8378         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8379                && toY == fromY && toX > fromX+1) {
8380         board[fromY][fromX] = EmptySquare;
8381         board[toY][toX] = king;
8382         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8383         board[fromY][BOARD_RGHT-1] = EmptySquare;
8384     } else if (board[fromY][fromX] == king
8385         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8386                && toY == fromY && toX < fromX-1) {
8387         board[fromY][fromX] = EmptySquare;
8388         board[toY][toX] = king;
8389         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8390         board[fromY][BOARD_LEFT] = EmptySquare;
8391     } else if (fromY == 7 && fromX == 3
8392                && board[fromY][fromX] == BlackKing
8393                && toY == 7 && toX == 5) {
8394         board[fromY][fromX] = EmptySquare;
8395         board[toY][toX] = BlackKing;
8396         board[fromY][7] = EmptySquare;
8397         board[toY][4] = BlackRook;
8398     } else if (fromY == 7 && fromX == 3
8399                && board[fromY][fromX] == BlackKing
8400                && toY == 7 && toX == 1) {
8401         board[fromY][fromX] = EmptySquare;
8402         board[toY][toX] = BlackKing;
8403         board[fromY][0] = EmptySquare;
8404         board[toY][2] = BlackRook;
8405     } else if (board[fromY][fromX] == BlackPawn
8406                && toY < promoRank
8407                && gameInfo.variant != VariantXiangqi
8408                ) {
8409         /* black pawn promotion */
8410         board[toY][toX] = CharToPiece(ToLower(promoChar));
8411         if (board[toY][toX] == EmptySquare) {
8412             board[toY][toX] = BlackQueen;
8413         }
8414         if(gameInfo.variant==VariantBughouse ||
8415            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8416             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8417         board[fromY][fromX] = EmptySquare;
8418     } else if ((fromY == 3)
8419                && (toX != fromX)
8420                && gameInfo.variant != VariantXiangqi
8421                && gameInfo.variant != VariantBerolina
8422                && (board[fromY][fromX] == BlackPawn)
8423                && (board[toY][toX] == EmptySquare)) {
8424         board[fromY][fromX] = EmptySquare;
8425         board[toY][toX] = BlackPawn;
8426         captured = board[toY + 1][toX];
8427         board[toY + 1][toX] = EmptySquare;
8428     } else if ((fromY == 3)
8429                && (toX == fromX)
8430                && gameInfo.variant == VariantBerolina
8431                && (board[fromY][fromX] == BlackPawn)
8432                && (board[toY][toX] == EmptySquare)) {
8433         board[fromY][fromX] = EmptySquare;
8434         board[toY][toX] = BlackPawn;
8435         if(oldEP & EP_BEROLIN_A) {
8436                 captured = board[fromY][fromX-1];
8437                 board[fromY][fromX-1] = EmptySquare;
8438         }else{  captured = board[fromY][fromX+1];
8439                 board[fromY][fromX+1] = EmptySquare;
8440         }
8441     } else {
8442         board[toY][toX] = board[fromY][fromX];
8443         board[fromY][fromX] = EmptySquare;
8444     }
8445
8446     /* [HGM] now we promote for Shogi, if needed */
8447     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8448         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8449   }
8450
8451     if (gameInfo.holdingsWidth != 0) {
8452
8453       /* !!A lot more code needs to be written to support holdings  */
8454       /* [HGM] OK, so I have written it. Holdings are stored in the */
8455       /* penultimate board files, so they are automaticlly stored   */
8456       /* in the game history.                                       */
8457       if (fromY == DROP_RANK) {
8458         /* Delete from holdings, by decreasing count */
8459         /* and erasing image if necessary            */
8460         p = (int) fromX;
8461         if(p < (int) BlackPawn) { /* white drop */
8462              p -= (int)WhitePawn;
8463                  p = PieceToNumber((ChessSquare)p);
8464              if(p >= gameInfo.holdingsSize) p = 0;
8465              if(--board[p][BOARD_WIDTH-2] <= 0)
8466                   board[p][BOARD_WIDTH-1] = EmptySquare;
8467              if((int)board[p][BOARD_WIDTH-2] < 0)
8468                         board[p][BOARD_WIDTH-2] = 0;
8469         } else {                  /* black drop */
8470              p -= (int)BlackPawn;
8471                  p = PieceToNumber((ChessSquare)p);
8472              if(p >= gameInfo.holdingsSize) p = 0;
8473              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8474                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8475              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8476                         board[BOARD_HEIGHT-1-p][1] = 0;
8477         }
8478       }
8479       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8480           && gameInfo.variant != VariantBughouse        ) {
8481         /* [HGM] holdings: Add to holdings, if holdings exist */
8482         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8483                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8484                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8485         }
8486         p = (int) captured;
8487         if (p >= (int) BlackPawn) {
8488           p -= (int)BlackPawn;
8489           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8490                   /* in Shogi restore piece to its original  first */
8491                   captured = (ChessSquare) (DEMOTED captured);
8492                   p = DEMOTED p;
8493           }
8494           p = PieceToNumber((ChessSquare)p);
8495           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8496           board[p][BOARD_WIDTH-2]++;
8497           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8498         } else {
8499           p -= (int)WhitePawn;
8500           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8501                   captured = (ChessSquare) (DEMOTED captured);
8502                   p = DEMOTED p;
8503           }
8504           p = PieceToNumber((ChessSquare)p);
8505           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8506           board[BOARD_HEIGHT-1-p][1]++;
8507           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8508         }
8509       }
8510     } else if (gameInfo.variant == VariantAtomic) {
8511       if (captured != EmptySquare) {
8512         int y, x;
8513         for (y = toY-1; y <= toY+1; y++) {
8514           for (x = toX-1; x <= toX+1; x++) {
8515             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8516                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8517               board[y][x] = EmptySquare;
8518             }
8519           }
8520         }
8521         board[toY][toX] = EmptySquare;
8522       }
8523     }
8524     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8525         /* [HGM] Shogi promotions */
8526         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8527     }
8528
8529     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8530                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8531         // [HGM] superchess: take promotion piece out of holdings
8532         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8533         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8534             if(!--board[k][BOARD_WIDTH-2])
8535                 board[k][BOARD_WIDTH-1] = EmptySquare;
8536         } else {
8537             if(!--board[BOARD_HEIGHT-1-k][1])
8538                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8539         }
8540     }
8541
8542 }
8543
8544 /* Updates forwardMostMove */
8545 void
8546 MakeMove(fromX, fromY, toX, toY, promoChar)
8547      int fromX, fromY, toX, toY;
8548      int promoChar;
8549 {
8550 //    forwardMostMove++; // [HGM] bare: moved downstream
8551
8552     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8553         int timeLeft; static int lastLoadFlag=0; int king, piece;
8554         piece = boards[forwardMostMove][fromY][fromX];
8555         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8556         if(gameInfo.variant == VariantKnightmate)
8557             king += (int) WhiteUnicorn - (int) WhiteKing;
8558         if(forwardMostMove == 0) {
8559             if(blackPlaysFirst) 
8560                 fprintf(serverMoves, "%s;", second.tidy);
8561             fprintf(serverMoves, "%s;", first.tidy);
8562             if(!blackPlaysFirst) 
8563                 fprintf(serverMoves, "%s;", second.tidy);
8564         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8565         lastLoadFlag = loadFlag;
8566         // print base move
8567         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8568         // print castling suffix
8569         if( toY == fromY && piece == king ) {
8570             if(toX-fromX > 1)
8571                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8572             if(fromX-toX >1)
8573                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8574         }
8575         // e.p. suffix
8576         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8577              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8578              boards[forwardMostMove][toY][toX] == EmptySquare
8579              && fromX != toX )
8580                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8581         // promotion suffix
8582         if(promoChar != NULLCHAR)
8583                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8584         if(!loadFlag) {
8585             fprintf(serverMoves, "/%d/%d",
8586                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8587             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8588             else                      timeLeft = blackTimeRemaining/1000;
8589             fprintf(serverMoves, "/%d", timeLeft);
8590         }
8591         fflush(serverMoves);
8592     }
8593
8594     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8595       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8596                         0, 1);
8597       return;
8598     }
8599     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8600     if (commentList[forwardMostMove+1] != NULL) {
8601         free(commentList[forwardMostMove+1]);
8602         commentList[forwardMostMove+1] = NULL;
8603     }
8604     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8605     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8606     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8607     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8608     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8609     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8610     gameInfo.result = GameUnfinished;
8611     if (gameInfo.resultDetails != NULL) {
8612         free(gameInfo.resultDetails);
8613         gameInfo.resultDetails = NULL;
8614     }
8615     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8616                               moveList[forwardMostMove - 1]);
8617     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8618                              PosFlags(forwardMostMove - 1),
8619                              fromY, fromX, toY, toX, promoChar,
8620                              parseList[forwardMostMove - 1]);
8621     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8622       case MT_NONE:
8623       case MT_STALEMATE:
8624       default:
8625         break;
8626       case MT_CHECK:
8627         if(gameInfo.variant != VariantShogi)
8628             strcat(parseList[forwardMostMove - 1], "+");
8629         break;
8630       case MT_CHECKMATE:
8631       case MT_STAINMATE:
8632         strcat(parseList[forwardMostMove - 1], "#");
8633         break;
8634     }
8635     if (appData.debugMode) {
8636         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8637     }
8638
8639 }
8640
8641 /* Updates currentMove if not pausing */
8642 void
8643 ShowMove(fromX, fromY, toX, toY)
8644 {
8645     int instant = (gameMode == PlayFromGameFile) ?
8646         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8647     if(appData.noGUI) return;
8648     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8649         if (!instant) {
8650             if (forwardMostMove == currentMove + 1) {
8651                 AnimateMove(boards[forwardMostMove - 1],
8652                             fromX, fromY, toX, toY);
8653             }
8654             if (appData.highlightLastMove) {
8655                 SetHighlights(fromX, fromY, toX, toY);
8656             }
8657         }
8658         currentMove = forwardMostMove;
8659     }
8660
8661     if (instant) return;
8662
8663     DisplayMove(currentMove - 1);
8664     DrawPosition(FALSE, boards[currentMove]);
8665     DisplayBothClocks();
8666     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8667 }
8668
8669 void SendEgtPath(ChessProgramState *cps)
8670 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8671         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8672
8673         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8674
8675         while(*p) {
8676             char c, *q = name+1, *r, *s;
8677
8678             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8679             while(*p && *p != ',') *q++ = *p++;
8680             *q++ = ':'; *q = 0;
8681             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8682                 strcmp(name, ",nalimov:") == 0 ) {
8683                 // take nalimov path from the menu-changeable option first, if it is defined
8684                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8685                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8686             } else
8687             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8688                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8689                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8690                 s = r = StrStr(s, ":") + 1; // beginning of path info
8691                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8692                 c = *r; *r = 0;             // temporarily null-terminate path info
8693                     *--q = 0;               // strip of trailig ':' from name
8694                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8695                 *r = c;
8696                 SendToProgram(buf,cps);     // send egtbpath command for this format
8697             }
8698             if(*p == ',') p++; // read away comma to position for next format name
8699         }
8700 }
8701
8702 void
8703 InitChessProgram(cps, setup)
8704      ChessProgramState *cps;
8705      int setup; /* [HGM] needed to setup FRC opening position */
8706 {
8707     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8708     if (appData.noChessProgram) return;
8709     hintRequested = FALSE;
8710     bookRequested = FALSE;
8711
8712     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8713     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8714     if(cps->memSize) { /* [HGM] memory */
8715         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8716         SendToProgram(buf, cps);
8717     }
8718     SendEgtPath(cps); /* [HGM] EGT */
8719     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8720         sprintf(buf, "cores %d\n", appData.smpCores);
8721         SendToProgram(buf, cps);
8722     }
8723
8724     SendToProgram(cps->initString, cps);
8725     if (gameInfo.variant != VariantNormal &&
8726         gameInfo.variant != VariantLoadable
8727         /* [HGM] also send variant if board size non-standard */
8728         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8729                                             ) {
8730       char *v = VariantName(gameInfo.variant);
8731       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8732         /* [HGM] in protocol 1 we have to assume all variants valid */
8733         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8734         DisplayFatalError(buf, 0, 1);
8735         return;
8736       }
8737
8738       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8739       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8740       if( gameInfo.variant == VariantXiangqi )
8741            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8742       if( gameInfo.variant == VariantShogi )
8743            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8744       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8745            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8746       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8747                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8748            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8749       if( gameInfo.variant == VariantCourier )
8750            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8751       if( gameInfo.variant == VariantSuper )
8752            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8753       if( gameInfo.variant == VariantGreat )
8754            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8755
8756       if(overruled) {
8757            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8758                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8759            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8760            if(StrStr(cps->variants, b) == NULL) { 
8761                // specific sized variant not known, check if general sizing allowed
8762                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8763                    if(StrStr(cps->variants, "boardsize") == NULL) {
8764                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8765                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8766                        DisplayFatalError(buf, 0, 1);
8767                        return;
8768                    }
8769                    /* [HGM] here we really should compare with the maximum supported board size */
8770                }
8771            }
8772       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8773       sprintf(buf, "variant %s\n", b);
8774       SendToProgram(buf, cps);
8775     }
8776     currentlyInitializedVariant = gameInfo.variant;
8777
8778     /* [HGM] send opening position in FRC to first engine */
8779     if(setup) {
8780           SendToProgram("force\n", cps);
8781           SendBoard(cps, 0);
8782           /* engine is now in force mode! Set flag to wake it up after first move. */
8783           setboardSpoiledMachineBlack = 1;
8784     }
8785
8786     if (cps->sendICS) {
8787       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8788       SendToProgram(buf, cps);
8789     }
8790     cps->maybeThinking = FALSE;
8791     cps->offeredDraw = 0;
8792     if (!appData.icsActive) {
8793         SendTimeControl(cps, movesPerSession, timeControl,
8794                         timeIncrement, appData.searchDepth,
8795                         searchTime);
8796     }
8797     if (appData.showThinking 
8798         // [HGM] thinking: four options require thinking output to be sent
8799         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8800                                 ) {
8801         SendToProgram("post\n", cps);
8802     }
8803     SendToProgram("hard\n", cps);
8804     if (!appData.ponderNextMove) {
8805         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8806            it without being sure what state we are in first.  "hard"
8807            is not a toggle, so that one is OK.
8808          */
8809         SendToProgram("easy\n", cps);
8810     }
8811     if (cps->usePing) {
8812       sprintf(buf, "ping %d\n", ++cps->lastPing);
8813       SendToProgram(buf, cps);
8814     }
8815     cps->initDone = TRUE;
8816 }   
8817
8818
8819 void
8820 StartChessProgram(cps)
8821      ChessProgramState *cps;
8822 {
8823     char buf[MSG_SIZ];
8824     int err;
8825
8826     if (appData.noChessProgram) return;
8827     cps->initDone = FALSE;
8828
8829     if (strcmp(cps->host, "localhost") == 0) {
8830         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8831     } else if (*appData.remoteShell == NULLCHAR) {
8832         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8833     } else {
8834         if (*appData.remoteUser == NULLCHAR) {
8835           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8836                     cps->program);
8837         } else {
8838           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8839                     cps->host, appData.remoteUser, cps->program);
8840         }
8841         err = StartChildProcess(buf, "", &cps->pr);
8842     }
8843     
8844     if (err != 0) {
8845         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8846         DisplayFatalError(buf, err, 1);
8847         cps->pr = NoProc;
8848         cps->isr = NULL;
8849         return;
8850     }
8851     
8852     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8853     if (cps->protocolVersion > 1) {
8854       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8855       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8856       cps->comboCnt = 0;  //                and values of combo boxes
8857       SendToProgram(buf, cps);
8858     } else {
8859       SendToProgram("xboard\n", cps);
8860     }
8861 }
8862
8863
8864 void
8865 TwoMachinesEventIfReady P((void))
8866 {
8867   if (first.lastPing != first.lastPong) {
8868     DisplayMessage("", _("Waiting for first chess program"));
8869     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8870     return;
8871   }
8872   if (second.lastPing != second.lastPong) {
8873     DisplayMessage("", _("Waiting for second chess program"));
8874     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8875     return;
8876   }
8877   ThawUI();
8878   TwoMachinesEvent();
8879 }
8880
8881 void
8882 NextMatchGame P((void))
8883 {
8884     int index; /* [HGM] autoinc: step load index during match */
8885     Reset(FALSE, TRUE);
8886     if (*appData.loadGameFile != NULLCHAR) {
8887         index = appData.loadGameIndex;
8888         if(index < 0) { // [HGM] autoinc
8889             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8890             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8891         } 
8892         LoadGameFromFile(appData.loadGameFile,
8893                          index,
8894                          appData.loadGameFile, FALSE);
8895     } else if (*appData.loadPositionFile != NULLCHAR) {
8896         index = appData.loadPositionIndex;
8897         if(index < 0) { // [HGM] autoinc
8898             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8899             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8900         } 
8901         LoadPositionFromFile(appData.loadPositionFile,
8902                              index,
8903                              appData.loadPositionFile);
8904     }
8905     TwoMachinesEventIfReady();
8906 }
8907
8908 void UserAdjudicationEvent( int result )
8909 {
8910     ChessMove gameResult = GameIsDrawn;
8911
8912     if( result > 0 ) {
8913         gameResult = WhiteWins;
8914     }
8915     else if( result < 0 ) {
8916         gameResult = BlackWins;
8917     }
8918
8919     if( gameMode == TwoMachinesPlay ) {
8920         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8921     }
8922 }
8923
8924
8925 // [HGM] save: calculate checksum of game to make games easily identifiable
8926 int StringCheckSum(char *s)
8927 {
8928         int i = 0;
8929         if(s==NULL) return 0;
8930         while(*s) i = i*259 + *s++;
8931         return i;
8932 }
8933
8934 int GameCheckSum()
8935 {
8936         int i, sum=0;
8937         for(i=backwardMostMove; i<forwardMostMove; i++) {
8938                 sum += pvInfoList[i].depth;
8939                 sum += StringCheckSum(parseList[i]);
8940                 sum += StringCheckSum(commentList[i]);
8941                 sum *= 261;
8942         }
8943         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8944         return sum + StringCheckSum(commentList[i]);
8945 } // end of save patch
8946
8947 void
8948 GameEnds(result, resultDetails, whosays)
8949      ChessMove result;
8950      char *resultDetails;
8951      int whosays;
8952 {
8953     GameMode nextGameMode;
8954     int isIcsGame;
8955     char buf[MSG_SIZ];
8956
8957     if(endingGame) return; /* [HGM] crash: forbid recursion */
8958     endingGame = 1;
8959     if(twoBoards) { // [HGM] dual: switch back to one board
8960         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8961         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8962     }
8963     if (appData.debugMode) {
8964       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8965               result, resultDetails ? resultDetails : "(null)", whosays);
8966     }
8967
8968     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8969
8970     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8971         /* If we are playing on ICS, the server decides when the
8972            game is over, but the engine can offer to draw, claim 
8973            a draw, or resign. 
8974          */
8975 #if ZIPPY
8976         if (appData.zippyPlay && first.initDone) {
8977             if (result == GameIsDrawn) {
8978                 /* In case draw still needs to be claimed */
8979                 SendToICS(ics_prefix);
8980                 SendToICS("draw\n");
8981             } else if (StrCaseStr(resultDetails, "resign")) {
8982                 SendToICS(ics_prefix);
8983                 SendToICS("resign\n");
8984             }
8985         }
8986 #endif
8987         endingGame = 0; /* [HGM] crash */
8988         return;
8989     }
8990
8991     /* If we're loading the game from a file, stop */
8992     if (whosays == GE_FILE) {
8993       (void) StopLoadGameTimer();
8994       gameFileFP = NULL;
8995     }
8996
8997     /* Cancel draw offers */
8998     first.offeredDraw = second.offeredDraw = 0;
8999
9000     /* If this is an ICS game, only ICS can really say it's done;
9001        if not, anyone can. */
9002     isIcsGame = (gameMode == IcsPlayingWhite || 
9003                  gameMode == IcsPlayingBlack || 
9004                  gameMode == IcsObserving    || 
9005                  gameMode == IcsExamining);
9006
9007     if (!isIcsGame || whosays == GE_ICS) {
9008         /* OK -- not an ICS game, or ICS said it was done */
9009         StopClocks();
9010         if (!isIcsGame && !appData.noChessProgram) 
9011           SetUserThinkingEnables();
9012     
9013         /* [HGM] if a machine claims the game end we verify this claim */
9014         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9015             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9016                 char claimer;
9017                 ChessMove trueResult = (ChessMove) -1;
9018
9019                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9020                                             first.twoMachinesColor[0] :
9021                                             second.twoMachinesColor[0] ;
9022
9023                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9024                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9025                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9026                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9027                 } else
9028                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9029                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9030                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9031                 } else
9032                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9033                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9034                 }
9035
9036                 // now verify win claims, but not in drop games, as we don't understand those yet
9037                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9038                                                  || gameInfo.variant == VariantGreat) &&
9039                     (result == WhiteWins && claimer == 'w' ||
9040                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9041                       if (appData.debugMode) {
9042                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9043                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9044                       }
9045                       if(result != trueResult) {
9046                               sprintf(buf, "False win claim: '%s'", resultDetails);
9047                               result = claimer == 'w' ? BlackWins : WhiteWins;
9048                               resultDetails = buf;
9049                       }
9050                 } else
9051                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9052                     && (forwardMostMove <= backwardMostMove ||
9053                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9054                         (claimer=='b')==(forwardMostMove&1))
9055                                                                                   ) {
9056                       /* [HGM] verify: draws that were not flagged are false claims */
9057                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9058                       result = claimer == 'w' ? BlackWins : WhiteWins;
9059                       resultDetails = buf;
9060                 }
9061                 /* (Claiming a loss is accepted no questions asked!) */
9062             }
9063             /* [HGM] bare: don't allow bare King to win */
9064             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9065                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9066                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9067                && result != GameIsDrawn)
9068             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9069                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9070                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9071                         if(p >= 0 && p <= (int)WhiteKing) k++;
9072                 }
9073                 if (appData.debugMode) {
9074                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9075                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9076                 }
9077                 if(k <= 1) {
9078                         result = GameIsDrawn;
9079                         sprintf(buf, "%s but bare king", resultDetails);
9080                         resultDetails = buf;
9081                 }
9082             }
9083         }
9084
9085
9086         if(serverMoves != NULL && !loadFlag) { char c = '=';
9087             if(result==WhiteWins) c = '+';
9088             if(result==BlackWins) c = '-';
9089             if(resultDetails != NULL)
9090                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9091         }
9092         if (resultDetails != NULL) {
9093             gameInfo.result = result;
9094             gameInfo.resultDetails = StrSave(resultDetails);
9095
9096             /* display last move only if game was not loaded from file */
9097             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9098                 DisplayMove(currentMove - 1);
9099     
9100             if (forwardMostMove != 0) {
9101                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9102                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9103                                                                 ) {
9104                     if (*appData.saveGameFile != NULLCHAR) {
9105                         SaveGameToFile(appData.saveGameFile, TRUE);
9106                     } else if (appData.autoSaveGames) {
9107                         AutoSaveGame();
9108                     }
9109                     if (*appData.savePositionFile != NULLCHAR) {
9110                         SavePositionToFile(appData.savePositionFile);
9111                     }
9112                 }
9113             }
9114
9115             /* Tell program how game ended in case it is learning */
9116             /* [HGM] Moved this to after saving the PGN, just in case */
9117             /* engine died and we got here through time loss. In that */
9118             /* case we will get a fatal error writing the pipe, which */
9119             /* would otherwise lose us the PGN.                       */
9120             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9121             /* output during GameEnds should never be fatal anymore   */
9122             if (gameMode == MachinePlaysWhite ||
9123                 gameMode == MachinePlaysBlack ||
9124                 gameMode == TwoMachinesPlay ||
9125                 gameMode == IcsPlayingWhite ||
9126                 gameMode == IcsPlayingBlack ||
9127                 gameMode == BeginningOfGame) {
9128                 char buf[MSG_SIZ];
9129                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9130                         resultDetails);
9131                 if (first.pr != NoProc) {
9132                     SendToProgram(buf, &first);
9133                 }
9134                 if (second.pr != NoProc &&
9135                     gameMode == TwoMachinesPlay) {
9136                     SendToProgram(buf, &second);
9137                 }
9138             }
9139         }
9140
9141         if (appData.icsActive) {
9142             if (appData.quietPlay &&
9143                 (gameMode == IcsPlayingWhite ||
9144                  gameMode == IcsPlayingBlack)) {
9145                 SendToICS(ics_prefix);
9146                 SendToICS("set shout 1\n");
9147             }
9148             nextGameMode = IcsIdle;
9149             ics_user_moved = FALSE;
9150             /* clean up premove.  It's ugly when the game has ended and the
9151              * premove highlights are still on the board.
9152              */
9153             if (gotPremove) {
9154               gotPremove = FALSE;
9155               ClearPremoveHighlights();
9156               DrawPosition(FALSE, boards[currentMove]);
9157             }
9158             if (whosays == GE_ICS) {
9159                 switch (result) {
9160                 case WhiteWins:
9161                     if (gameMode == IcsPlayingWhite)
9162                         PlayIcsWinSound();
9163                     else if(gameMode == IcsPlayingBlack)
9164                         PlayIcsLossSound();
9165                     break;
9166                 case BlackWins:
9167                     if (gameMode == IcsPlayingBlack)
9168                         PlayIcsWinSound();
9169                     else if(gameMode == IcsPlayingWhite)
9170                         PlayIcsLossSound();
9171                     break;
9172                 case GameIsDrawn:
9173                     PlayIcsDrawSound();
9174                     break;
9175                 default:
9176                     PlayIcsUnfinishedSound();
9177                 }
9178             }
9179         } else if (gameMode == EditGame ||
9180                    gameMode == PlayFromGameFile || 
9181                    gameMode == AnalyzeMode || 
9182                    gameMode == AnalyzeFile) {
9183             nextGameMode = gameMode;
9184         } else {
9185             nextGameMode = EndOfGame;
9186         }
9187         pausing = FALSE;
9188         ModeHighlight();
9189     } else {
9190         nextGameMode = gameMode;
9191     }
9192
9193     if (appData.noChessProgram) {
9194         gameMode = nextGameMode;
9195         ModeHighlight();
9196         endingGame = 0; /* [HGM] crash */
9197         return;
9198     }
9199
9200     if (first.reuse) {
9201         /* Put first chess program into idle state */
9202         if (first.pr != NoProc &&
9203             (gameMode == MachinePlaysWhite ||
9204              gameMode == MachinePlaysBlack ||
9205              gameMode == TwoMachinesPlay ||
9206              gameMode == IcsPlayingWhite ||
9207              gameMode == IcsPlayingBlack ||
9208              gameMode == BeginningOfGame)) {
9209             SendToProgram("force\n", &first);
9210             if (first.usePing) {
9211               char buf[MSG_SIZ];
9212               sprintf(buf, "ping %d\n", ++first.lastPing);
9213               SendToProgram(buf, &first);
9214             }
9215         }
9216     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9217         /* Kill off first chess program */
9218         if (first.isr != NULL)
9219           RemoveInputSource(first.isr);
9220         first.isr = NULL;
9221     
9222         if (first.pr != NoProc) {
9223             ExitAnalyzeMode();
9224             DoSleep( appData.delayBeforeQuit );
9225             SendToProgram("quit\n", &first);
9226             DoSleep( appData.delayAfterQuit );
9227             DestroyChildProcess(first.pr, first.useSigterm);
9228         }
9229         first.pr = NoProc;
9230     }
9231     if (second.reuse) {
9232         /* Put second chess program into idle state */
9233         if (second.pr != NoProc &&
9234             gameMode == TwoMachinesPlay) {
9235             SendToProgram("force\n", &second);
9236             if (second.usePing) {
9237               char buf[MSG_SIZ];
9238               sprintf(buf, "ping %d\n", ++second.lastPing);
9239               SendToProgram(buf, &second);
9240             }
9241         }
9242     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9243         /* Kill off second chess program */
9244         if (second.isr != NULL)
9245           RemoveInputSource(second.isr);
9246         second.isr = NULL;
9247     
9248         if (second.pr != NoProc) {
9249             DoSleep( appData.delayBeforeQuit );
9250             SendToProgram("quit\n", &second);
9251             DoSleep( appData.delayAfterQuit );
9252             DestroyChildProcess(second.pr, second.useSigterm);
9253         }
9254         second.pr = NoProc;
9255     }
9256
9257     if (matchMode && gameMode == TwoMachinesPlay) {
9258         switch (result) {
9259         case WhiteWins:
9260           if (first.twoMachinesColor[0] == 'w') {
9261             first.matchWins++;
9262           } else {
9263             second.matchWins++;
9264           }
9265           break;
9266         case BlackWins:
9267           if (first.twoMachinesColor[0] == 'b') {
9268             first.matchWins++;
9269           } else {
9270             second.matchWins++;
9271           }
9272           break;
9273         default:
9274           break;
9275         }
9276         if (matchGame < appData.matchGames) {
9277             char *tmp;
9278             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9279                 tmp = first.twoMachinesColor;
9280                 first.twoMachinesColor = second.twoMachinesColor;
9281                 second.twoMachinesColor = tmp;
9282             }
9283             gameMode = nextGameMode;
9284             matchGame++;
9285             if(appData.matchPause>10000 || appData.matchPause<10)
9286                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9287             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9288             endingGame = 0; /* [HGM] crash */
9289             return;
9290         } else {
9291             char buf[MSG_SIZ];
9292             gameMode = nextGameMode;
9293             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9294                     first.tidy, second.tidy,
9295                     first.matchWins, second.matchWins,
9296                     appData.matchGames - (first.matchWins + second.matchWins));
9297             DisplayFatalError(buf, 0, 0);
9298         }
9299     }
9300     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9301         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9302       ExitAnalyzeMode();
9303     gameMode = nextGameMode;
9304     ModeHighlight();
9305     endingGame = 0;  /* [HGM] crash */
9306 }
9307
9308 /* Assumes program was just initialized (initString sent).
9309    Leaves program in force mode. */
9310 void
9311 FeedMovesToProgram(cps, upto) 
9312      ChessProgramState *cps;
9313      int upto;
9314 {
9315     int i;
9316     
9317     if (appData.debugMode)
9318       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9319               startedFromSetupPosition ? "position and " : "",
9320               backwardMostMove, upto, cps->which);
9321     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9322         // [HGM] variantswitch: make engine aware of new variant
9323         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9324                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9325         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9326         SendToProgram(buf, cps);
9327         currentlyInitializedVariant = gameInfo.variant;
9328     }
9329     SendToProgram("force\n", cps);
9330     if (startedFromSetupPosition) {
9331         SendBoard(cps, backwardMostMove);
9332     if (appData.debugMode) {
9333         fprintf(debugFP, "feedMoves\n");
9334     }
9335     }
9336     for (i = backwardMostMove; i < upto; i++) {
9337         SendMoveToProgram(i, cps);
9338     }
9339 }
9340
9341
9342 void
9343 ResurrectChessProgram()
9344 {
9345      /* The chess program may have exited.
9346         If so, restart it and feed it all the moves made so far. */
9347
9348     if (appData.noChessProgram || first.pr != NoProc) return;
9349     
9350     StartChessProgram(&first);
9351     InitChessProgram(&first, FALSE);
9352     FeedMovesToProgram(&first, currentMove);
9353
9354     if (!first.sendTime) {
9355         /* can't tell gnuchess what its clock should read,
9356            so we bow to its notion. */
9357         ResetClocks();
9358         timeRemaining[0][currentMove] = whiteTimeRemaining;
9359         timeRemaining[1][currentMove] = blackTimeRemaining;
9360     }
9361
9362     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9363                 appData.icsEngineAnalyze) && first.analysisSupport) {
9364       SendToProgram("analyze\n", &first);
9365       first.analyzing = TRUE;
9366     }
9367 }
9368
9369 /*
9370  * Button procedures
9371  */
9372 void
9373 Reset(redraw, init)
9374      int redraw, init;
9375 {
9376     int i;
9377
9378     if (appData.debugMode) {
9379         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9380                 redraw, init, gameMode);
9381     }
9382     CleanupTail(); // [HGM] vari: delete any stored variations
9383     pausing = pauseExamInvalid = FALSE;
9384     startedFromSetupPosition = blackPlaysFirst = FALSE;
9385     firstMove = TRUE;
9386     whiteFlag = blackFlag = FALSE;
9387     userOfferedDraw = FALSE;
9388     hintRequested = bookRequested = FALSE;
9389     first.maybeThinking = FALSE;
9390     second.maybeThinking = FALSE;
9391     first.bookSuspend = FALSE; // [HGM] book
9392     second.bookSuspend = FALSE;
9393     thinkOutput[0] = NULLCHAR;
9394     lastHint[0] = NULLCHAR;
9395     ClearGameInfo(&gameInfo);
9396     gameInfo.variant = StringToVariant(appData.variant);
9397     ics_user_moved = ics_clock_paused = FALSE;
9398     ics_getting_history = H_FALSE;
9399     ics_gamenum = -1;
9400     white_holding[0] = black_holding[0] = NULLCHAR;
9401     ClearProgramStats();
9402     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9403     
9404     ResetFrontEnd();
9405     ClearHighlights();
9406     flipView = appData.flipView;
9407     ClearPremoveHighlights();
9408     gotPremove = FALSE;
9409     alarmSounded = FALSE;
9410
9411     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9412     if(appData.serverMovesName != NULL) {
9413         /* [HGM] prepare to make moves file for broadcasting */
9414         clock_t t = clock();
9415         if(serverMoves != NULL) fclose(serverMoves);
9416         serverMoves = fopen(appData.serverMovesName, "r");
9417         if(serverMoves != NULL) {
9418             fclose(serverMoves);
9419             /* delay 15 sec before overwriting, so all clients can see end */
9420             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9421         }
9422         serverMoves = fopen(appData.serverMovesName, "w");
9423     }
9424
9425     ExitAnalyzeMode();
9426     gameMode = BeginningOfGame;
9427     ModeHighlight();
9428     if(appData.icsActive) gameInfo.variant = VariantNormal;
9429     currentMove = forwardMostMove = backwardMostMove = 0;
9430     InitPosition(redraw);
9431     for (i = 0; i < MAX_MOVES; i++) {
9432         if (commentList[i] != NULL) {
9433             free(commentList[i]);
9434             commentList[i] = NULL;
9435         }
9436     }
9437     ResetClocks();
9438     timeRemaining[0][0] = whiteTimeRemaining;
9439     timeRemaining[1][0] = blackTimeRemaining;
9440     if (first.pr == NULL) {
9441         StartChessProgram(&first);
9442     }
9443     if (init) {
9444             InitChessProgram(&first, startedFromSetupPosition);
9445     }
9446     DisplayTitle("");
9447     DisplayMessage("", "");
9448     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9449     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9450 }
9451
9452 void
9453 AutoPlayGameLoop()
9454 {
9455     for (;;) {
9456         if (!AutoPlayOneMove())
9457           return;
9458         if (matchMode || appData.timeDelay == 0)
9459           continue;
9460         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9461           return;
9462         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9463         break;
9464     }
9465 }
9466
9467
9468 int
9469 AutoPlayOneMove()
9470 {
9471     int fromX, fromY, toX, toY;
9472
9473     if (appData.debugMode) {
9474       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9475     }
9476
9477     if (gameMode != PlayFromGameFile)
9478       return FALSE;
9479
9480     if (currentMove >= forwardMostMove) {
9481       gameMode = EditGame;
9482       ModeHighlight();
9483
9484       /* [AS] Clear current move marker at the end of a game */
9485       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9486
9487       return FALSE;
9488     }
9489     
9490     toX = moveList[currentMove][2] - AAA;
9491     toY = moveList[currentMove][3] - ONE;
9492
9493     if (moveList[currentMove][1] == '@') {
9494         if (appData.highlightLastMove) {
9495             SetHighlights(-1, -1, toX, toY);
9496         }
9497     } else {
9498         fromX = moveList[currentMove][0] - AAA;
9499         fromY = moveList[currentMove][1] - ONE;
9500
9501         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9502
9503         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9504
9505         if (appData.highlightLastMove) {
9506             SetHighlights(fromX, fromY, toX, toY);
9507         }
9508     }
9509     DisplayMove(currentMove);
9510     SendMoveToProgram(currentMove++, &first);
9511     DisplayBothClocks();
9512     DrawPosition(FALSE, boards[currentMove]);
9513     // [HGM] PV info: always display, routine tests if empty
9514     DisplayComment(currentMove - 1, commentList[currentMove]);
9515     return TRUE;
9516 }
9517
9518
9519 int
9520 LoadGameOneMove(readAhead)
9521      ChessMove readAhead;
9522 {
9523     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9524     char promoChar = NULLCHAR;
9525     ChessMove moveType;
9526     char move[MSG_SIZ];
9527     char *p, *q;
9528     
9529     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9530         gameMode != AnalyzeMode && gameMode != Training) {
9531         gameFileFP = NULL;
9532         return FALSE;
9533     }
9534     
9535     yyboardindex = forwardMostMove;
9536     if (readAhead != (ChessMove)0) {
9537       moveType = readAhead;
9538     } else {
9539       if (gameFileFP == NULL)
9540           return FALSE;
9541       moveType = (ChessMove) yylex();
9542     }
9543     
9544     done = FALSE;
9545     switch (moveType) {
9546       case Comment:
9547         if (appData.debugMode) 
9548           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9549         p = yy_text;
9550
9551         /* append the comment but don't display it */
9552         AppendComment(currentMove, p, FALSE);
9553         return TRUE;
9554
9555       case WhiteCapturesEnPassant:
9556       case BlackCapturesEnPassant:
9557       case WhitePromotionChancellor:
9558       case BlackPromotionChancellor:
9559       case WhitePromotionArchbishop:
9560       case BlackPromotionArchbishop:
9561       case WhitePromotionCentaur:
9562       case BlackPromotionCentaur:
9563       case WhitePromotionQueen:
9564       case BlackPromotionQueen:
9565       case WhitePromotionRook:
9566       case BlackPromotionRook:
9567       case WhitePromotionBishop:
9568       case BlackPromotionBishop:
9569       case WhitePromotionKnight:
9570       case BlackPromotionKnight:
9571       case WhitePromotionKing:
9572       case BlackPromotionKing:
9573       case NormalMove:
9574       case WhiteKingSideCastle:
9575       case WhiteQueenSideCastle:
9576       case BlackKingSideCastle:
9577       case BlackQueenSideCastle:
9578       case WhiteKingSideCastleWild:
9579       case WhiteQueenSideCastleWild:
9580       case BlackKingSideCastleWild:
9581       case BlackQueenSideCastleWild:
9582       /* PUSH Fabien */
9583       case WhiteHSideCastleFR:
9584       case WhiteASideCastleFR:
9585       case BlackHSideCastleFR:
9586       case BlackASideCastleFR:
9587       /* POP Fabien */
9588         if (appData.debugMode)
9589           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9590         fromX = currentMoveString[0] - AAA;
9591         fromY = currentMoveString[1] - ONE;
9592         toX = currentMoveString[2] - AAA;
9593         toY = currentMoveString[3] - ONE;
9594         promoChar = currentMoveString[4];
9595         break;
9596
9597       case WhiteDrop:
9598       case BlackDrop:
9599         if (appData.debugMode)
9600           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9601         fromX = moveType == WhiteDrop ?
9602           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9603         (int) CharToPiece(ToLower(currentMoveString[0]));
9604         fromY = DROP_RANK;
9605         toX = currentMoveString[2] - AAA;
9606         toY = currentMoveString[3] - ONE;
9607         break;
9608
9609       case WhiteWins:
9610       case BlackWins:
9611       case GameIsDrawn:
9612       case GameUnfinished:
9613         if (appData.debugMode)
9614           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9615         p = strchr(yy_text, '{');
9616         if (p == NULL) p = strchr(yy_text, '(');
9617         if (p == NULL) {
9618             p = yy_text;
9619             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9620         } else {
9621             q = strchr(p, *p == '{' ? '}' : ')');
9622             if (q != NULL) *q = NULLCHAR;
9623             p++;
9624         }
9625         GameEnds(moveType, p, GE_FILE);
9626         done = TRUE;
9627         if (cmailMsgLoaded) {
9628             ClearHighlights();
9629             flipView = WhiteOnMove(currentMove);
9630             if (moveType == GameUnfinished) flipView = !flipView;
9631             if (appData.debugMode)
9632               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9633         }
9634         break;
9635
9636       case (ChessMove) 0:       /* end of file */
9637         if (appData.debugMode)
9638           fprintf(debugFP, "Parser hit end of file\n");
9639         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9640           case MT_NONE:
9641           case MT_CHECK:
9642             break;
9643           case MT_CHECKMATE:
9644           case MT_STAINMATE:
9645             if (WhiteOnMove(currentMove)) {
9646                 GameEnds(BlackWins, "Black mates", GE_FILE);
9647             } else {
9648                 GameEnds(WhiteWins, "White mates", GE_FILE);
9649             }
9650             break;
9651           case MT_STALEMATE:
9652             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9653             break;
9654         }
9655         done = TRUE;
9656         break;
9657
9658       case MoveNumberOne:
9659         if (lastLoadGameStart == GNUChessGame) {
9660             /* GNUChessGames have numbers, but they aren't move numbers */
9661             if (appData.debugMode)
9662               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9663                       yy_text, (int) moveType);
9664             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9665         }
9666         /* else fall thru */
9667
9668       case XBoardGame:
9669       case GNUChessGame:
9670       case PGNTag:
9671         /* Reached start of next game in file */
9672         if (appData.debugMode)
9673           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9674         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9675           case MT_NONE:
9676           case MT_CHECK:
9677             break;
9678           case MT_CHECKMATE:
9679           case MT_STAINMATE:
9680             if (WhiteOnMove(currentMove)) {
9681                 GameEnds(BlackWins, "Black mates", GE_FILE);
9682             } else {
9683                 GameEnds(WhiteWins, "White mates", GE_FILE);
9684             }
9685             break;
9686           case MT_STALEMATE:
9687             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9688             break;
9689         }
9690         done = TRUE;
9691         break;
9692
9693       case PositionDiagram:     /* should not happen; ignore */
9694       case ElapsedTime:         /* ignore */
9695       case NAG:                 /* ignore */
9696         if (appData.debugMode)
9697           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9698                   yy_text, (int) moveType);
9699         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9700
9701       case IllegalMove:
9702         if (appData.testLegality) {
9703             if (appData.debugMode)
9704               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9705             sprintf(move, _("Illegal move: %d.%s%s"),
9706                     (forwardMostMove / 2) + 1,
9707                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9708             DisplayError(move, 0);
9709             done = TRUE;
9710         } else {
9711             if (appData.debugMode)
9712               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9713                       yy_text, currentMoveString);
9714             fromX = currentMoveString[0] - AAA;
9715             fromY = currentMoveString[1] - ONE;
9716             toX = currentMoveString[2] - AAA;
9717             toY = currentMoveString[3] - ONE;
9718             promoChar = currentMoveString[4];
9719         }
9720         break;
9721
9722       case AmbiguousMove:
9723         if (appData.debugMode)
9724           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9725         sprintf(move, _("Ambiguous move: %d.%s%s"),
9726                 (forwardMostMove / 2) + 1,
9727                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9728         DisplayError(move, 0);
9729         done = TRUE;
9730         break;
9731
9732       default:
9733       case ImpossibleMove:
9734         if (appData.debugMode)
9735           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9736         sprintf(move, _("Illegal move: %d.%s%s"),
9737                 (forwardMostMove / 2) + 1,
9738                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9739         DisplayError(move, 0);
9740         done = TRUE;
9741         break;
9742     }
9743
9744     if (done) {
9745         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9746             DrawPosition(FALSE, boards[currentMove]);
9747             DisplayBothClocks();
9748             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9749               DisplayComment(currentMove - 1, commentList[currentMove]);
9750         }
9751         (void) StopLoadGameTimer();
9752         gameFileFP = NULL;
9753         cmailOldMove = forwardMostMove;
9754         return FALSE;
9755     } else {
9756         /* currentMoveString is set as a side-effect of yylex */
9757         strcat(currentMoveString, "\n");
9758         strcpy(moveList[forwardMostMove], currentMoveString);
9759         
9760         thinkOutput[0] = NULLCHAR;
9761         MakeMove(fromX, fromY, toX, toY, promoChar);
9762         currentMove = forwardMostMove;
9763         return TRUE;
9764     }
9765 }
9766
9767 /* Load the nth game from the given file */
9768 int
9769 LoadGameFromFile(filename, n, title, useList)
9770      char *filename;
9771      int n;
9772      char *title;
9773      /*Boolean*/ int useList;
9774 {
9775     FILE *f;
9776     char buf[MSG_SIZ];
9777
9778     if (strcmp(filename, "-") == 0) {
9779         f = stdin;
9780         title = "stdin";
9781     } else {
9782         f = fopen(filename, "rb");
9783         if (f == NULL) {
9784           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9785             DisplayError(buf, errno);
9786             return FALSE;
9787         }
9788     }
9789     if (fseek(f, 0, 0) == -1) {
9790         /* f is not seekable; probably a pipe */
9791         useList = FALSE;
9792     }
9793     if (useList && n == 0) {
9794         int error = GameListBuild(f);
9795         if (error) {
9796             DisplayError(_("Cannot build game list"), error);
9797         } else if (!ListEmpty(&gameList) &&
9798                    ((ListGame *) gameList.tailPred)->number > 1) {
9799             GameListPopUp(f, title);
9800             return TRUE;
9801         }
9802         GameListDestroy();
9803         n = 1;
9804     }
9805     if (n == 0) n = 1;
9806     return LoadGame(f, n, title, FALSE);
9807 }
9808
9809
9810 void
9811 MakeRegisteredMove()
9812 {
9813     int fromX, fromY, toX, toY;
9814     char promoChar;
9815     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9816         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9817           case CMAIL_MOVE:
9818           case CMAIL_DRAW:
9819             if (appData.debugMode)
9820               fprintf(debugFP, "Restoring %s for game %d\n",
9821                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9822     
9823             thinkOutput[0] = NULLCHAR;
9824             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9825             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9826             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9827             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9828             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9829             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9830             MakeMove(fromX, fromY, toX, toY, promoChar);
9831             ShowMove(fromX, fromY, toX, toY);
9832               
9833             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9834               case MT_NONE:
9835               case MT_CHECK:
9836                 break;
9837                 
9838               case MT_CHECKMATE:
9839               case MT_STAINMATE:
9840                 if (WhiteOnMove(currentMove)) {
9841                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9842                 } else {
9843                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9844                 }
9845                 break;
9846                 
9847               case MT_STALEMATE:
9848                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9849                 break;
9850             }
9851
9852             break;
9853             
9854           case CMAIL_RESIGN:
9855             if (WhiteOnMove(currentMove)) {
9856                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9857             } else {
9858                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9859             }
9860             break;
9861             
9862           case CMAIL_ACCEPT:
9863             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9864             break;
9865               
9866           default:
9867             break;
9868         }
9869     }
9870
9871     return;
9872 }
9873
9874 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9875 int
9876 CmailLoadGame(f, gameNumber, title, useList)
9877      FILE *f;
9878      int gameNumber;
9879      char *title;
9880      int useList;
9881 {
9882     int retVal;
9883
9884     if (gameNumber > nCmailGames) {
9885         DisplayError(_("No more games in this message"), 0);
9886         return FALSE;
9887     }
9888     if (f == lastLoadGameFP) {
9889         int offset = gameNumber - lastLoadGameNumber;
9890         if (offset == 0) {
9891             cmailMsg[0] = NULLCHAR;
9892             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9893                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9894                 nCmailMovesRegistered--;
9895             }
9896             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9897             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9898                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9899             }
9900         } else {
9901             if (! RegisterMove()) return FALSE;
9902         }
9903     }
9904
9905     retVal = LoadGame(f, gameNumber, title, useList);
9906
9907     /* Make move registered during previous look at this game, if any */
9908     MakeRegisteredMove();
9909
9910     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9911         commentList[currentMove]
9912           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9913         DisplayComment(currentMove - 1, commentList[currentMove]);
9914     }
9915
9916     return retVal;
9917 }
9918
9919 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9920 int
9921 ReloadGame(offset)
9922      int offset;
9923 {
9924     int gameNumber = lastLoadGameNumber + offset;
9925     if (lastLoadGameFP == NULL) {
9926         DisplayError(_("No game has been loaded yet"), 0);
9927         return FALSE;
9928     }
9929     if (gameNumber <= 0) {
9930         DisplayError(_("Can't back up any further"), 0);
9931         return FALSE;
9932     }
9933     if (cmailMsgLoaded) {
9934         return CmailLoadGame(lastLoadGameFP, gameNumber,
9935                              lastLoadGameTitle, lastLoadGameUseList);
9936     } else {
9937         return LoadGame(lastLoadGameFP, gameNumber,
9938                         lastLoadGameTitle, lastLoadGameUseList);
9939     }
9940 }
9941
9942
9943
9944 /* Load the nth game from open file f */
9945 int
9946 LoadGame(f, gameNumber, title, useList)
9947      FILE *f;
9948      int gameNumber;
9949      char *title;
9950      int useList;
9951 {
9952     ChessMove cm;
9953     char buf[MSG_SIZ];
9954     int gn = gameNumber;
9955     ListGame *lg = NULL;
9956     int numPGNTags = 0;
9957     int err;
9958     GameMode oldGameMode;
9959     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9960
9961     if (appData.debugMode) 
9962         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9963
9964     if (gameMode == Training )
9965         SetTrainingModeOff();
9966
9967     oldGameMode = gameMode;
9968     if (gameMode != BeginningOfGame) {
9969       Reset(FALSE, TRUE);
9970     }
9971
9972     gameFileFP = f;
9973     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9974         fclose(lastLoadGameFP);
9975     }
9976
9977     if (useList) {
9978         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9979         
9980         if (lg) {
9981             fseek(f, lg->offset, 0);
9982             GameListHighlight(gameNumber);
9983             gn = 1;
9984         }
9985         else {
9986             DisplayError(_("Game number out of range"), 0);
9987             return FALSE;
9988         }
9989     } else {
9990         GameListDestroy();
9991         if (fseek(f, 0, 0) == -1) {
9992             if (f == lastLoadGameFP ?
9993                 gameNumber == lastLoadGameNumber + 1 :
9994                 gameNumber == 1) {
9995                 gn = 1;
9996             } else {
9997                 DisplayError(_("Can't seek on game file"), 0);
9998                 return FALSE;
9999             }
10000         }
10001     }
10002     lastLoadGameFP = f;
10003     lastLoadGameNumber = gameNumber;
10004     strcpy(lastLoadGameTitle, title);
10005     lastLoadGameUseList = useList;
10006
10007     yynewfile(f);
10008
10009     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10010       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10011                 lg->gameInfo.black);
10012             DisplayTitle(buf);
10013     } else if (*title != NULLCHAR) {
10014         if (gameNumber > 1) {
10015             sprintf(buf, "%s %d", title, gameNumber);
10016             DisplayTitle(buf);
10017         } else {
10018             DisplayTitle(title);
10019         }
10020     }
10021
10022     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10023         gameMode = PlayFromGameFile;
10024         ModeHighlight();
10025     }
10026
10027     currentMove = forwardMostMove = backwardMostMove = 0;
10028     CopyBoard(boards[0], initialPosition);
10029     StopClocks();
10030
10031     /*
10032      * Skip the first gn-1 games in the file.
10033      * Also skip over anything that precedes an identifiable 
10034      * start of game marker, to avoid being confused by 
10035      * garbage at the start of the file.  Currently 
10036      * recognized start of game markers are the move number "1",
10037      * the pattern "gnuchess .* game", the pattern
10038      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10039      * A game that starts with one of the latter two patterns
10040      * will also have a move number 1, possibly
10041      * following a position diagram.
10042      * 5-4-02: Let's try being more lenient and allowing a game to
10043      * start with an unnumbered move.  Does that break anything?
10044      */
10045     cm = lastLoadGameStart = (ChessMove) 0;
10046     while (gn > 0) {
10047         yyboardindex = forwardMostMove;
10048         cm = (ChessMove) yylex();
10049         switch (cm) {
10050           case (ChessMove) 0:
10051             if (cmailMsgLoaded) {
10052                 nCmailGames = CMAIL_MAX_GAMES - gn;
10053             } else {
10054                 Reset(TRUE, TRUE);
10055                 DisplayError(_("Game not found in file"), 0);
10056             }
10057             return FALSE;
10058
10059           case GNUChessGame:
10060           case XBoardGame:
10061             gn--;
10062             lastLoadGameStart = cm;
10063             break;
10064             
10065           case MoveNumberOne:
10066             switch (lastLoadGameStart) {
10067               case GNUChessGame:
10068               case XBoardGame:
10069               case PGNTag:
10070                 break;
10071               case MoveNumberOne:
10072               case (ChessMove) 0:
10073                 gn--;           /* count this game */
10074                 lastLoadGameStart = cm;
10075                 break;
10076               default:
10077                 /* impossible */
10078                 break;
10079             }
10080             break;
10081
10082           case PGNTag:
10083             switch (lastLoadGameStart) {
10084               case GNUChessGame:
10085               case PGNTag:
10086               case MoveNumberOne:
10087               case (ChessMove) 0:
10088                 gn--;           /* count this game */
10089                 lastLoadGameStart = cm;
10090                 break;
10091               case XBoardGame:
10092                 lastLoadGameStart = cm; /* game counted already */
10093                 break;
10094               default:
10095                 /* impossible */
10096                 break;
10097             }
10098             if (gn > 0) {
10099                 do {
10100                     yyboardindex = forwardMostMove;
10101                     cm = (ChessMove) yylex();
10102                 } while (cm == PGNTag || cm == Comment);
10103             }
10104             break;
10105
10106           case WhiteWins:
10107           case BlackWins:
10108           case GameIsDrawn:
10109             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10110                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10111                     != CMAIL_OLD_RESULT) {
10112                     nCmailResults ++ ;
10113                     cmailResult[  CMAIL_MAX_GAMES
10114                                 - gn - 1] = CMAIL_OLD_RESULT;
10115                 }
10116             }
10117             break;
10118
10119           case NormalMove:
10120             /* Only a NormalMove can be at the start of a game
10121              * without a position diagram. */
10122             if (lastLoadGameStart == (ChessMove) 0) {
10123               gn--;
10124               lastLoadGameStart = MoveNumberOne;
10125             }
10126             break;
10127
10128           default:
10129             break;
10130         }
10131     }
10132     
10133     if (appData.debugMode)
10134       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10135
10136     if (cm == XBoardGame) {
10137         /* Skip any header junk before position diagram and/or move 1 */
10138         for (;;) {
10139             yyboardindex = forwardMostMove;
10140             cm = (ChessMove) yylex();
10141
10142             if (cm == (ChessMove) 0 ||
10143                 cm == GNUChessGame || cm == XBoardGame) {
10144                 /* Empty game; pretend end-of-file and handle later */
10145                 cm = (ChessMove) 0;
10146                 break;
10147             }
10148
10149             if (cm == MoveNumberOne || cm == PositionDiagram ||
10150                 cm == PGNTag || cm == Comment)
10151               break;
10152         }
10153     } else if (cm == GNUChessGame) {
10154         if (gameInfo.event != NULL) {
10155             free(gameInfo.event);
10156         }
10157         gameInfo.event = StrSave(yy_text);
10158     }   
10159
10160     startedFromSetupPosition = FALSE;
10161     while (cm == PGNTag) {
10162         if (appData.debugMode) 
10163           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10164         err = ParsePGNTag(yy_text, &gameInfo);
10165         if (!err) numPGNTags++;
10166
10167         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10168         if(gameInfo.variant != oldVariant) {
10169             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10170             InitPosition(TRUE);
10171             oldVariant = gameInfo.variant;
10172             if (appData.debugMode) 
10173               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10174         }
10175
10176
10177         if (gameInfo.fen != NULL) {
10178           Board initial_position;
10179           startedFromSetupPosition = TRUE;
10180           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10181             Reset(TRUE, TRUE);
10182             DisplayError(_("Bad FEN position in file"), 0);
10183             return FALSE;
10184           }
10185           CopyBoard(boards[0], initial_position);
10186           if (blackPlaysFirst) {
10187             currentMove = forwardMostMove = backwardMostMove = 1;
10188             CopyBoard(boards[1], initial_position);
10189             strcpy(moveList[0], "");
10190             strcpy(parseList[0], "");
10191             timeRemaining[0][1] = whiteTimeRemaining;
10192             timeRemaining[1][1] = blackTimeRemaining;
10193             if (commentList[0] != NULL) {
10194               commentList[1] = commentList[0];
10195               commentList[0] = NULL;
10196             }
10197           } else {
10198             currentMove = forwardMostMove = backwardMostMove = 0;
10199           }
10200           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10201           {   int i;
10202               initialRulePlies = FENrulePlies;
10203               for( i=0; i< nrCastlingRights; i++ )
10204                   initialRights[i] = initial_position[CASTLING][i];
10205           }
10206           yyboardindex = forwardMostMove;
10207           free(gameInfo.fen);
10208           gameInfo.fen = NULL;
10209         }
10210
10211         yyboardindex = forwardMostMove;
10212         cm = (ChessMove) yylex();
10213
10214         /* Handle comments interspersed among the tags */
10215         while (cm == Comment) {
10216             char *p;
10217             if (appData.debugMode) 
10218               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10219             p = yy_text;
10220             AppendComment(currentMove, p, FALSE);
10221             yyboardindex = forwardMostMove;
10222             cm = (ChessMove) yylex();
10223         }
10224     }
10225
10226     /* don't rely on existence of Event tag since if game was
10227      * pasted from clipboard the Event tag may not exist
10228      */
10229     if (numPGNTags > 0){
10230         char *tags;
10231         if (gameInfo.variant == VariantNormal) {
10232           VariantClass v = StringToVariant(gameInfo.event);
10233           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10234           if(v < VariantShogi) gameInfo.variant = v;
10235         }
10236         if (!matchMode) {
10237           if( appData.autoDisplayTags ) {
10238             tags = PGNTags(&gameInfo);
10239             TagsPopUp(tags, CmailMsg());
10240             free(tags);
10241           }
10242         }
10243     } else {
10244         /* Make something up, but don't display it now */
10245         SetGameInfo();
10246         TagsPopDown();
10247     }
10248
10249     if (cm == PositionDiagram) {
10250         int i, j;
10251         char *p;
10252         Board initial_position;
10253
10254         if (appData.debugMode)
10255           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10256
10257         if (!startedFromSetupPosition) {
10258             p = yy_text;
10259             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10260               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10261                 switch (*p) {
10262                   case '[':
10263                   case '-':
10264                   case ' ':
10265                   case '\t':
10266                   case '\n':
10267                   case '\r':
10268                     break;
10269                   default:
10270                     initial_position[i][j++] = CharToPiece(*p);
10271                     break;
10272                 }
10273             while (*p == ' ' || *p == '\t' ||
10274                    *p == '\n' || *p == '\r') p++;
10275         
10276             if (strncmp(p, "black", strlen("black"))==0)
10277               blackPlaysFirst = TRUE;
10278             else
10279               blackPlaysFirst = FALSE;
10280             startedFromSetupPosition = TRUE;
10281         
10282             CopyBoard(boards[0], initial_position);
10283             if (blackPlaysFirst) {
10284                 currentMove = forwardMostMove = backwardMostMove = 1;
10285                 CopyBoard(boards[1], initial_position);
10286                 strcpy(moveList[0], "");
10287                 strcpy(parseList[0], "");
10288                 timeRemaining[0][1] = whiteTimeRemaining;
10289                 timeRemaining[1][1] = blackTimeRemaining;
10290                 if (commentList[0] != NULL) {
10291                     commentList[1] = commentList[0];
10292                     commentList[0] = NULL;
10293                 }
10294             } else {
10295                 currentMove = forwardMostMove = backwardMostMove = 0;
10296             }
10297         }
10298         yyboardindex = forwardMostMove;
10299         cm = (ChessMove) yylex();
10300     }
10301
10302     if (first.pr == NoProc) {
10303         StartChessProgram(&first);
10304     }
10305     InitChessProgram(&first, FALSE);
10306     SendToProgram("force\n", &first);
10307     if (startedFromSetupPosition) {
10308         SendBoard(&first, forwardMostMove);
10309     if (appData.debugMode) {
10310         fprintf(debugFP, "Load Game\n");
10311     }
10312         DisplayBothClocks();
10313     }      
10314
10315     /* [HGM] server: flag to write setup moves in broadcast file as one */
10316     loadFlag = appData.suppressLoadMoves;
10317
10318     while (cm == Comment) {
10319         char *p;
10320         if (appData.debugMode) 
10321           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10322         p = yy_text;
10323         AppendComment(currentMove, p, FALSE);
10324         yyboardindex = forwardMostMove;
10325         cm = (ChessMove) yylex();
10326     }
10327
10328     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10329         cm == WhiteWins || cm == BlackWins ||
10330         cm == GameIsDrawn || cm == GameUnfinished) {
10331         DisplayMessage("", _("No moves in game"));
10332         if (cmailMsgLoaded) {
10333             if (appData.debugMode)
10334               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10335             ClearHighlights();
10336             flipView = FALSE;
10337         }
10338         DrawPosition(FALSE, boards[currentMove]);
10339         DisplayBothClocks();
10340         gameMode = EditGame;
10341         ModeHighlight();
10342         gameFileFP = NULL;
10343         cmailOldMove = 0;
10344         return TRUE;
10345     }
10346
10347     // [HGM] PV info: routine tests if comment empty
10348     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10349         DisplayComment(currentMove - 1, commentList[currentMove]);
10350     }
10351     if (!matchMode && appData.timeDelay != 0) 
10352       DrawPosition(FALSE, boards[currentMove]);
10353
10354     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10355       programStats.ok_to_send = 1;
10356     }
10357
10358     /* if the first token after the PGN tags is a move
10359      * and not move number 1, retrieve it from the parser 
10360      */
10361     if (cm != MoveNumberOne)
10362         LoadGameOneMove(cm);
10363
10364     /* load the remaining moves from the file */
10365     while (LoadGameOneMove((ChessMove)0)) {
10366       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10367       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10368     }
10369
10370     /* rewind to the start of the game */
10371     currentMove = backwardMostMove;
10372
10373     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10374
10375     if (oldGameMode == AnalyzeFile ||
10376         oldGameMode == AnalyzeMode) {
10377       AnalyzeFileEvent();
10378     }
10379
10380     if (matchMode || appData.timeDelay == 0) {
10381       ToEndEvent();
10382       gameMode = EditGame;
10383       ModeHighlight();
10384     } else if (appData.timeDelay > 0) {
10385       AutoPlayGameLoop();
10386     }
10387
10388     if (appData.debugMode) 
10389         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10390
10391     loadFlag = 0; /* [HGM] true game starts */
10392     return TRUE;
10393 }
10394
10395 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10396 int
10397 ReloadPosition(offset)
10398      int offset;
10399 {
10400     int positionNumber = lastLoadPositionNumber + offset;
10401     if (lastLoadPositionFP == NULL) {
10402         DisplayError(_("No position has been loaded yet"), 0);
10403         return FALSE;
10404     }
10405     if (positionNumber <= 0) {
10406         DisplayError(_("Can't back up any further"), 0);
10407         return FALSE;
10408     }
10409     return LoadPosition(lastLoadPositionFP, positionNumber,
10410                         lastLoadPositionTitle);
10411 }
10412
10413 /* Load the nth position from the given file */
10414 int
10415 LoadPositionFromFile(filename, n, title)
10416      char *filename;
10417      int n;
10418      char *title;
10419 {
10420     FILE *f;
10421     char buf[MSG_SIZ];
10422
10423     if (strcmp(filename, "-") == 0) {
10424         return LoadPosition(stdin, n, "stdin");
10425     } else {
10426         f = fopen(filename, "rb");
10427         if (f == NULL) {
10428             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10429             DisplayError(buf, errno);
10430             return FALSE;
10431         } else {
10432             return LoadPosition(f, n, title);
10433         }
10434     }
10435 }
10436
10437 /* Load the nth position from the given open file, and close it */
10438 int
10439 LoadPosition(f, positionNumber, title)
10440      FILE *f;
10441      int positionNumber;
10442      char *title;
10443 {
10444     char *p, line[MSG_SIZ];
10445     Board initial_position;
10446     int i, j, fenMode, pn;
10447     
10448     if (gameMode == Training )
10449         SetTrainingModeOff();
10450
10451     if (gameMode != BeginningOfGame) {
10452         Reset(FALSE, TRUE);
10453     }
10454     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10455         fclose(lastLoadPositionFP);
10456     }
10457     if (positionNumber == 0) positionNumber = 1;
10458     lastLoadPositionFP = f;
10459     lastLoadPositionNumber = positionNumber;
10460     strcpy(lastLoadPositionTitle, title);
10461     if (first.pr == NoProc) {
10462       StartChessProgram(&first);
10463       InitChessProgram(&first, FALSE);
10464     }    
10465     pn = positionNumber;
10466     if (positionNumber < 0) {
10467         /* Negative position number means to seek to that byte offset */
10468         if (fseek(f, -positionNumber, 0) == -1) {
10469             DisplayError(_("Can't seek on position file"), 0);
10470             return FALSE;
10471         };
10472         pn = 1;
10473     } else {
10474         if (fseek(f, 0, 0) == -1) {
10475             if (f == lastLoadPositionFP ?
10476                 positionNumber == lastLoadPositionNumber + 1 :
10477                 positionNumber == 1) {
10478                 pn = 1;
10479             } else {
10480                 DisplayError(_("Can't seek on position file"), 0);
10481                 return FALSE;
10482             }
10483         }
10484     }
10485     /* See if this file is FEN or old-style xboard */
10486     if (fgets(line, MSG_SIZ, f) == NULL) {
10487         DisplayError(_("Position not found in file"), 0);
10488         return FALSE;
10489     }
10490     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10491     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10492
10493     if (pn >= 2) {
10494         if (fenMode || line[0] == '#') pn--;
10495         while (pn > 0) {
10496             /* skip positions before number pn */
10497             if (fgets(line, MSG_SIZ, f) == NULL) {
10498                 Reset(TRUE, TRUE);
10499                 DisplayError(_("Position not found in file"), 0);
10500                 return FALSE;
10501             }
10502             if (fenMode || line[0] == '#') pn--;
10503         }
10504     }
10505
10506     if (fenMode) {
10507         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10508             DisplayError(_("Bad FEN position in file"), 0);
10509             return FALSE;
10510         }
10511     } else {
10512         (void) fgets(line, MSG_SIZ, f);
10513         (void) fgets(line, MSG_SIZ, f);
10514     
10515         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10516             (void) fgets(line, MSG_SIZ, f);
10517             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10518                 if (*p == ' ')
10519                   continue;
10520                 initial_position[i][j++] = CharToPiece(*p);
10521             }
10522         }
10523     
10524         blackPlaysFirst = FALSE;
10525         if (!feof(f)) {
10526             (void) fgets(line, MSG_SIZ, f);
10527             if (strncmp(line, "black", strlen("black"))==0)
10528               blackPlaysFirst = TRUE;
10529         }
10530     }
10531     startedFromSetupPosition = TRUE;
10532     
10533     SendToProgram("force\n", &first);
10534     CopyBoard(boards[0], initial_position);
10535     if (blackPlaysFirst) {
10536         currentMove = forwardMostMove = backwardMostMove = 1;
10537         strcpy(moveList[0], "");
10538         strcpy(parseList[0], "");
10539         CopyBoard(boards[1], initial_position);
10540         DisplayMessage("", _("Black to play"));
10541     } else {
10542         currentMove = forwardMostMove = backwardMostMove = 0;
10543         DisplayMessage("", _("White to play"));
10544     }
10545     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10546     SendBoard(&first, forwardMostMove);
10547     if (appData.debugMode) {
10548 int i, j;
10549   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10550   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10551         fprintf(debugFP, "Load Position\n");
10552     }
10553
10554     if (positionNumber > 1) {
10555         sprintf(line, "%s %d", title, positionNumber);
10556         DisplayTitle(line);
10557     } else {
10558         DisplayTitle(title);
10559     }
10560     gameMode = EditGame;
10561     ModeHighlight();
10562     ResetClocks();
10563     timeRemaining[0][1] = whiteTimeRemaining;
10564     timeRemaining[1][1] = blackTimeRemaining;
10565     DrawPosition(FALSE, boards[currentMove]);
10566    
10567     return TRUE;
10568 }
10569
10570
10571 void
10572 CopyPlayerNameIntoFileName(dest, src)
10573      char **dest, *src;
10574 {
10575     while (*src != NULLCHAR && *src != ',') {
10576         if (*src == ' ') {
10577             *(*dest)++ = '_';
10578             src++;
10579         } else {
10580             *(*dest)++ = *src++;
10581         }
10582     }
10583 }
10584
10585 char *DefaultFileName(ext)
10586      char *ext;
10587 {
10588     static char def[MSG_SIZ];
10589     char *p;
10590
10591     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10592         p = def;
10593         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10594         *p++ = '-';
10595         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10596         *p++ = '.';
10597         strcpy(p, ext);
10598     } else {
10599         def[0] = NULLCHAR;
10600     }
10601     return def;
10602 }
10603
10604 /* Save the current game to the given file */
10605 int
10606 SaveGameToFile(filename, append)
10607      char *filename;
10608      int append;
10609 {
10610     FILE *f;
10611     char buf[MSG_SIZ];
10612
10613     if (strcmp(filename, "-") == 0) {
10614         return SaveGame(stdout, 0, NULL);
10615     } else {
10616         f = fopen(filename, append ? "a" : "w");
10617         if (f == NULL) {
10618             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10619             DisplayError(buf, errno);
10620             return FALSE;
10621         } else {
10622             return SaveGame(f, 0, NULL);
10623         }
10624     }
10625 }
10626
10627 char *
10628 SavePart(str)
10629      char *str;
10630 {
10631     static char buf[MSG_SIZ];
10632     char *p;
10633     
10634     p = strchr(str, ' ');
10635     if (p == NULL) return str;
10636     strncpy(buf, str, p - str);
10637     buf[p - str] = NULLCHAR;
10638     return buf;
10639 }
10640
10641 #define PGN_MAX_LINE 75
10642
10643 #define PGN_SIDE_WHITE  0
10644 #define PGN_SIDE_BLACK  1
10645
10646 /* [AS] */
10647 static int FindFirstMoveOutOfBook( int side )
10648 {
10649     int result = -1;
10650
10651     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10652         int index = backwardMostMove;
10653         int has_book_hit = 0;
10654
10655         if( (index % 2) != side ) {
10656             index++;
10657         }
10658
10659         while( index < forwardMostMove ) {
10660             /* Check to see if engine is in book */
10661             int depth = pvInfoList[index].depth;
10662             int score = pvInfoList[index].score;
10663             int in_book = 0;
10664
10665             if( depth <= 2 ) {
10666                 in_book = 1;
10667             }
10668             else if( score == 0 && depth == 63 ) {
10669                 in_book = 1; /* Zappa */
10670             }
10671             else if( score == 2 && depth == 99 ) {
10672                 in_book = 1; /* Abrok */
10673             }
10674
10675             has_book_hit += in_book;
10676
10677             if( ! in_book ) {
10678                 result = index;
10679
10680                 break;
10681             }
10682
10683             index += 2;
10684         }
10685     }
10686
10687     return result;
10688 }
10689
10690 /* [AS] */
10691 void GetOutOfBookInfo( char * buf )
10692 {
10693     int oob[2];
10694     int i;
10695     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10696
10697     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10698     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10699
10700     *buf = '\0';
10701
10702     if( oob[0] >= 0 || oob[1] >= 0 ) {
10703         for( i=0; i<2; i++ ) {
10704             int idx = oob[i];
10705
10706             if( idx >= 0 ) {
10707                 if( i > 0 && oob[0] >= 0 ) {
10708                     strcat( buf, "   " );
10709                 }
10710
10711                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10712                 sprintf( buf+strlen(buf), "%s%.2f", 
10713                     pvInfoList[idx].score >= 0 ? "+" : "",
10714                     pvInfoList[idx].score / 100.0 );
10715             }
10716         }
10717     }
10718 }
10719
10720 /* Save game in PGN style and close the file */
10721 int
10722 SaveGamePGN(f)
10723      FILE *f;
10724 {
10725     int i, offset, linelen, newblock;
10726     time_t tm;
10727 //    char *movetext;
10728     char numtext[32];
10729     int movelen, numlen, blank;
10730     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10731
10732     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10733     
10734     tm = time((time_t *) NULL);
10735     
10736     PrintPGNTags(f, &gameInfo);
10737     
10738     if (backwardMostMove > 0 || startedFromSetupPosition) {
10739         char *fen = PositionToFEN(backwardMostMove, NULL);
10740         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10741         fprintf(f, "\n{--------------\n");
10742         PrintPosition(f, backwardMostMove);
10743         fprintf(f, "--------------}\n");
10744         free(fen);
10745     }
10746     else {
10747         /* [AS] Out of book annotation */
10748         if( appData.saveOutOfBookInfo ) {
10749             char buf[64];
10750
10751             GetOutOfBookInfo( buf );
10752
10753             if( buf[0] != '\0' ) {
10754                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10755             }
10756         }
10757
10758         fprintf(f, "\n");
10759     }
10760
10761     i = backwardMostMove;
10762     linelen = 0;
10763     newblock = TRUE;
10764
10765     while (i < forwardMostMove) {
10766         /* Print comments preceding this move */
10767         if (commentList[i] != NULL) {
10768             if (linelen > 0) fprintf(f, "\n");
10769             fprintf(f, "%s", commentList[i]);
10770             linelen = 0;
10771             newblock = TRUE;
10772         }
10773
10774         /* Format move number */
10775         if ((i % 2) == 0) {
10776             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10777         } else {
10778             if (newblock) {
10779                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10780             } else {
10781                 numtext[0] = NULLCHAR;
10782             }
10783         }
10784         numlen = strlen(numtext);
10785         newblock = FALSE;
10786
10787         /* Print move number */
10788         blank = linelen > 0 && numlen > 0;
10789         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10790             fprintf(f, "\n");
10791             linelen = 0;
10792             blank = 0;
10793         }
10794         if (blank) {
10795             fprintf(f, " ");
10796             linelen++;
10797         }
10798         fprintf(f, "%s", numtext);
10799         linelen += numlen;
10800
10801         /* Get move */
10802         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10803         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10804
10805         /* Print move */
10806         blank = linelen > 0 && movelen > 0;
10807         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10808             fprintf(f, "\n");
10809             linelen = 0;
10810             blank = 0;
10811         }
10812         if (blank) {
10813             fprintf(f, " ");
10814             linelen++;
10815         }
10816         fprintf(f, "%s", move_buffer);
10817         linelen += movelen;
10818
10819         /* [AS] Add PV info if present */
10820         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10821             /* [HGM] add time */
10822             char buf[MSG_SIZ]; int seconds;
10823
10824             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10825
10826             if( seconds <= 0) buf[0] = 0; else
10827             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10828                 seconds = (seconds + 4)/10; // round to full seconds
10829                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10830                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10831             }
10832
10833             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10834                 pvInfoList[i].score >= 0 ? "+" : "",
10835                 pvInfoList[i].score / 100.0,
10836                 pvInfoList[i].depth,
10837                 buf );
10838
10839             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10840
10841             /* Print score/depth */
10842             blank = linelen > 0 && movelen > 0;
10843             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10844                 fprintf(f, "\n");
10845                 linelen = 0;
10846                 blank = 0;
10847             }
10848             if (blank) {
10849                 fprintf(f, " ");
10850                 linelen++;
10851             }
10852             fprintf(f, "%s", move_buffer);
10853             linelen += movelen;
10854         }
10855
10856         i++;
10857     }
10858     
10859     /* Start a new line */
10860     if (linelen > 0) fprintf(f, "\n");
10861
10862     /* Print comments after last move */
10863     if (commentList[i] != NULL) {
10864         fprintf(f, "%s\n", commentList[i]);
10865     }
10866
10867     /* Print result */
10868     if (gameInfo.resultDetails != NULL &&
10869         gameInfo.resultDetails[0] != NULLCHAR) {
10870         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10871                 PGNResult(gameInfo.result));
10872     } else {
10873         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10874     }
10875
10876     fclose(f);
10877     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10878     return TRUE;
10879 }
10880
10881 /* Save game in old style and close the file */
10882 int
10883 SaveGameOldStyle(f)
10884      FILE *f;
10885 {
10886     int i, offset;
10887     time_t tm;
10888     
10889     tm = time((time_t *) NULL);
10890     
10891     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10892     PrintOpponents(f);
10893     
10894     if (backwardMostMove > 0 || startedFromSetupPosition) {
10895         fprintf(f, "\n[--------------\n");
10896         PrintPosition(f, backwardMostMove);
10897         fprintf(f, "--------------]\n");
10898     } else {
10899         fprintf(f, "\n");
10900     }
10901
10902     i = backwardMostMove;
10903     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10904
10905     while (i < forwardMostMove) {
10906         if (commentList[i] != NULL) {
10907             fprintf(f, "[%s]\n", commentList[i]);
10908         }
10909
10910         if ((i % 2) == 1) {
10911             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10912             i++;
10913         } else {
10914             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10915             i++;
10916             if (commentList[i] != NULL) {
10917                 fprintf(f, "\n");
10918                 continue;
10919             }
10920             if (i >= forwardMostMove) {
10921                 fprintf(f, "\n");
10922                 break;
10923             }
10924             fprintf(f, "%s\n", parseList[i]);
10925             i++;
10926         }
10927     }
10928     
10929     if (commentList[i] != NULL) {
10930         fprintf(f, "[%s]\n", commentList[i]);
10931     }
10932
10933     /* This isn't really the old style, but it's close enough */
10934     if (gameInfo.resultDetails != NULL &&
10935         gameInfo.resultDetails[0] != NULLCHAR) {
10936         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10937                 gameInfo.resultDetails);
10938     } else {
10939         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10940     }
10941
10942     fclose(f);
10943     return TRUE;
10944 }
10945
10946 /* Save the current game to open file f and close the file */
10947 int
10948 SaveGame(f, dummy, dummy2)
10949      FILE *f;
10950      int dummy;
10951      char *dummy2;
10952 {
10953     if (gameMode == EditPosition) EditPositionDone(TRUE);
10954     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10955     if (appData.oldSaveStyle)
10956       return SaveGameOldStyle(f);
10957     else
10958       return SaveGamePGN(f);
10959 }
10960
10961 /* Save the current position to the given file */
10962 int
10963 SavePositionToFile(filename)
10964      char *filename;
10965 {
10966     FILE *f;
10967     char buf[MSG_SIZ];
10968
10969     if (strcmp(filename, "-") == 0) {
10970         return SavePosition(stdout, 0, NULL);
10971     } else {
10972         f = fopen(filename, "a");
10973         if (f == NULL) {
10974             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10975             DisplayError(buf, errno);
10976             return FALSE;
10977         } else {
10978             SavePosition(f, 0, NULL);
10979             return TRUE;
10980         }
10981     }
10982 }
10983
10984 /* Save the current position to the given open file and close the file */
10985 int
10986 SavePosition(f, dummy, dummy2)
10987      FILE *f;
10988      int dummy;
10989      char *dummy2;
10990 {
10991     time_t tm;
10992     char *fen;
10993     
10994     if (gameMode == EditPosition) EditPositionDone(TRUE);
10995     if (appData.oldSaveStyle) {
10996         tm = time((time_t *) NULL);
10997     
10998         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10999         PrintOpponents(f);
11000         fprintf(f, "[--------------\n");
11001         PrintPosition(f, currentMove);
11002         fprintf(f, "--------------]\n");
11003     } else {
11004         fen = PositionToFEN(currentMove, NULL);
11005         fprintf(f, "%s\n", fen);
11006         free(fen);
11007     }
11008     fclose(f);
11009     return TRUE;
11010 }
11011
11012 void
11013 ReloadCmailMsgEvent(unregister)
11014      int unregister;
11015 {
11016 #if !WIN32
11017     static char *inFilename = NULL;
11018     static char *outFilename;
11019     int i;
11020     struct stat inbuf, outbuf;
11021     int status;
11022     
11023     /* Any registered moves are unregistered if unregister is set, */
11024     /* i.e. invoked by the signal handler */
11025     if (unregister) {
11026         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11027             cmailMoveRegistered[i] = FALSE;
11028             if (cmailCommentList[i] != NULL) {
11029                 free(cmailCommentList[i]);
11030                 cmailCommentList[i] = NULL;
11031             }
11032         }
11033         nCmailMovesRegistered = 0;
11034     }
11035
11036     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11037         cmailResult[i] = CMAIL_NOT_RESULT;
11038     }
11039     nCmailResults = 0;
11040
11041     if (inFilename == NULL) {
11042         /* Because the filenames are static they only get malloced once  */
11043         /* and they never get freed                                      */
11044         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11045         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11046
11047         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11048         sprintf(outFilename, "%s.out", appData.cmailGameName);
11049     }
11050     
11051     status = stat(outFilename, &outbuf);
11052     if (status < 0) {
11053         cmailMailedMove = FALSE;
11054     } else {
11055         status = stat(inFilename, &inbuf);
11056         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11057     }
11058     
11059     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11060        counts the games, notes how each one terminated, etc.
11061        
11062        It would be nice to remove this kludge and instead gather all
11063        the information while building the game list.  (And to keep it
11064        in the game list nodes instead of having a bunch of fixed-size
11065        parallel arrays.)  Note this will require getting each game's
11066        termination from the PGN tags, as the game list builder does
11067        not process the game moves.  --mann
11068        */
11069     cmailMsgLoaded = TRUE;
11070     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11071     
11072     /* Load first game in the file or popup game menu */
11073     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11074
11075 #endif /* !WIN32 */
11076     return;
11077 }
11078
11079 int
11080 RegisterMove()
11081 {
11082     FILE *f;
11083     char string[MSG_SIZ];
11084
11085     if (   cmailMailedMove
11086         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11087         return TRUE;            /* Allow free viewing  */
11088     }
11089
11090     /* Unregister move to ensure that we don't leave RegisterMove        */
11091     /* with the move registered when the conditions for registering no   */
11092     /* longer hold                                                       */
11093     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11094         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11095         nCmailMovesRegistered --;
11096
11097         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11098           {
11099               free(cmailCommentList[lastLoadGameNumber - 1]);
11100               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11101           }
11102     }
11103
11104     if (cmailOldMove == -1) {
11105         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11106         return FALSE;
11107     }
11108
11109     if (currentMove > cmailOldMove + 1) {
11110         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11111         return FALSE;
11112     }
11113
11114     if (currentMove < cmailOldMove) {
11115         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11116         return FALSE;
11117     }
11118
11119     if (forwardMostMove > currentMove) {
11120         /* Silently truncate extra moves */
11121         TruncateGame();
11122     }
11123
11124     if (   (currentMove == cmailOldMove + 1)
11125         || (   (currentMove == cmailOldMove)
11126             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11127                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11128         if (gameInfo.result != GameUnfinished) {
11129             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11130         }
11131
11132         if (commentList[currentMove] != NULL) {
11133             cmailCommentList[lastLoadGameNumber - 1]
11134               = StrSave(commentList[currentMove]);
11135         }
11136         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11137
11138         if (appData.debugMode)
11139           fprintf(debugFP, "Saving %s for game %d\n",
11140                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11141
11142         sprintf(string,
11143                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11144         
11145         f = fopen(string, "w");
11146         if (appData.oldSaveStyle) {
11147             SaveGameOldStyle(f); /* also closes the file */
11148             
11149             sprintf(string, "%s.pos.out", appData.cmailGameName);
11150             f = fopen(string, "w");
11151             SavePosition(f, 0, NULL); /* also closes the file */
11152         } else {
11153             fprintf(f, "{--------------\n");
11154             PrintPosition(f, currentMove);
11155             fprintf(f, "--------------}\n\n");
11156             
11157             SaveGame(f, 0, NULL); /* also closes the file*/
11158         }
11159         
11160         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11161         nCmailMovesRegistered ++;
11162     } else if (nCmailGames == 1) {
11163         DisplayError(_("You have not made a move yet"), 0);
11164         return FALSE;
11165     }
11166
11167     return TRUE;
11168 }
11169
11170 void
11171 MailMoveEvent()
11172 {
11173 #if !WIN32
11174     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11175     FILE *commandOutput;
11176     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11177     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11178     int nBuffers;
11179     int i;
11180     int archived;
11181     char *arcDir;
11182
11183     if (! cmailMsgLoaded) {
11184         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11185         return;
11186     }
11187
11188     if (nCmailGames == nCmailResults) {
11189         DisplayError(_("No unfinished games"), 0);
11190         return;
11191     }
11192
11193 #if CMAIL_PROHIBIT_REMAIL
11194     if (cmailMailedMove) {
11195         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);
11196         DisplayError(msg, 0);
11197         return;
11198     }
11199 #endif
11200
11201     if (! (cmailMailedMove || RegisterMove())) return;
11202     
11203     if (   cmailMailedMove
11204         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11205         sprintf(string, partCommandString,
11206                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11207         commandOutput = popen(string, "r");
11208
11209         if (commandOutput == NULL) {
11210             DisplayError(_("Failed to invoke cmail"), 0);
11211         } else {
11212             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11213                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11214             }
11215             if (nBuffers > 1) {
11216                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11217                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11218                 nBytes = MSG_SIZ - 1;
11219             } else {
11220                 (void) memcpy(msg, buffer, nBytes);
11221             }
11222             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11223
11224             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11225                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11226
11227                 archived = TRUE;
11228                 for (i = 0; i < nCmailGames; i ++) {
11229                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11230                         archived = FALSE;
11231                     }
11232                 }
11233                 if (   archived
11234                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11235                         != NULL)) {
11236                     sprintf(buffer, "%s/%s.%s.archive",
11237                             arcDir,
11238                             appData.cmailGameName,
11239                             gameInfo.date);
11240                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11241                     cmailMsgLoaded = FALSE;
11242                 }
11243             }
11244
11245             DisplayInformation(msg);
11246             pclose(commandOutput);
11247         }
11248     } else {
11249         if ((*cmailMsg) != '\0') {
11250             DisplayInformation(cmailMsg);
11251         }
11252     }
11253
11254     return;
11255 #endif /* !WIN32 */
11256 }
11257
11258 char *
11259 CmailMsg()
11260 {
11261 #if WIN32
11262     return NULL;
11263 #else
11264     int  prependComma = 0;
11265     char number[5];
11266     char string[MSG_SIZ];       /* Space for game-list */
11267     int  i;
11268     
11269     if (!cmailMsgLoaded) return "";
11270
11271     if (cmailMailedMove) {
11272         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11273     } else {
11274         /* Create a list of games left */
11275         sprintf(string, "[");
11276         for (i = 0; i < nCmailGames; i ++) {
11277             if (! (   cmailMoveRegistered[i]
11278                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11279                 if (prependComma) {
11280                     sprintf(number, ",%d", i + 1);
11281                 } else {
11282                     sprintf(number, "%d", i + 1);
11283                     prependComma = 1;
11284                 }
11285                 
11286                 strcat(string, number);
11287             }
11288         }
11289         strcat(string, "]");
11290
11291         if (nCmailMovesRegistered + nCmailResults == 0) {
11292             switch (nCmailGames) {
11293               case 1:
11294                 sprintf(cmailMsg,
11295                         _("Still need to make move for game\n"));
11296                 break;
11297                 
11298               case 2:
11299                 sprintf(cmailMsg,
11300                         _("Still need to make moves for both games\n"));
11301                 break;
11302                 
11303               default:
11304                 sprintf(cmailMsg,
11305                         _("Still need to make moves for all %d games\n"),
11306                         nCmailGames);
11307                 break;
11308             }
11309         } else {
11310             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11311               case 1:
11312                 sprintf(cmailMsg,
11313                         _("Still need to make a move for game %s\n"),
11314                         string);
11315                 break;
11316                 
11317               case 0:
11318                 if (nCmailResults == nCmailGames) {
11319                     sprintf(cmailMsg, _("No unfinished games\n"));
11320                 } else {
11321                     sprintf(cmailMsg, _("Ready to send mail\n"));
11322                 }
11323                 break;
11324                 
11325               default:
11326                 sprintf(cmailMsg,
11327                         _("Still need to make moves for games %s\n"),
11328                         string);
11329             }
11330         }
11331     }
11332     return cmailMsg;
11333 #endif /* WIN32 */
11334 }
11335
11336 void
11337 ResetGameEvent()
11338 {
11339     if (gameMode == Training)
11340       SetTrainingModeOff();
11341
11342     Reset(TRUE, TRUE);
11343     cmailMsgLoaded = FALSE;
11344     if (appData.icsActive) {
11345       SendToICS(ics_prefix);
11346       SendToICS("refresh\n");
11347     }
11348 }
11349
11350 void
11351 ExitEvent(status)
11352      int status;
11353 {
11354     exiting++;
11355     if (exiting > 2) {
11356       /* Give up on clean exit */
11357       exit(status);
11358     }
11359     if (exiting > 1) {
11360       /* Keep trying for clean exit */
11361       return;
11362     }
11363
11364     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11365
11366     if (telnetISR != NULL) {
11367       RemoveInputSource(telnetISR);
11368     }
11369     if (icsPR != NoProc) {
11370       DestroyChildProcess(icsPR, TRUE);
11371     }
11372
11373     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11374     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11375
11376     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11377     /* make sure this other one finishes before killing it!                  */
11378     if(endingGame) { int count = 0;
11379         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11380         while(endingGame && count++ < 10) DoSleep(1);
11381         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11382     }
11383
11384     /* Kill off chess programs */
11385     if (first.pr != NoProc) {
11386         ExitAnalyzeMode();
11387         
11388         DoSleep( appData.delayBeforeQuit );
11389         SendToProgram("quit\n", &first);
11390         DoSleep( appData.delayAfterQuit );
11391         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11392     }
11393     if (second.pr != NoProc) {
11394         DoSleep( appData.delayBeforeQuit );
11395         SendToProgram("quit\n", &second);
11396         DoSleep( appData.delayAfterQuit );
11397         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11398     }
11399     if (first.isr != NULL) {
11400         RemoveInputSource(first.isr);
11401     }
11402     if (second.isr != NULL) {
11403         RemoveInputSource(second.isr);
11404     }
11405
11406     ShutDownFrontEnd();
11407     exit(status);
11408 }
11409
11410 void
11411 PauseEvent()
11412 {
11413     if (appData.debugMode)
11414         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11415     if (pausing) {
11416         pausing = FALSE;
11417         ModeHighlight();
11418         if (gameMode == MachinePlaysWhite ||
11419             gameMode == MachinePlaysBlack) {
11420             StartClocks();
11421         } else {
11422             DisplayBothClocks();
11423         }
11424         if (gameMode == PlayFromGameFile) {
11425             if (appData.timeDelay >= 0) 
11426                 AutoPlayGameLoop();
11427         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11428             Reset(FALSE, TRUE);
11429             SendToICS(ics_prefix);
11430             SendToICS("refresh\n");
11431         } else if (currentMove < forwardMostMove) {
11432             ForwardInner(forwardMostMove);
11433         }
11434         pauseExamInvalid = FALSE;
11435     } else {
11436         switch (gameMode) {
11437           default:
11438             return;
11439           case IcsExamining:
11440             pauseExamForwardMostMove = forwardMostMove;
11441             pauseExamInvalid = FALSE;
11442             /* fall through */
11443           case IcsObserving:
11444           case IcsPlayingWhite:
11445           case IcsPlayingBlack:
11446             pausing = TRUE;
11447             ModeHighlight();
11448             return;
11449           case PlayFromGameFile:
11450             (void) StopLoadGameTimer();
11451             pausing = TRUE;
11452             ModeHighlight();
11453             break;
11454           case BeginningOfGame:
11455             if (appData.icsActive) return;
11456             /* else fall through */
11457           case MachinePlaysWhite:
11458           case MachinePlaysBlack:
11459           case TwoMachinesPlay:
11460             if (forwardMostMove == 0)
11461               return;           /* don't pause if no one has moved */
11462             if ((gameMode == MachinePlaysWhite &&
11463                  !WhiteOnMove(forwardMostMove)) ||
11464                 (gameMode == MachinePlaysBlack &&
11465                  WhiteOnMove(forwardMostMove))) {
11466                 StopClocks();
11467             }
11468             pausing = TRUE;
11469             ModeHighlight();
11470             break;
11471         }
11472     }
11473 }
11474
11475 void
11476 EditCommentEvent()
11477 {
11478     char title[MSG_SIZ];
11479
11480     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11481         strcpy(title, _("Edit comment"));
11482     } else {
11483         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11484                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11485                 parseList[currentMove - 1]);
11486     }
11487
11488     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11489 }
11490
11491
11492 void
11493 EditTagsEvent()
11494 {
11495     char *tags = PGNTags(&gameInfo);
11496     EditTagsPopUp(tags);
11497     free(tags);
11498 }
11499
11500 void
11501 AnalyzeModeEvent()
11502 {
11503     if (appData.noChessProgram || gameMode == AnalyzeMode)
11504       return;
11505
11506     if (gameMode != AnalyzeFile) {
11507         if (!appData.icsEngineAnalyze) {
11508                EditGameEvent();
11509                if (gameMode != EditGame) return;
11510         }
11511         ResurrectChessProgram();
11512         SendToProgram("analyze\n", &first);
11513         first.analyzing = TRUE;
11514         /*first.maybeThinking = TRUE;*/
11515         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11516         EngineOutputPopUp();
11517     }
11518     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11519     pausing = FALSE;
11520     ModeHighlight();
11521     SetGameInfo();
11522
11523     StartAnalysisClock();
11524     GetTimeMark(&lastNodeCountTime);
11525     lastNodeCount = 0;
11526 }
11527
11528 void
11529 AnalyzeFileEvent()
11530 {
11531     if (appData.noChessProgram || gameMode == AnalyzeFile)
11532       return;
11533
11534     if (gameMode != AnalyzeMode) {
11535         EditGameEvent();
11536         if (gameMode != EditGame) return;
11537         ResurrectChessProgram();
11538         SendToProgram("analyze\n", &first);
11539         first.analyzing = TRUE;
11540         /*first.maybeThinking = TRUE;*/
11541         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11542         EngineOutputPopUp();
11543     }
11544     gameMode = AnalyzeFile;
11545     pausing = FALSE;
11546     ModeHighlight();
11547     SetGameInfo();
11548
11549     StartAnalysisClock();
11550     GetTimeMark(&lastNodeCountTime);
11551     lastNodeCount = 0;
11552 }
11553
11554 void
11555 MachineWhiteEvent()
11556 {
11557     char buf[MSG_SIZ];
11558     char *bookHit = NULL;
11559
11560     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11561       return;
11562
11563
11564     if (gameMode == PlayFromGameFile || 
11565         gameMode == TwoMachinesPlay  || 
11566         gameMode == Training         || 
11567         gameMode == AnalyzeMode      || 
11568         gameMode == EndOfGame)
11569         EditGameEvent();
11570
11571     if (gameMode == EditPosition) 
11572         EditPositionDone(TRUE);
11573
11574     if (!WhiteOnMove(currentMove)) {
11575         DisplayError(_("It is not White's turn"), 0);
11576         return;
11577     }
11578   
11579     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11580       ExitAnalyzeMode();
11581
11582     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11583         gameMode == AnalyzeFile)
11584         TruncateGame();
11585
11586     ResurrectChessProgram();    /* in case it isn't running */
11587     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11588         gameMode = MachinePlaysWhite;
11589         ResetClocks();
11590     } else
11591     gameMode = MachinePlaysWhite;
11592     pausing = FALSE;
11593     ModeHighlight();
11594     SetGameInfo();
11595     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11596     DisplayTitle(buf);
11597     if (first.sendName) {
11598       sprintf(buf, "name %s\n", gameInfo.black);
11599       SendToProgram(buf, &first);
11600     }
11601     if (first.sendTime) {
11602       if (first.useColors) {
11603         SendToProgram("black\n", &first); /*gnu kludge*/
11604       }
11605       SendTimeRemaining(&first, TRUE);
11606     }
11607     if (first.useColors) {
11608       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11609     }
11610     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11611     SetMachineThinkingEnables();
11612     first.maybeThinking = TRUE;
11613     StartClocks();
11614     firstMove = FALSE;
11615
11616     if (appData.autoFlipView && !flipView) {
11617       flipView = !flipView;
11618       DrawPosition(FALSE, NULL);
11619       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11620     }
11621
11622     if(bookHit) { // [HGM] book: simulate book reply
11623         static char bookMove[MSG_SIZ]; // a bit generous?
11624
11625         programStats.nodes = programStats.depth = programStats.time = 
11626         programStats.score = programStats.got_only_move = 0;
11627         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11628
11629         strcpy(bookMove, "move ");
11630         strcat(bookMove, bookHit);
11631         HandleMachineMove(bookMove, &first);
11632     }
11633 }
11634
11635 void
11636 MachineBlackEvent()
11637 {
11638     char buf[MSG_SIZ];
11639    char *bookHit = NULL;
11640
11641     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11642         return;
11643
11644
11645     if (gameMode == PlayFromGameFile || 
11646         gameMode == TwoMachinesPlay  || 
11647         gameMode == Training         || 
11648         gameMode == AnalyzeMode      || 
11649         gameMode == EndOfGame)
11650         EditGameEvent();
11651
11652     if (gameMode == EditPosition) 
11653         EditPositionDone(TRUE);
11654
11655     if (WhiteOnMove(currentMove)) {
11656         DisplayError(_("It is not Black's turn"), 0);
11657         return;
11658     }
11659     
11660     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11661       ExitAnalyzeMode();
11662
11663     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11664         gameMode == AnalyzeFile)
11665         TruncateGame();
11666
11667     ResurrectChessProgram();    /* in case it isn't running */
11668     gameMode = MachinePlaysBlack;
11669     pausing = FALSE;
11670     ModeHighlight();
11671     SetGameInfo();
11672     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11673     DisplayTitle(buf);
11674     if (first.sendName) {
11675       sprintf(buf, "name %s\n", gameInfo.white);
11676       SendToProgram(buf, &first);
11677     }
11678     if (first.sendTime) {
11679       if (first.useColors) {
11680         SendToProgram("white\n", &first); /*gnu kludge*/
11681       }
11682       SendTimeRemaining(&first, FALSE);
11683     }
11684     if (first.useColors) {
11685       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11686     }
11687     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11688     SetMachineThinkingEnables();
11689     first.maybeThinking = TRUE;
11690     StartClocks();
11691
11692     if (appData.autoFlipView && flipView) {
11693       flipView = !flipView;
11694       DrawPosition(FALSE, NULL);
11695       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11696     }
11697     if(bookHit) { // [HGM] book: simulate book reply
11698         static char bookMove[MSG_SIZ]; // a bit generous?
11699
11700         programStats.nodes = programStats.depth = programStats.time = 
11701         programStats.score = programStats.got_only_move = 0;
11702         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11703
11704         strcpy(bookMove, "move ");
11705         strcat(bookMove, bookHit);
11706         HandleMachineMove(bookMove, &first);
11707     }
11708 }
11709
11710
11711 void
11712 DisplayTwoMachinesTitle()
11713 {
11714     char buf[MSG_SIZ];
11715     if (appData.matchGames > 0) {
11716         if (first.twoMachinesColor[0] == 'w') {
11717             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11718                     gameInfo.white, gameInfo.black,
11719                     first.matchWins, second.matchWins,
11720                     matchGame - 1 - (first.matchWins + second.matchWins));
11721         } else {
11722             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11723                     gameInfo.white, gameInfo.black,
11724                     second.matchWins, first.matchWins,
11725                     matchGame - 1 - (first.matchWins + second.matchWins));
11726         }
11727     } else {
11728         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11729     }
11730     DisplayTitle(buf);
11731 }
11732
11733 void
11734 TwoMachinesEvent P((void))
11735 {
11736     int i;
11737     char buf[MSG_SIZ];
11738     ChessProgramState *onmove;
11739     char *bookHit = NULL;
11740     
11741     if (appData.noChessProgram) return;
11742
11743     switch (gameMode) {
11744       case TwoMachinesPlay:
11745         return;
11746       case MachinePlaysWhite:
11747       case MachinePlaysBlack:
11748         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11749             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11750             return;
11751         }
11752         /* fall through */
11753       case BeginningOfGame:
11754       case PlayFromGameFile:
11755       case EndOfGame:
11756         EditGameEvent();
11757         if (gameMode != EditGame) return;
11758         break;
11759       case EditPosition:
11760         EditPositionDone(TRUE);
11761         break;
11762       case AnalyzeMode:
11763       case AnalyzeFile:
11764         ExitAnalyzeMode();
11765         break;
11766       case EditGame:
11767       default:
11768         break;
11769     }
11770
11771 //    forwardMostMove = currentMove;
11772     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11773     ResurrectChessProgram();    /* in case first program isn't running */
11774
11775     if (second.pr == NULL) {
11776         StartChessProgram(&second);
11777         if (second.protocolVersion == 1) {
11778           TwoMachinesEventIfReady();
11779         } else {
11780           /* kludge: allow timeout for initial "feature" command */
11781           FreezeUI();
11782           DisplayMessage("", _("Starting second chess program"));
11783           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11784         }
11785         return;
11786     }
11787     DisplayMessage("", "");
11788     InitChessProgram(&second, FALSE);
11789     SendToProgram("force\n", &second);
11790     if (startedFromSetupPosition) {
11791         SendBoard(&second, backwardMostMove);
11792     if (appData.debugMode) {
11793         fprintf(debugFP, "Two Machines\n");
11794     }
11795     }
11796     for (i = backwardMostMove; i < forwardMostMove; i++) {
11797         SendMoveToProgram(i, &second);
11798     }
11799
11800     gameMode = TwoMachinesPlay;
11801     pausing = FALSE;
11802     ModeHighlight();
11803     SetGameInfo();
11804     DisplayTwoMachinesTitle();
11805     firstMove = TRUE;
11806     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11807         onmove = &first;
11808     } else {
11809         onmove = &second;
11810     }
11811
11812     SendToProgram(first.computerString, &first);
11813     if (first.sendName) {
11814       sprintf(buf, "name %s\n", second.tidy);
11815       SendToProgram(buf, &first);
11816     }
11817     SendToProgram(second.computerString, &second);
11818     if (second.sendName) {
11819       sprintf(buf, "name %s\n", first.tidy);
11820       SendToProgram(buf, &second);
11821     }
11822
11823     ResetClocks();
11824     if (!first.sendTime || !second.sendTime) {
11825         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11826         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11827     }
11828     if (onmove->sendTime) {
11829       if (onmove->useColors) {
11830         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11831       }
11832       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11833     }
11834     if (onmove->useColors) {
11835       SendToProgram(onmove->twoMachinesColor, onmove);
11836     }
11837     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11838 //    SendToProgram("go\n", onmove);
11839     onmove->maybeThinking = TRUE;
11840     SetMachineThinkingEnables();
11841
11842     StartClocks();
11843
11844     if(bookHit) { // [HGM] book: simulate book reply
11845         static char bookMove[MSG_SIZ]; // a bit generous?
11846
11847         programStats.nodes = programStats.depth = programStats.time = 
11848         programStats.score = programStats.got_only_move = 0;
11849         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11850
11851         strcpy(bookMove, "move ");
11852         strcat(bookMove, bookHit);
11853         savedMessage = bookMove; // args for deferred call
11854         savedState = onmove;
11855         ScheduleDelayedEvent(DeferredBookMove, 1);
11856     }
11857 }
11858
11859 void
11860 TrainingEvent()
11861 {
11862     if (gameMode == Training) {
11863       SetTrainingModeOff();
11864       gameMode = PlayFromGameFile;
11865       DisplayMessage("", _("Training mode off"));
11866     } else {
11867       gameMode = Training;
11868       animateTraining = appData.animate;
11869
11870       /* make sure we are not already at the end of the game */
11871       if (currentMove < forwardMostMove) {
11872         SetTrainingModeOn();
11873         DisplayMessage("", _("Training mode on"));
11874       } else {
11875         gameMode = PlayFromGameFile;
11876         DisplayError(_("Already at end of game"), 0);
11877       }
11878     }
11879     ModeHighlight();
11880 }
11881
11882 void
11883 IcsClientEvent()
11884 {
11885     if (!appData.icsActive) return;
11886     switch (gameMode) {
11887       case IcsPlayingWhite:
11888       case IcsPlayingBlack:
11889       case IcsObserving:
11890       case IcsIdle:
11891       case BeginningOfGame:
11892       case IcsExamining:
11893         return;
11894
11895       case EditGame:
11896         break;
11897
11898       case EditPosition:
11899         EditPositionDone(TRUE);
11900         break;
11901
11902       case AnalyzeMode:
11903       case AnalyzeFile:
11904         ExitAnalyzeMode();
11905         break;
11906         
11907       default:
11908         EditGameEvent();
11909         break;
11910     }
11911
11912     gameMode = IcsIdle;
11913     ModeHighlight();
11914     return;
11915 }
11916
11917
11918 void
11919 EditGameEvent()
11920 {
11921     int i;
11922
11923     switch (gameMode) {
11924       case Training:
11925         SetTrainingModeOff();
11926         break;
11927       case MachinePlaysWhite:
11928       case MachinePlaysBlack:
11929       case BeginningOfGame:
11930         SendToProgram("force\n", &first);
11931         SetUserThinkingEnables();
11932         break;
11933       case PlayFromGameFile:
11934         (void) StopLoadGameTimer();
11935         if (gameFileFP != NULL) {
11936             gameFileFP = NULL;
11937         }
11938         break;
11939       case EditPosition:
11940         EditPositionDone(TRUE);
11941         break;
11942       case AnalyzeMode:
11943       case AnalyzeFile:
11944         ExitAnalyzeMode();
11945         SendToProgram("force\n", &first);
11946         break;
11947       case TwoMachinesPlay:
11948         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11949         ResurrectChessProgram();
11950         SetUserThinkingEnables();
11951         break;
11952       case EndOfGame:
11953         ResurrectChessProgram();
11954         break;
11955       case IcsPlayingBlack:
11956       case IcsPlayingWhite:
11957         DisplayError(_("Warning: You are still playing a game"), 0);
11958         break;
11959       case IcsObserving:
11960         DisplayError(_("Warning: You are still observing a game"), 0);
11961         break;
11962       case IcsExamining:
11963         DisplayError(_("Warning: You are still examining a game"), 0);
11964         break;
11965       case IcsIdle:
11966         break;
11967       case EditGame:
11968       default:
11969         return;
11970     }
11971     
11972     pausing = FALSE;
11973     StopClocks();
11974     first.offeredDraw = second.offeredDraw = 0;
11975
11976     if (gameMode == PlayFromGameFile) {
11977         whiteTimeRemaining = timeRemaining[0][currentMove];
11978         blackTimeRemaining = timeRemaining[1][currentMove];
11979         DisplayTitle("");
11980     }
11981
11982     if (gameMode == MachinePlaysWhite ||
11983         gameMode == MachinePlaysBlack ||
11984         gameMode == TwoMachinesPlay ||
11985         gameMode == EndOfGame) {
11986         i = forwardMostMove;
11987         while (i > currentMove) {
11988             SendToProgram("undo\n", &first);
11989             i--;
11990         }
11991         whiteTimeRemaining = timeRemaining[0][currentMove];
11992         blackTimeRemaining = timeRemaining[1][currentMove];
11993         DisplayBothClocks();
11994         if (whiteFlag || blackFlag) {
11995             whiteFlag = blackFlag = 0;
11996         }
11997         DisplayTitle("");
11998     }           
11999     
12000     gameMode = EditGame;
12001     ModeHighlight();
12002     SetGameInfo();
12003 }
12004
12005
12006 void
12007 EditPositionEvent()
12008 {
12009     if (gameMode == EditPosition) {
12010         EditGameEvent();
12011         return;
12012     }
12013     
12014     EditGameEvent();
12015     if (gameMode != EditGame) return;
12016     
12017     gameMode = EditPosition;
12018     ModeHighlight();
12019     SetGameInfo();
12020     if (currentMove > 0)
12021       CopyBoard(boards[0], boards[currentMove]);
12022     
12023     blackPlaysFirst = !WhiteOnMove(currentMove);
12024     ResetClocks();
12025     currentMove = forwardMostMove = backwardMostMove = 0;
12026     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12027     DisplayMove(-1);
12028 }
12029
12030 void
12031 ExitAnalyzeMode()
12032 {
12033     /* [DM] icsEngineAnalyze - possible call from other functions */
12034     if (appData.icsEngineAnalyze) {
12035         appData.icsEngineAnalyze = FALSE;
12036
12037         DisplayMessage("",_("Close ICS engine analyze..."));
12038     }
12039     if (first.analysisSupport && first.analyzing) {
12040       SendToProgram("exit\n", &first);
12041       first.analyzing = FALSE;
12042     }
12043     thinkOutput[0] = NULLCHAR;
12044 }
12045
12046 void
12047 EditPositionDone(Boolean fakeRights)
12048 {
12049     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12050
12051     startedFromSetupPosition = TRUE;
12052     InitChessProgram(&first, FALSE);
12053     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12054       boards[0][EP_STATUS] = EP_NONE;
12055       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12056     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12057         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12058         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12059       } else boards[0][CASTLING][2] = NoRights;
12060     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12061         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12062         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12063       } else boards[0][CASTLING][5] = NoRights;
12064     }
12065     SendToProgram("force\n", &first);
12066     if (blackPlaysFirst) {
12067         strcpy(moveList[0], "");
12068         strcpy(parseList[0], "");
12069         currentMove = forwardMostMove = backwardMostMove = 1;
12070         CopyBoard(boards[1], boards[0]);
12071     } else {
12072         currentMove = forwardMostMove = backwardMostMove = 0;
12073     }
12074     SendBoard(&first, forwardMostMove);
12075     if (appData.debugMode) {
12076         fprintf(debugFP, "EditPosDone\n");
12077     }
12078     DisplayTitle("");
12079     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12080     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12081     gameMode = EditGame;
12082     ModeHighlight();
12083     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12084     ClearHighlights(); /* [AS] */
12085 }
12086
12087 /* Pause for `ms' milliseconds */
12088 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12089 void
12090 TimeDelay(ms)
12091      long ms;
12092 {
12093     TimeMark m1, m2;
12094
12095     GetTimeMark(&m1);
12096     do {
12097         GetTimeMark(&m2);
12098     } while (SubtractTimeMarks(&m2, &m1) < ms);
12099 }
12100
12101 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12102 void
12103 SendMultiLineToICS(buf)
12104      char *buf;
12105 {
12106     char temp[MSG_SIZ+1], *p;
12107     int len;
12108
12109     len = strlen(buf);
12110     if (len > MSG_SIZ)
12111       len = MSG_SIZ;
12112   
12113     strncpy(temp, buf, len);
12114     temp[len] = 0;
12115
12116     p = temp;
12117     while (*p) {
12118         if (*p == '\n' || *p == '\r')
12119           *p = ' ';
12120         ++p;
12121     }
12122
12123     strcat(temp, "\n");
12124     SendToICS(temp);
12125     SendToPlayer(temp, strlen(temp));
12126 }
12127
12128 void
12129 SetWhiteToPlayEvent()
12130 {
12131     if (gameMode == EditPosition) {
12132         blackPlaysFirst = FALSE;
12133         DisplayBothClocks();    /* works because currentMove is 0 */
12134     } else if (gameMode == IcsExamining) {
12135         SendToICS(ics_prefix);
12136         SendToICS("tomove white\n");
12137     }
12138 }
12139
12140 void
12141 SetBlackToPlayEvent()
12142 {
12143     if (gameMode == EditPosition) {
12144         blackPlaysFirst = TRUE;
12145         currentMove = 1;        /* kludge */
12146         DisplayBothClocks();
12147         currentMove = 0;
12148     } else if (gameMode == IcsExamining) {
12149         SendToICS(ics_prefix);
12150         SendToICS("tomove black\n");
12151     }
12152 }
12153
12154 void
12155 EditPositionMenuEvent(selection, x, y)
12156      ChessSquare selection;
12157      int x, y;
12158 {
12159     char buf[MSG_SIZ];
12160     ChessSquare piece = boards[0][y][x];
12161
12162     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12163
12164     switch (selection) {
12165       case ClearBoard:
12166         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12167             SendToICS(ics_prefix);
12168             SendToICS("bsetup clear\n");
12169         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12170             SendToICS(ics_prefix);
12171             SendToICS("clearboard\n");
12172         } else {
12173             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12174                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12175                 for (y = 0; y < BOARD_HEIGHT; y++) {
12176                     if (gameMode == IcsExamining) {
12177                         if (boards[currentMove][y][x] != EmptySquare) {
12178                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12179                                     AAA + x, ONE + y);
12180                             SendToICS(buf);
12181                         }
12182                     } else {
12183                         boards[0][y][x] = p;
12184                     }
12185                 }
12186             }
12187         }
12188         if (gameMode == EditPosition) {
12189             DrawPosition(FALSE, boards[0]);
12190         }
12191         break;
12192
12193       case WhitePlay:
12194         SetWhiteToPlayEvent();
12195         break;
12196
12197       case BlackPlay:
12198         SetBlackToPlayEvent();
12199         break;
12200
12201       case EmptySquare:
12202         if (gameMode == IcsExamining) {
12203             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12204             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12205             SendToICS(buf);
12206         } else {
12207             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12208                 if(x == BOARD_LEFT-2) {
12209                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12210                     boards[0][y][1] = 0;
12211                 } else
12212                 if(x == BOARD_RGHT+1) {
12213                     if(y >= gameInfo.holdingsSize) break;
12214                     boards[0][y][BOARD_WIDTH-2] = 0;
12215                 } else break;
12216             }
12217             boards[0][y][x] = EmptySquare;
12218             DrawPosition(FALSE, boards[0]);
12219         }
12220         break;
12221
12222       case PromotePiece:
12223         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12224            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12225             selection = (ChessSquare) (PROMOTED piece);
12226         } else if(piece == EmptySquare) selection = WhiteSilver;
12227         else selection = (ChessSquare)((int)piece - 1);
12228         goto defaultlabel;
12229
12230       case DemotePiece:
12231         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12232            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12233             selection = (ChessSquare) (DEMOTED piece);
12234         } else if(piece == EmptySquare) selection = BlackSilver;
12235         else selection = (ChessSquare)((int)piece + 1);       
12236         goto defaultlabel;
12237
12238       case WhiteQueen:
12239       case BlackQueen:
12240         if(gameInfo.variant == VariantShatranj ||
12241            gameInfo.variant == VariantXiangqi  ||
12242            gameInfo.variant == VariantCourier  ||
12243            gameInfo.variant == VariantMakruk     )
12244             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12245         goto defaultlabel;
12246
12247       case WhiteKing:
12248       case BlackKing:
12249         if(gameInfo.variant == VariantXiangqi)
12250             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12251         if(gameInfo.variant == VariantKnightmate)
12252             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12253       default:
12254         defaultlabel:
12255         if (gameMode == IcsExamining) {
12256             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12257             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12258                     PieceToChar(selection), AAA + x, ONE + y);
12259             SendToICS(buf);
12260         } else {
12261             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12262                 int n;
12263                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12264                     n = PieceToNumber(selection - BlackPawn);
12265                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12266                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12267                     boards[0][BOARD_HEIGHT-1-n][1]++;
12268                 } else
12269                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12270                     n = PieceToNumber(selection);
12271                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12272                     boards[0][n][BOARD_WIDTH-1] = selection;
12273                     boards[0][n][BOARD_WIDTH-2]++;
12274                 }
12275             } else
12276             boards[0][y][x] = selection;
12277             DrawPosition(TRUE, boards[0]);
12278         }
12279         break;
12280     }
12281 }
12282
12283
12284 void
12285 DropMenuEvent(selection, x, y)
12286      ChessSquare selection;
12287      int x, y;
12288 {
12289     ChessMove moveType;
12290
12291     switch (gameMode) {
12292       case IcsPlayingWhite:
12293       case MachinePlaysBlack:
12294         if (!WhiteOnMove(currentMove)) {
12295             DisplayMoveError(_("It is Black's turn"));
12296             return;
12297         }
12298         moveType = WhiteDrop;
12299         break;
12300       case IcsPlayingBlack:
12301       case MachinePlaysWhite:
12302         if (WhiteOnMove(currentMove)) {
12303             DisplayMoveError(_("It is White's turn"));
12304             return;
12305         }
12306         moveType = BlackDrop;
12307         break;
12308       case EditGame:
12309         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12310         break;
12311       default:
12312         return;
12313     }
12314
12315     if (moveType == BlackDrop && selection < BlackPawn) {
12316       selection = (ChessSquare) ((int) selection
12317                                  + (int) BlackPawn - (int) WhitePawn);
12318     }
12319     if (boards[currentMove][y][x] != EmptySquare) {
12320         DisplayMoveError(_("That square is occupied"));
12321         return;
12322     }
12323
12324     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12325 }
12326
12327 void
12328 AcceptEvent()
12329 {
12330     /* Accept a pending offer of any kind from opponent */
12331     
12332     if (appData.icsActive) {
12333         SendToICS(ics_prefix);
12334         SendToICS("accept\n");
12335     } else if (cmailMsgLoaded) {
12336         if (currentMove == cmailOldMove &&
12337             commentList[cmailOldMove] != NULL &&
12338             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12339                    "Black offers a draw" : "White offers a draw")) {
12340             TruncateGame();
12341             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12342             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12343         } else {
12344             DisplayError(_("There is no pending offer on this move"), 0);
12345             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12346         }
12347     } else {
12348         /* Not used for offers from chess program */
12349     }
12350 }
12351
12352 void
12353 DeclineEvent()
12354 {
12355     /* Decline a pending offer of any kind from opponent */
12356     
12357     if (appData.icsActive) {
12358         SendToICS(ics_prefix);
12359         SendToICS("decline\n");
12360     } else if (cmailMsgLoaded) {
12361         if (currentMove == cmailOldMove &&
12362             commentList[cmailOldMove] != NULL &&
12363             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12364                    "Black offers a draw" : "White offers a draw")) {
12365 #ifdef NOTDEF
12366             AppendComment(cmailOldMove, "Draw declined", TRUE);
12367             DisplayComment(cmailOldMove - 1, "Draw declined");
12368 #endif /*NOTDEF*/
12369         } else {
12370             DisplayError(_("There is no pending offer on this move"), 0);
12371         }
12372     } else {
12373         /* Not used for offers from chess program */
12374     }
12375 }
12376
12377 void
12378 RematchEvent()
12379 {
12380     /* Issue ICS rematch command */
12381     if (appData.icsActive) {
12382         SendToICS(ics_prefix);
12383         SendToICS("rematch\n");
12384     }
12385 }
12386
12387 void
12388 CallFlagEvent()
12389 {
12390     /* Call your opponent's flag (claim a win on time) */
12391     if (appData.icsActive) {
12392         SendToICS(ics_prefix);
12393         SendToICS("flag\n");
12394     } else {
12395         switch (gameMode) {
12396           default:
12397             return;
12398           case MachinePlaysWhite:
12399             if (whiteFlag) {
12400                 if (blackFlag)
12401                   GameEnds(GameIsDrawn, "Both players ran out of time",
12402                            GE_PLAYER);
12403                 else
12404                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12405             } else {
12406                 DisplayError(_("Your opponent is not out of time"), 0);
12407             }
12408             break;
12409           case MachinePlaysBlack:
12410             if (blackFlag) {
12411                 if (whiteFlag)
12412                   GameEnds(GameIsDrawn, "Both players ran out of time",
12413                            GE_PLAYER);
12414                 else
12415                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12416             } else {
12417                 DisplayError(_("Your opponent is not out of time"), 0);
12418             }
12419             break;
12420         }
12421     }
12422 }
12423
12424 void
12425 DrawEvent()
12426 {
12427     /* Offer draw or accept pending draw offer from opponent */
12428     
12429     if (appData.icsActive) {
12430         /* Note: tournament rules require draw offers to be
12431            made after you make your move but before you punch
12432            your clock.  Currently ICS doesn't let you do that;
12433            instead, you immediately punch your clock after making
12434            a move, but you can offer a draw at any time. */
12435         
12436         SendToICS(ics_prefix);
12437         SendToICS("draw\n");
12438         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12439     } else if (cmailMsgLoaded) {
12440         if (currentMove == cmailOldMove &&
12441             commentList[cmailOldMove] != NULL &&
12442             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12443                    "Black offers a draw" : "White offers a draw")) {
12444             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12445             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12446         } else if (currentMove == cmailOldMove + 1) {
12447             char *offer = WhiteOnMove(cmailOldMove) ?
12448               "White offers a draw" : "Black offers a draw";
12449             AppendComment(currentMove, offer, TRUE);
12450             DisplayComment(currentMove - 1, offer);
12451             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12452         } else {
12453             DisplayError(_("You must make your move before offering a draw"), 0);
12454             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12455         }
12456     } else if (first.offeredDraw) {
12457         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12458     } else {
12459         if (first.sendDrawOffers) {
12460             SendToProgram("draw\n", &first);
12461             userOfferedDraw = TRUE;
12462         }
12463     }
12464 }
12465
12466 void
12467 AdjournEvent()
12468 {
12469     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12470     
12471     if (appData.icsActive) {
12472         SendToICS(ics_prefix);
12473         SendToICS("adjourn\n");
12474     } else {
12475         /* Currently GNU Chess doesn't offer or accept Adjourns */
12476     }
12477 }
12478
12479
12480 void
12481 AbortEvent()
12482 {
12483     /* Offer Abort or accept pending Abort offer from opponent */
12484     
12485     if (appData.icsActive) {
12486         SendToICS(ics_prefix);
12487         SendToICS("abort\n");
12488     } else {
12489         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12490     }
12491 }
12492
12493 void
12494 ResignEvent()
12495 {
12496     /* Resign.  You can do this even if it's not your turn. */
12497     
12498     if (appData.icsActive) {
12499         SendToICS(ics_prefix);
12500         SendToICS("resign\n");
12501     } else {
12502         switch (gameMode) {
12503           case MachinePlaysWhite:
12504             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12505             break;
12506           case MachinePlaysBlack:
12507             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12508             break;
12509           case EditGame:
12510             if (cmailMsgLoaded) {
12511                 TruncateGame();
12512                 if (WhiteOnMove(cmailOldMove)) {
12513                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12514                 } else {
12515                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12516                 }
12517                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12518             }
12519             break;
12520           default:
12521             break;
12522         }
12523     }
12524 }
12525
12526
12527 void
12528 StopObservingEvent()
12529 {
12530     /* Stop observing current games */
12531     SendToICS(ics_prefix);
12532     SendToICS("unobserve\n");
12533 }
12534
12535 void
12536 StopExaminingEvent()
12537 {
12538     /* Stop observing current game */
12539     SendToICS(ics_prefix);
12540     SendToICS("unexamine\n");
12541 }
12542
12543 void
12544 ForwardInner(target)
12545      int target;
12546 {
12547     int limit;
12548
12549     if (appData.debugMode)
12550         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12551                 target, currentMove, forwardMostMove);
12552
12553     if (gameMode == EditPosition)
12554       return;
12555
12556     if (gameMode == PlayFromGameFile && !pausing)
12557       PauseEvent();
12558     
12559     if (gameMode == IcsExamining && pausing)
12560       limit = pauseExamForwardMostMove;
12561     else
12562       limit = forwardMostMove;
12563     
12564     if (target > limit) target = limit;
12565
12566     if (target > 0 && moveList[target - 1][0]) {
12567         int fromX, fromY, toX, toY;
12568         toX = moveList[target - 1][2] - AAA;
12569         toY = moveList[target - 1][3] - ONE;
12570         if (moveList[target - 1][1] == '@') {
12571             if (appData.highlightLastMove) {
12572                 SetHighlights(-1, -1, toX, toY);
12573             }
12574         } else {
12575             fromX = moveList[target - 1][0] - AAA;
12576             fromY = moveList[target - 1][1] - ONE;
12577             if (target == currentMove + 1) {
12578                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12579             }
12580             if (appData.highlightLastMove) {
12581                 SetHighlights(fromX, fromY, toX, toY);
12582             }
12583         }
12584     }
12585     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12586         gameMode == Training || gameMode == PlayFromGameFile || 
12587         gameMode == AnalyzeFile) {
12588         while (currentMove < target) {
12589             SendMoveToProgram(currentMove++, &first);
12590         }
12591     } else {
12592         currentMove = target;
12593     }
12594     
12595     if (gameMode == EditGame || gameMode == EndOfGame) {
12596         whiteTimeRemaining = timeRemaining[0][currentMove];
12597         blackTimeRemaining = timeRemaining[1][currentMove];
12598     }
12599     DisplayBothClocks();
12600     DisplayMove(currentMove - 1);
12601     DrawPosition(FALSE, boards[currentMove]);
12602     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12603     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12604         DisplayComment(currentMove - 1, commentList[currentMove]);
12605     }
12606 }
12607
12608
12609 void
12610 ForwardEvent()
12611 {
12612     if (gameMode == IcsExamining && !pausing) {
12613         SendToICS(ics_prefix);
12614         SendToICS("forward\n");
12615     } else {
12616         ForwardInner(currentMove + 1);
12617     }
12618 }
12619
12620 void
12621 ToEndEvent()
12622 {
12623     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12624         /* to optimze, we temporarily turn off analysis mode while we feed
12625          * the remaining moves to the engine. Otherwise we get analysis output
12626          * after each move.
12627          */ 
12628         if (first.analysisSupport) {
12629           SendToProgram("exit\nforce\n", &first);
12630           first.analyzing = FALSE;
12631         }
12632     }
12633         
12634     if (gameMode == IcsExamining && !pausing) {
12635         SendToICS(ics_prefix);
12636         SendToICS("forward 999999\n");
12637     } else {
12638         ForwardInner(forwardMostMove);
12639     }
12640
12641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12642         /* we have fed all the moves, so reactivate analysis mode */
12643         SendToProgram("analyze\n", &first);
12644         first.analyzing = TRUE;
12645         /*first.maybeThinking = TRUE;*/
12646         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12647     }
12648 }
12649
12650 void
12651 BackwardInner(target)
12652      int target;
12653 {
12654     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12655
12656     if (appData.debugMode)
12657         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12658                 target, currentMove, forwardMostMove);
12659
12660     if (gameMode == EditPosition) return;
12661     if (currentMove <= backwardMostMove) {
12662         ClearHighlights();
12663         DrawPosition(full_redraw, boards[currentMove]);
12664         return;
12665     }
12666     if (gameMode == PlayFromGameFile && !pausing)
12667       PauseEvent();
12668     
12669     if (moveList[target][0]) {
12670         int fromX, fromY, toX, toY;
12671         toX = moveList[target][2] - AAA;
12672         toY = moveList[target][3] - ONE;
12673         if (moveList[target][1] == '@') {
12674             if (appData.highlightLastMove) {
12675                 SetHighlights(-1, -1, toX, toY);
12676             }
12677         } else {
12678             fromX = moveList[target][0] - AAA;
12679             fromY = moveList[target][1] - ONE;
12680             if (target == currentMove - 1) {
12681                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12682             }
12683             if (appData.highlightLastMove) {
12684                 SetHighlights(fromX, fromY, toX, toY);
12685             }
12686         }
12687     }
12688     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12689         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12690         while (currentMove > target) {
12691             SendToProgram("undo\n", &first);
12692             currentMove--;
12693         }
12694     } else {
12695         currentMove = target;
12696     }
12697     
12698     if (gameMode == EditGame || gameMode == EndOfGame) {
12699         whiteTimeRemaining = timeRemaining[0][currentMove];
12700         blackTimeRemaining = timeRemaining[1][currentMove];
12701     }
12702     DisplayBothClocks();
12703     DisplayMove(currentMove - 1);
12704     DrawPosition(full_redraw, boards[currentMove]);
12705     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12706     // [HGM] PV info: routine tests if comment empty
12707     DisplayComment(currentMove - 1, commentList[currentMove]);
12708 }
12709
12710 void
12711 BackwardEvent()
12712 {
12713     if (gameMode == IcsExamining && !pausing) {
12714         SendToICS(ics_prefix);
12715         SendToICS("backward\n");
12716     } else {
12717         BackwardInner(currentMove - 1);
12718     }
12719 }
12720
12721 void
12722 ToStartEvent()
12723 {
12724     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12725         /* to optimize, we temporarily turn off analysis mode while we undo
12726          * all the moves. Otherwise we get analysis output after each undo.
12727          */ 
12728         if (first.analysisSupport) {
12729           SendToProgram("exit\nforce\n", &first);
12730           first.analyzing = FALSE;
12731         }
12732     }
12733
12734     if (gameMode == IcsExamining && !pausing) {
12735         SendToICS(ics_prefix);
12736         SendToICS("backward 999999\n");
12737     } else {
12738         BackwardInner(backwardMostMove);
12739     }
12740
12741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12742         /* we have fed all the moves, so reactivate analysis mode */
12743         SendToProgram("analyze\n", &first);
12744         first.analyzing = TRUE;
12745         /*first.maybeThinking = TRUE;*/
12746         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12747     }
12748 }
12749
12750 void
12751 ToNrEvent(int to)
12752 {
12753   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12754   if (to >= forwardMostMove) to = forwardMostMove;
12755   if (to <= backwardMostMove) to = backwardMostMove;
12756   if (to < currentMove) {
12757     BackwardInner(to);
12758   } else {
12759     ForwardInner(to);
12760   }
12761 }
12762
12763 void
12764 RevertEvent(Boolean annotate)
12765 {
12766     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12767         return;
12768     }
12769     if (gameMode != IcsExamining) {
12770         DisplayError(_("You are not examining a game"), 0);
12771         return;
12772     }
12773     if (pausing) {
12774         DisplayError(_("You can't revert while pausing"), 0);
12775         return;
12776     }
12777     SendToICS(ics_prefix);
12778     SendToICS("revert\n");
12779 }
12780
12781 void
12782 RetractMoveEvent()
12783 {
12784     switch (gameMode) {
12785       case MachinePlaysWhite:
12786       case MachinePlaysBlack:
12787         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12788             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12789             return;
12790         }
12791         if (forwardMostMove < 2) return;
12792         currentMove = forwardMostMove = forwardMostMove - 2;
12793         whiteTimeRemaining = timeRemaining[0][currentMove];
12794         blackTimeRemaining = timeRemaining[1][currentMove];
12795         DisplayBothClocks();
12796         DisplayMove(currentMove - 1);
12797         ClearHighlights();/*!! could figure this out*/
12798         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12799         SendToProgram("remove\n", &first);
12800         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12801         break;
12802
12803       case BeginningOfGame:
12804       default:
12805         break;
12806
12807       case IcsPlayingWhite:
12808       case IcsPlayingBlack:
12809         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12810             SendToICS(ics_prefix);
12811             SendToICS("takeback 2\n");
12812         } else {
12813             SendToICS(ics_prefix);
12814             SendToICS("takeback 1\n");
12815         }
12816         break;
12817     }
12818 }
12819
12820 void
12821 MoveNowEvent()
12822 {
12823     ChessProgramState *cps;
12824
12825     switch (gameMode) {
12826       case MachinePlaysWhite:
12827         if (!WhiteOnMove(forwardMostMove)) {
12828             DisplayError(_("It is your turn"), 0);
12829             return;
12830         }
12831         cps = &first;
12832         break;
12833       case MachinePlaysBlack:
12834         if (WhiteOnMove(forwardMostMove)) {
12835             DisplayError(_("It is your turn"), 0);
12836             return;
12837         }
12838         cps = &first;
12839         break;
12840       case TwoMachinesPlay:
12841         if (WhiteOnMove(forwardMostMove) ==
12842             (first.twoMachinesColor[0] == 'w')) {
12843             cps = &first;
12844         } else {
12845             cps = &second;
12846         }
12847         break;
12848       case BeginningOfGame:
12849       default:
12850         return;
12851     }
12852     SendToProgram("?\n", cps);
12853 }
12854
12855 void
12856 TruncateGameEvent()
12857 {
12858     EditGameEvent();
12859     if (gameMode != EditGame) return;
12860     TruncateGame();
12861 }
12862
12863 void
12864 TruncateGame()
12865 {
12866     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12867     if (forwardMostMove > currentMove) {
12868         if (gameInfo.resultDetails != NULL) {
12869             free(gameInfo.resultDetails);
12870             gameInfo.resultDetails = NULL;
12871             gameInfo.result = GameUnfinished;
12872         }
12873         forwardMostMove = currentMove;
12874         HistorySet(parseList, backwardMostMove, forwardMostMove,
12875                    currentMove-1);
12876     }
12877 }
12878
12879 void
12880 HintEvent()
12881 {
12882     if (appData.noChessProgram) return;
12883     switch (gameMode) {
12884       case MachinePlaysWhite:
12885         if (WhiteOnMove(forwardMostMove)) {
12886             DisplayError(_("Wait until your turn"), 0);
12887             return;
12888         }
12889         break;
12890       case BeginningOfGame:
12891       case MachinePlaysBlack:
12892         if (!WhiteOnMove(forwardMostMove)) {
12893             DisplayError(_("Wait until your turn"), 0);
12894             return;
12895         }
12896         break;
12897       default:
12898         DisplayError(_("No hint available"), 0);
12899         return;
12900     }
12901     SendToProgram("hint\n", &first);
12902     hintRequested = TRUE;
12903 }
12904
12905 void
12906 BookEvent()
12907 {
12908     if (appData.noChessProgram) return;
12909     switch (gameMode) {
12910       case MachinePlaysWhite:
12911         if (WhiteOnMove(forwardMostMove)) {
12912             DisplayError(_("Wait until your turn"), 0);
12913             return;
12914         }
12915         break;
12916       case BeginningOfGame:
12917       case MachinePlaysBlack:
12918         if (!WhiteOnMove(forwardMostMove)) {
12919             DisplayError(_("Wait until your turn"), 0);
12920             return;
12921         }
12922         break;
12923       case EditPosition:
12924         EditPositionDone(TRUE);
12925         break;
12926       case TwoMachinesPlay:
12927         return;
12928       default:
12929         break;
12930     }
12931     SendToProgram("bk\n", &first);
12932     bookOutput[0] = NULLCHAR;
12933     bookRequested = TRUE;
12934 }
12935
12936 void
12937 AboutGameEvent()
12938 {
12939     char *tags = PGNTags(&gameInfo);
12940     TagsPopUp(tags, CmailMsg());
12941     free(tags);
12942 }
12943
12944 /* end button procedures */
12945
12946 void
12947 PrintPosition(fp, move)
12948      FILE *fp;
12949      int move;
12950 {
12951     int i, j;
12952     
12953     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12954         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12955             char c = PieceToChar(boards[move][i][j]);
12956             fputc(c == 'x' ? '.' : c, fp);
12957             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12958         }
12959     }
12960     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12961       fprintf(fp, "white to play\n");
12962     else
12963       fprintf(fp, "black to play\n");
12964 }
12965
12966 void
12967 PrintOpponents(fp)
12968      FILE *fp;
12969 {
12970     if (gameInfo.white != NULL) {
12971         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12972     } else {
12973         fprintf(fp, "\n");
12974     }
12975 }
12976
12977 /* Find last component of program's own name, using some heuristics */
12978 void
12979 TidyProgramName(prog, host, buf)
12980      char *prog, *host, buf[MSG_SIZ];
12981 {
12982     char *p, *q;
12983     int local = (strcmp(host, "localhost") == 0);
12984     while (!local && (p = strchr(prog, ';')) != NULL) {
12985         p++;
12986         while (*p == ' ') p++;
12987         prog = p;
12988     }
12989     if (*prog == '"' || *prog == '\'') {
12990         q = strchr(prog + 1, *prog);
12991     } else {
12992         q = strchr(prog, ' ');
12993     }
12994     if (q == NULL) q = prog + strlen(prog);
12995     p = q;
12996     while (p >= prog && *p != '/' && *p != '\\') p--;
12997     p++;
12998     if(p == prog && *p == '"') p++;
12999     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13000     memcpy(buf, p, q - p);
13001     buf[q - p] = NULLCHAR;
13002     if (!local) {
13003         strcat(buf, "@");
13004         strcat(buf, host);
13005     }
13006 }
13007
13008 char *
13009 TimeControlTagValue()
13010 {
13011     char buf[MSG_SIZ];
13012     if (!appData.clockMode) {
13013         strcpy(buf, "-");
13014     } else if (movesPerSession > 0) {
13015         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
13016     } else if (timeIncrement == 0) {
13017         sprintf(buf, "%ld", timeControl/1000);
13018     } else {
13019         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13020     }
13021     return StrSave(buf);
13022 }
13023
13024 void
13025 SetGameInfo()
13026 {
13027     /* This routine is used only for certain modes */
13028     VariantClass v = gameInfo.variant;
13029     ChessMove r = GameUnfinished;
13030     char *p = NULL;
13031
13032     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13033         r = gameInfo.result; 
13034         p = gameInfo.resultDetails; 
13035         gameInfo.resultDetails = NULL;
13036     }
13037     ClearGameInfo(&gameInfo);
13038     gameInfo.variant = v;
13039
13040     switch (gameMode) {
13041       case MachinePlaysWhite:
13042         gameInfo.event = StrSave( appData.pgnEventHeader );
13043         gameInfo.site = StrSave(HostName());
13044         gameInfo.date = PGNDate();
13045         gameInfo.round = StrSave("-");
13046         gameInfo.white = StrSave(first.tidy);
13047         gameInfo.black = StrSave(UserName());
13048         gameInfo.timeControl = TimeControlTagValue();
13049         break;
13050
13051       case MachinePlaysBlack:
13052         gameInfo.event = StrSave( appData.pgnEventHeader );
13053         gameInfo.site = StrSave(HostName());
13054         gameInfo.date = PGNDate();
13055         gameInfo.round = StrSave("-");
13056         gameInfo.white = StrSave(UserName());
13057         gameInfo.black = StrSave(first.tidy);
13058         gameInfo.timeControl = TimeControlTagValue();
13059         break;
13060
13061       case TwoMachinesPlay:
13062         gameInfo.event = StrSave( appData.pgnEventHeader );
13063         gameInfo.site = StrSave(HostName());
13064         gameInfo.date = PGNDate();
13065         if (matchGame > 0) {
13066             char buf[MSG_SIZ];
13067             sprintf(buf, "%d", matchGame);
13068             gameInfo.round = StrSave(buf);
13069         } else {
13070             gameInfo.round = StrSave("-");
13071         }
13072         if (first.twoMachinesColor[0] == 'w') {
13073             gameInfo.white = StrSave(first.tidy);
13074             gameInfo.black = StrSave(second.tidy);
13075         } else {
13076             gameInfo.white = StrSave(second.tidy);
13077             gameInfo.black = StrSave(first.tidy);
13078         }
13079         gameInfo.timeControl = TimeControlTagValue();
13080         break;
13081
13082       case EditGame:
13083         gameInfo.event = StrSave("Edited game");
13084         gameInfo.site = StrSave(HostName());
13085         gameInfo.date = PGNDate();
13086         gameInfo.round = StrSave("-");
13087         gameInfo.white = StrSave("-");
13088         gameInfo.black = StrSave("-");
13089         gameInfo.result = r;
13090         gameInfo.resultDetails = p;
13091         break;
13092
13093       case EditPosition:
13094         gameInfo.event = StrSave("Edited position");
13095         gameInfo.site = StrSave(HostName());
13096         gameInfo.date = PGNDate();
13097         gameInfo.round = StrSave("-");
13098         gameInfo.white = StrSave("-");
13099         gameInfo.black = StrSave("-");
13100         break;
13101
13102       case IcsPlayingWhite:
13103       case IcsPlayingBlack:
13104       case IcsObserving:
13105       case IcsExamining:
13106         break;
13107
13108       case PlayFromGameFile:
13109         gameInfo.event = StrSave("Game from non-PGN file");
13110         gameInfo.site = StrSave(HostName());
13111         gameInfo.date = PGNDate();
13112         gameInfo.round = StrSave("-");
13113         gameInfo.white = StrSave("?");
13114         gameInfo.black = StrSave("?");
13115         break;
13116
13117       default:
13118         break;
13119     }
13120 }
13121
13122 void
13123 ReplaceComment(index, text)
13124      int index;
13125      char *text;
13126 {
13127     int len;
13128
13129     while (*text == '\n') text++;
13130     len = strlen(text);
13131     while (len > 0 && text[len - 1] == '\n') len--;
13132
13133     if (commentList[index] != NULL)
13134       free(commentList[index]);
13135
13136     if (len == 0) {
13137         commentList[index] = NULL;
13138         return;
13139     }
13140   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13141       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13142       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13143     commentList[index] = (char *) malloc(len + 2);
13144     strncpy(commentList[index], text, len);
13145     commentList[index][len] = '\n';
13146     commentList[index][len + 1] = NULLCHAR;
13147   } else { 
13148     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13149     char *p;
13150     commentList[index] = (char *) malloc(len + 6);
13151     strcpy(commentList[index], "{\n");
13152     strncpy(commentList[index]+2, text, len);
13153     commentList[index][len+2] = NULLCHAR;
13154     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13155     strcat(commentList[index], "\n}\n");
13156   }
13157 }
13158
13159 void
13160 CrushCRs(text)
13161      char *text;
13162 {
13163   char *p = text;
13164   char *q = text;
13165   char ch;
13166
13167   do {
13168     ch = *p++;
13169     if (ch == '\r') continue;
13170     *q++ = ch;
13171   } while (ch != '\0');
13172 }
13173
13174 void
13175 AppendComment(index, text, addBraces)
13176      int index;
13177      char *text;
13178      Boolean addBraces; // [HGM] braces: tells if we should add {}
13179 {
13180     int oldlen, len;
13181     char *old;
13182
13183 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13184     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13185
13186     CrushCRs(text);
13187     while (*text == '\n') text++;
13188     len = strlen(text);
13189     while (len > 0 && text[len - 1] == '\n') len--;
13190
13191     if (len == 0) return;
13192
13193     if (commentList[index] != NULL) {
13194         old = commentList[index];
13195         oldlen = strlen(old);
13196         while(commentList[index][oldlen-1] ==  '\n')
13197           commentList[index][--oldlen] = NULLCHAR;
13198         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13199         strcpy(commentList[index], old);
13200         free(old);
13201         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13202         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13203           if(addBraces) addBraces = FALSE; else { text++; len--; }
13204           while (*text == '\n') { text++; len--; }
13205           commentList[index][--oldlen] = NULLCHAR;
13206       }
13207         if(addBraces) strcat(commentList[index], "\n{\n");
13208         else          strcat(commentList[index], "\n");
13209         strcat(commentList[index], text);
13210         if(addBraces) strcat(commentList[index], "\n}\n");
13211         else          strcat(commentList[index], "\n");
13212     } else {
13213         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13214         if(addBraces)
13215              strcpy(commentList[index], "{\n");
13216         else commentList[index][0] = NULLCHAR;
13217         strcat(commentList[index], text);
13218         strcat(commentList[index], "\n");
13219         if(addBraces) strcat(commentList[index], "}\n");
13220     }
13221 }
13222
13223 static char * FindStr( char * text, char * sub_text )
13224 {
13225     char * result = strstr( text, sub_text );
13226
13227     if( result != NULL ) {
13228         result += strlen( sub_text );
13229     }
13230
13231     return result;
13232 }
13233
13234 /* [AS] Try to extract PV info from PGN comment */
13235 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13236 char *GetInfoFromComment( int index, char * text )
13237 {
13238     char * sep = text;
13239
13240     if( text != NULL && index > 0 ) {
13241         int score = 0;
13242         int depth = 0;
13243         int time = -1, sec = 0, deci;
13244         char * s_eval = FindStr( text, "[%eval " );
13245         char * s_emt = FindStr( text, "[%emt " );
13246
13247         if( s_eval != NULL || s_emt != NULL ) {
13248             /* New style */
13249             char delim;
13250
13251             if( s_eval != NULL ) {
13252                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13253                     return text;
13254                 }
13255
13256                 if( delim != ']' ) {
13257                     return text;
13258                 }
13259             }
13260
13261             if( s_emt != NULL ) {
13262             }
13263                 return text;
13264         }
13265         else {
13266             /* We expect something like: [+|-]nnn.nn/dd */
13267             int score_lo = 0;
13268
13269             if(*text != '{') return text; // [HGM] braces: must be normal comment
13270
13271             sep = strchr( text, '/' );
13272             if( sep == NULL || sep < (text+4) ) {
13273                 return text;
13274             }
13275
13276             time = -1; sec = -1; deci = -1;
13277             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13278                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13279                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13280                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13281                 return text;
13282             }
13283
13284             if( score_lo < 0 || score_lo >= 100 ) {
13285                 return text;
13286             }
13287
13288             if(sec >= 0) time = 600*time + 10*sec; else
13289             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13290
13291             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13292
13293             /* [HGM] PV time: now locate end of PV info */
13294             while( *++sep >= '0' && *sep <= '9'); // strip depth
13295             if(time >= 0)
13296             while( *++sep >= '0' && *sep <= '9'); // strip time
13297             if(sec >= 0)
13298             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13299             if(deci >= 0)
13300             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13301             while(*sep == ' ') sep++;
13302         }
13303
13304         if( depth <= 0 ) {
13305             return text;
13306         }
13307
13308         if( time < 0 ) {
13309             time = -1;
13310         }
13311
13312         pvInfoList[index-1].depth = depth;
13313         pvInfoList[index-1].score = score;
13314         pvInfoList[index-1].time  = 10*time; // centi-sec
13315         if(*sep == '}') *sep = 0; else *--sep = '{';
13316     }
13317     return sep;
13318 }
13319
13320 void
13321 SendToProgram(message, cps)
13322      char *message;
13323      ChessProgramState *cps;
13324 {
13325     int count, outCount, error;
13326     char buf[MSG_SIZ];
13327
13328     if (cps->pr == NULL) return;
13329     Attention(cps);
13330     
13331     if (appData.debugMode) {
13332         TimeMark now;
13333         GetTimeMark(&now);
13334         fprintf(debugFP, "%ld >%-6s: %s", 
13335                 SubtractTimeMarks(&now, &programStartTime),
13336                 cps->which, message);
13337     }
13338     
13339     count = strlen(message);
13340     outCount = OutputToProcess(cps->pr, message, count, &error);
13341     if (outCount < count && !exiting 
13342                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13343         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13344         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13345             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13346                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13347                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13348             } else {
13349                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13350             }
13351             gameInfo.resultDetails = StrSave(buf);
13352         }
13353         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13354     }
13355 }
13356
13357 void
13358 ReceiveFromProgram(isr, closure, message, count, error)
13359      InputSourceRef isr;
13360      VOIDSTAR closure;
13361      char *message;
13362      int count;
13363      int error;
13364 {
13365     char *end_str;
13366     char buf[MSG_SIZ];
13367     ChessProgramState *cps = (ChessProgramState *)closure;
13368
13369     if (isr != cps->isr) return; /* Killed intentionally */
13370     if (count <= 0) {
13371         if (count == 0) {
13372             sprintf(buf,
13373                     _("Error: %s chess program (%s) exited unexpectedly"),
13374                     cps->which, cps->program);
13375         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13376                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13377                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13378                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13379                 } else {
13380                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13381                 }
13382                 gameInfo.resultDetails = StrSave(buf);
13383             }
13384             RemoveInputSource(cps->isr);
13385             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13386         } else {
13387             sprintf(buf,
13388                     _("Error reading from %s chess program (%s)"),
13389                     cps->which, cps->program);
13390             RemoveInputSource(cps->isr);
13391
13392             /* [AS] Program is misbehaving badly... kill it */
13393             if( count == -2 ) {
13394                 DestroyChildProcess( cps->pr, 9 );
13395                 cps->pr = NoProc;
13396             }
13397
13398             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13399         }
13400         return;
13401     }
13402     
13403     if ((end_str = strchr(message, '\r')) != NULL)
13404       *end_str = NULLCHAR;
13405     if ((end_str = strchr(message, '\n')) != NULL)
13406       *end_str = NULLCHAR;
13407     
13408     if (appData.debugMode) {
13409         TimeMark now; int print = 1;
13410         char *quote = ""; char c; int i;
13411
13412         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13413                 char start = message[0];
13414                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13415                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13416                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13417                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13418                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13419                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13420                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13421                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13422                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13423                     print = (appData.engineComments >= 2);
13424                 }
13425                 message[0] = start; // restore original message
13426         }
13427         if(print) {
13428                 GetTimeMark(&now);
13429                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13430                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13431                         quote,
13432                         message);
13433         }
13434     }
13435
13436     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13437     if (appData.icsEngineAnalyze) {
13438         if (strstr(message, "whisper") != NULL ||
13439              strstr(message, "kibitz") != NULL || 
13440             strstr(message, "tellics") != NULL) return;
13441     }
13442
13443     HandleMachineMove(message, cps);
13444 }
13445
13446
13447 void
13448 SendTimeControl(cps, mps, tc, inc, sd, st)
13449      ChessProgramState *cps;
13450      int mps, inc, sd, st;
13451      long tc;
13452 {
13453     char buf[MSG_SIZ];
13454     int seconds;
13455
13456     if( timeControl_2 > 0 ) {
13457         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13458             tc = timeControl_2;
13459         }
13460     }
13461     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13462     inc /= cps->timeOdds;
13463     st  /= cps->timeOdds;
13464
13465     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13466
13467     if (st > 0) {
13468       /* Set exact time per move, normally using st command */
13469       if (cps->stKludge) {
13470         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13471         seconds = st % 60;
13472         if (seconds == 0) {
13473           sprintf(buf, "level 1 %d\n", st/60);
13474         } else {
13475           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13476         }
13477       } else {
13478         sprintf(buf, "st %d\n", st);
13479       }
13480     } else {
13481       /* Set conventional or incremental time control, using level command */
13482       if (seconds == 0) {
13483         /* Note old gnuchess bug -- minutes:seconds used to not work.
13484            Fixed in later versions, but still avoid :seconds
13485            when seconds is 0. */
13486         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13487       } else {
13488         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13489                 seconds, inc/1000);
13490       }
13491     }
13492     SendToProgram(buf, cps);
13493
13494     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13495     /* Orthogonally, limit search to given depth */
13496     if (sd > 0) {
13497       if (cps->sdKludge) {
13498         sprintf(buf, "depth\n%d\n", sd);
13499       } else {
13500         sprintf(buf, "sd %d\n", sd);
13501       }
13502       SendToProgram(buf, cps);
13503     }
13504
13505     if(cps->nps > 0) { /* [HGM] nps */
13506         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13507         else {
13508                 sprintf(buf, "nps %d\n", cps->nps);
13509               SendToProgram(buf, cps);
13510         }
13511     }
13512 }
13513
13514 ChessProgramState *WhitePlayer()
13515 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13516 {
13517     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13518        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13519         return &second;
13520     return &first;
13521 }
13522
13523 void
13524 SendTimeRemaining(cps, machineWhite)
13525      ChessProgramState *cps;
13526      int /*boolean*/ machineWhite;
13527 {
13528     char message[MSG_SIZ];
13529     long time, otime;
13530
13531     /* Note: this routine must be called when the clocks are stopped
13532        or when they have *just* been set or switched; otherwise
13533        it will be off by the time since the current tick started.
13534     */
13535     if (machineWhite) {
13536         time = whiteTimeRemaining / 10;
13537         otime = blackTimeRemaining / 10;
13538     } else {
13539         time = blackTimeRemaining / 10;
13540         otime = whiteTimeRemaining / 10;
13541     }
13542     /* [HGM] translate opponent's time by time-odds factor */
13543     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13544     if (appData.debugMode) {
13545         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13546     }
13547
13548     if (time <= 0) time = 1;
13549     if (otime <= 0) otime = 1;
13550     
13551     sprintf(message, "time %ld\n", time);
13552     SendToProgram(message, cps);
13553
13554     sprintf(message, "otim %ld\n", otime);
13555     SendToProgram(message, cps);
13556 }
13557
13558 int
13559 BoolFeature(p, name, loc, cps)
13560      char **p;
13561      char *name;
13562      int *loc;
13563      ChessProgramState *cps;
13564 {
13565   char buf[MSG_SIZ];
13566   int len = strlen(name);
13567   int val;
13568   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13569     (*p) += len + 1;
13570     sscanf(*p, "%d", &val);
13571     *loc = (val != 0);
13572     while (**p && **p != ' ') (*p)++;
13573     sprintf(buf, "accepted %s\n", name);
13574     SendToProgram(buf, cps);
13575     return TRUE;
13576   }
13577   return FALSE;
13578 }
13579
13580 int
13581 IntFeature(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   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13590     (*p) += len + 1;
13591     sscanf(*p, "%d", loc);
13592     while (**p && **p != ' ') (*p)++;
13593     sprintf(buf, "accepted %s\n", name);
13594     SendToProgram(buf, cps);
13595     return TRUE;
13596   }
13597   return FALSE;
13598 }
13599
13600 int
13601 StringFeature(p, name, loc, cps)
13602      char **p;
13603      char *name;
13604      char loc[];
13605      ChessProgramState *cps;
13606 {
13607   char buf[MSG_SIZ];
13608   int len = strlen(name);
13609   if (strncmp((*p), name, len) == 0
13610       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13611     (*p) += len + 2;
13612     sscanf(*p, "%[^\"]", loc);
13613     while (**p && **p != '\"') (*p)++;
13614     if (**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 ParseOption(Option *opt, ChessProgramState *cps)
13624 // [HGM] options: process the string that defines an engine option, and determine
13625 // name, type, default value, and allowed value range
13626 {
13627         char *p, *q, buf[MSG_SIZ];
13628         int n, min = (-1)<<31, max = 1<<31, def;
13629
13630         if(p = strstr(opt->name, " -spin ")) {
13631             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13632             if(max < min) max = min; // enforce consistency
13633             if(def < min) def = min;
13634             if(def > max) def = max;
13635             opt->value = def;
13636             opt->min = min;
13637             opt->max = max;
13638             opt->type = Spin;
13639         } else if((p = strstr(opt->name, " -slider "))) {
13640             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13641             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13642             if(max < min) max = min; // enforce consistency
13643             if(def < min) def = min;
13644             if(def > max) def = max;
13645             opt->value = def;
13646             opt->min = min;
13647             opt->max = max;
13648             opt->type = Spin; // Slider;
13649         } else if((p = strstr(opt->name, " -string "))) {
13650             opt->textValue = p+9;
13651             opt->type = TextBox;
13652         } else if((p = strstr(opt->name, " -file "))) {
13653             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13654             opt->textValue = p+7;
13655             opt->type = TextBox; // FileName;
13656         } else if((p = strstr(opt->name, " -path "))) {
13657             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13658             opt->textValue = p+7;
13659             opt->type = TextBox; // PathName;
13660         } else if(p = strstr(opt->name, " -check ")) {
13661             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13662             opt->value = (def != 0);
13663             opt->type = CheckBox;
13664         } else if(p = strstr(opt->name, " -combo ")) {
13665             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13666             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13667             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13668             opt->value = n = 0;
13669             while(q = StrStr(q, " /// ")) {
13670                 n++; *q = 0;    // count choices, and null-terminate each of them
13671                 q += 5;
13672                 if(*q == '*') { // remember default, which is marked with * prefix
13673                     q++;
13674                     opt->value = n;
13675                 }
13676                 cps->comboList[cps->comboCnt++] = q;
13677             }
13678             cps->comboList[cps->comboCnt++] = NULL;
13679             opt->max = n + 1;
13680             opt->type = ComboBox;
13681         } else if(p = strstr(opt->name, " -button")) {
13682             opt->type = Button;
13683         } else if(p = strstr(opt->name, " -save")) {
13684             opt->type = SaveButton;
13685         } else return FALSE;
13686         *p = 0; // terminate option name
13687         // now look if the command-line options define a setting for this engine option.
13688         if(cps->optionSettings && cps->optionSettings[0])
13689             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13690         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13691                 sprintf(buf, "option %s", p);
13692                 if(p = strstr(buf, ",")) *p = 0;
13693                 strcat(buf, "\n");
13694                 SendToProgram(buf, cps);
13695         }
13696         return TRUE;
13697 }
13698
13699 void
13700 FeatureDone(cps, val)
13701      ChessProgramState* cps;
13702      int val;
13703 {
13704   DelayedEventCallback cb = GetDelayedEvent();
13705   if ((cb == InitBackEnd3 && cps == &first) ||
13706       (cb == TwoMachinesEventIfReady && cps == &second)) {
13707     CancelDelayedEvent();
13708     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13709   }
13710   cps->initDone = val;
13711 }
13712
13713 /* Parse feature command from engine */
13714 void
13715 ParseFeatures(args, cps)
13716      char* args;
13717      ChessProgramState *cps;  
13718 {
13719   char *p = args;
13720   char *q;
13721   int val;
13722   char buf[MSG_SIZ];
13723
13724   for (;;) {
13725     while (*p == ' ') p++;
13726     if (*p == NULLCHAR) return;
13727
13728     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13729     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13730     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13731     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13732     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13733     if (BoolFeature(&p, "reuse", &val, cps)) {
13734       /* Engine can disable reuse, but can't enable it if user said no */
13735       if (!val) cps->reuse = FALSE;
13736       continue;
13737     }
13738     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13739     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13740       if (gameMode == TwoMachinesPlay) {
13741         DisplayTwoMachinesTitle();
13742       } else {
13743         DisplayTitle("");
13744       }
13745       continue;
13746     }
13747     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13748     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13749     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13750     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13751     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13752     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13753     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13754     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13755     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13756     if (IntFeature(&p, "done", &val, cps)) {
13757       FeatureDone(cps, val);
13758       continue;
13759     }
13760     /* Added by Tord: */
13761     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13762     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13763     /* End of additions by Tord */
13764
13765     /* [HGM] added features: */
13766     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13767     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13768     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13769     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13770     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13771     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13772     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13773         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13774             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13775             SendToProgram(buf, cps);
13776             continue;
13777         }
13778         if(cps->nrOptions >= MAX_OPTIONS) {
13779             cps->nrOptions--;
13780             sprintf(buf, "%s engine has too many options\n", cps->which);
13781             DisplayError(buf, 0);
13782         }
13783         continue;
13784     }
13785     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13786     /* End of additions by HGM */
13787
13788     /* unknown feature: complain and skip */
13789     q = p;
13790     while (*q && *q != '=') q++;
13791     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13792     SendToProgram(buf, cps);
13793     p = q;
13794     if (*p == '=') {
13795       p++;
13796       if (*p == '\"') {
13797         p++;
13798         while (*p && *p != '\"') p++;
13799         if (*p == '\"') p++;
13800       } else {
13801         while (*p && *p != ' ') p++;
13802       }
13803     }
13804   }
13805
13806 }
13807
13808 void
13809 PeriodicUpdatesEvent(newState)
13810      int newState;
13811 {
13812     if (newState == appData.periodicUpdates)
13813       return;
13814
13815     appData.periodicUpdates=newState;
13816
13817     /* Display type changes, so update it now */
13818 //    DisplayAnalysis();
13819
13820     /* Get the ball rolling again... */
13821     if (newState) {
13822         AnalysisPeriodicEvent(1);
13823         StartAnalysisClock();
13824     }
13825 }
13826
13827 void
13828 PonderNextMoveEvent(newState)
13829      int newState;
13830 {
13831     if (newState == appData.ponderNextMove) return;
13832     if (gameMode == EditPosition) EditPositionDone(TRUE);
13833     if (newState) {
13834         SendToProgram("hard\n", &first);
13835         if (gameMode == TwoMachinesPlay) {
13836             SendToProgram("hard\n", &second);
13837         }
13838     } else {
13839         SendToProgram("easy\n", &first);
13840         thinkOutput[0] = NULLCHAR;
13841         if (gameMode == TwoMachinesPlay) {
13842             SendToProgram("easy\n", &second);
13843         }
13844     }
13845     appData.ponderNextMove = newState;
13846 }
13847
13848 void
13849 NewSettingEvent(option, command, value)
13850      char *command;
13851      int option, value;
13852 {
13853     char buf[MSG_SIZ];
13854
13855     if (gameMode == EditPosition) EditPositionDone(TRUE);
13856     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13857     SendToProgram(buf, &first);
13858     if (gameMode == TwoMachinesPlay) {
13859         SendToProgram(buf, &second);
13860     }
13861 }
13862
13863 void
13864 ShowThinkingEvent()
13865 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13866 {
13867     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13868     int newState = appData.showThinking
13869         // [HGM] thinking: other features now need thinking output as well
13870         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13871     
13872     if (oldState == newState) return;
13873     oldState = newState;
13874     if (gameMode == EditPosition) EditPositionDone(TRUE);
13875     if (oldState) {
13876         SendToProgram("post\n", &first);
13877         if (gameMode == TwoMachinesPlay) {
13878             SendToProgram("post\n", &second);
13879         }
13880     } else {
13881         SendToProgram("nopost\n", &first);
13882         thinkOutput[0] = NULLCHAR;
13883         if (gameMode == TwoMachinesPlay) {
13884             SendToProgram("nopost\n", &second);
13885         }
13886     }
13887 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13888 }
13889
13890 void
13891 AskQuestionEvent(title, question, replyPrefix, which)
13892      char *title; char *question; char *replyPrefix; char *which;
13893 {
13894   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13895   if (pr == NoProc) return;
13896   AskQuestion(title, question, replyPrefix, pr);
13897 }
13898
13899 void
13900 DisplayMove(moveNumber)
13901      int moveNumber;
13902 {
13903     char message[MSG_SIZ];
13904     char res[MSG_SIZ];
13905     char cpThinkOutput[MSG_SIZ];
13906
13907     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13908     
13909     if (moveNumber == forwardMostMove - 1 || 
13910         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13911
13912         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13913
13914         if (strchr(cpThinkOutput, '\n')) {
13915             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13916         }
13917     } else {
13918         *cpThinkOutput = NULLCHAR;
13919     }
13920
13921     /* [AS] Hide thinking from human user */
13922     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13923         *cpThinkOutput = NULLCHAR;
13924         if( thinkOutput[0] != NULLCHAR ) {
13925             int i;
13926
13927             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13928                 cpThinkOutput[i] = '.';
13929             }
13930             cpThinkOutput[i] = NULLCHAR;
13931             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13932         }
13933     }
13934
13935     if (moveNumber == forwardMostMove - 1 &&
13936         gameInfo.resultDetails != NULL) {
13937         if (gameInfo.resultDetails[0] == NULLCHAR) {
13938             sprintf(res, " %s", PGNResult(gameInfo.result));
13939         } else {
13940             sprintf(res, " {%s} %s",
13941                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13942         }
13943     } else {
13944         res[0] = NULLCHAR;
13945     }
13946
13947     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13948         DisplayMessage(res, cpThinkOutput);
13949     } else {
13950         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13951                 WhiteOnMove(moveNumber) ? " " : ".. ",
13952                 parseList[moveNumber], res);
13953         DisplayMessage(message, cpThinkOutput);
13954     }
13955 }
13956
13957 void
13958 DisplayComment(moveNumber, text)
13959      int moveNumber;
13960      char *text;
13961 {
13962     char title[MSG_SIZ];
13963     char buf[8000]; // comment can be long!
13964     int score, depth;
13965     
13966     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13967       strcpy(title, "Comment");
13968     } else {
13969       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13970               WhiteOnMove(moveNumber) ? " " : ".. ",
13971               parseList[moveNumber]);
13972     }
13973     // [HGM] PV info: display PV info together with (or as) comment
13974     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13975       if(text == NULL) text = "";                                           
13976       score = pvInfoList[moveNumber].score;
13977       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13978               depth, (pvInfoList[moveNumber].time+50)/100, text);
13979       text = buf;
13980     }
13981     if (text != NULL && (appData.autoDisplayComment || commentUp))
13982         CommentPopUp(title, text);
13983 }
13984
13985 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13986  * might be busy thinking or pondering.  It can be omitted if your
13987  * gnuchess is configured to stop thinking immediately on any user
13988  * input.  However, that gnuchess feature depends on the FIONREAD
13989  * ioctl, which does not work properly on some flavors of Unix.
13990  */
13991 void
13992 Attention(cps)
13993      ChessProgramState *cps;
13994 {
13995 #if ATTENTION
13996     if (!cps->useSigint) return;
13997     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13998     switch (gameMode) {
13999       case MachinePlaysWhite:
14000       case MachinePlaysBlack:
14001       case TwoMachinesPlay:
14002       case IcsPlayingWhite:
14003       case IcsPlayingBlack:
14004       case AnalyzeMode:
14005       case AnalyzeFile:
14006         /* Skip if we know it isn't thinking */
14007         if (!cps->maybeThinking) return;
14008         if (appData.debugMode)
14009           fprintf(debugFP, "Interrupting %s\n", cps->which);
14010         InterruptChildProcess(cps->pr);
14011         cps->maybeThinking = FALSE;
14012         break;
14013       default:
14014         break;
14015     }
14016 #endif /*ATTENTION*/
14017 }
14018
14019 int
14020 CheckFlags()
14021 {
14022     if (whiteTimeRemaining <= 0) {
14023         if (!whiteFlag) {
14024             whiteFlag = TRUE;
14025             if (appData.icsActive) {
14026                 if (appData.autoCallFlag &&
14027                     gameMode == IcsPlayingBlack && !blackFlag) {
14028                   SendToICS(ics_prefix);
14029                   SendToICS("flag\n");
14030                 }
14031             } else {
14032                 if (blackFlag) {
14033                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14034                 } else {
14035                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14036                     if (appData.autoCallFlag) {
14037                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14038                         return TRUE;
14039                     }
14040                 }
14041             }
14042         }
14043     }
14044     if (blackTimeRemaining <= 0) {
14045         if (!blackFlag) {
14046             blackFlag = TRUE;
14047             if (appData.icsActive) {
14048                 if (appData.autoCallFlag &&
14049                     gameMode == IcsPlayingWhite && !whiteFlag) {
14050                   SendToICS(ics_prefix);
14051                   SendToICS("flag\n");
14052                 }
14053             } else {
14054                 if (whiteFlag) {
14055                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14056                 } else {
14057                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14058                     if (appData.autoCallFlag) {
14059                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14060                         return TRUE;
14061                     }
14062                 }
14063             }
14064         }
14065     }
14066     return FALSE;
14067 }
14068
14069 void
14070 CheckTimeControl()
14071 {
14072     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14073         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14074
14075     /*
14076      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14077      */
14078     if ( !WhiteOnMove(forwardMostMove) )
14079         /* White made time control */
14080         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14081         /* [HGM] time odds: correct new time quota for time odds! */
14082                                             / WhitePlayer()->timeOdds;
14083       else
14084         /* Black made time control */
14085         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14086                                             / WhitePlayer()->other->timeOdds;
14087 }
14088
14089 void
14090 DisplayBothClocks()
14091 {
14092     int wom = gameMode == EditPosition ?
14093       !blackPlaysFirst : WhiteOnMove(currentMove);
14094     DisplayWhiteClock(whiteTimeRemaining, wom);
14095     DisplayBlackClock(blackTimeRemaining, !wom);
14096 }
14097
14098
14099 /* Timekeeping seems to be a portability nightmare.  I think everyone
14100    has ftime(), but I'm really not sure, so I'm including some ifdefs
14101    to use other calls if you don't.  Clocks will be less accurate if
14102    you have neither ftime nor gettimeofday.
14103 */
14104
14105 /* VS 2008 requires the #include outside of the function */
14106 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14107 #include <sys/timeb.h>
14108 #endif
14109
14110 /* Get the current time as a TimeMark */
14111 void
14112 GetTimeMark(tm)
14113      TimeMark *tm;
14114 {
14115 #if HAVE_GETTIMEOFDAY
14116
14117     struct timeval timeVal;
14118     struct timezone timeZone;
14119
14120     gettimeofday(&timeVal, &timeZone);
14121     tm->sec = (long) timeVal.tv_sec; 
14122     tm->ms = (int) (timeVal.tv_usec / 1000L);
14123
14124 #else /*!HAVE_GETTIMEOFDAY*/
14125 #if HAVE_FTIME
14126
14127 // include <sys/timeb.h> / moved to just above start of function
14128     struct timeb timeB;
14129
14130     ftime(&timeB);
14131     tm->sec = (long) timeB.time;
14132     tm->ms = (int) timeB.millitm;
14133
14134 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14135     tm->sec = (long) time(NULL);
14136     tm->ms = 0;
14137 #endif
14138 #endif
14139 }
14140
14141 /* Return the difference in milliseconds between two
14142    time marks.  We assume the difference will fit in a long!
14143 */
14144 long
14145 SubtractTimeMarks(tm2, tm1)
14146      TimeMark *tm2, *tm1;
14147 {
14148     return 1000L*(tm2->sec - tm1->sec) +
14149            (long) (tm2->ms - tm1->ms);
14150 }
14151
14152
14153 /*
14154  * Code to manage the game clocks.
14155  *
14156  * In tournament play, black starts the clock and then white makes a move.
14157  * We give the human user a slight advantage if he is playing white---the
14158  * clocks don't run until he makes his first move, so it takes zero time.
14159  * Also, we don't account for network lag, so we could get out of sync
14160  * with GNU Chess's clock -- but then, referees are always right.  
14161  */
14162
14163 static TimeMark tickStartTM;
14164 static long intendedTickLength;
14165
14166 long
14167 NextTickLength(timeRemaining)
14168      long timeRemaining;
14169 {
14170     long nominalTickLength, nextTickLength;
14171
14172     if (timeRemaining > 0L && timeRemaining <= 10000L)
14173       nominalTickLength = 100L;
14174     else
14175       nominalTickLength = 1000L;
14176     nextTickLength = timeRemaining % nominalTickLength;
14177     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14178
14179     return nextTickLength;
14180 }
14181
14182 /* Adjust clock one minute up or down */
14183 void
14184 AdjustClock(Boolean which, int dir)
14185 {
14186     if(which) blackTimeRemaining += 60000*dir;
14187     else      whiteTimeRemaining += 60000*dir;
14188     DisplayBothClocks();
14189 }
14190
14191 /* Stop clocks and reset to a fresh time control */
14192 void
14193 ResetClocks() 
14194 {
14195     (void) StopClockTimer();
14196     if (appData.icsActive) {
14197         whiteTimeRemaining = blackTimeRemaining = 0;
14198     } else if (searchTime) {
14199         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14200         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14201     } else { /* [HGM] correct new time quote for time odds */
14202         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14203         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14204     }
14205     if (whiteFlag || blackFlag) {
14206         DisplayTitle("");
14207         whiteFlag = blackFlag = FALSE;
14208     }
14209     DisplayBothClocks();
14210 }
14211
14212 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14213
14214 /* Decrement running clock by amount of time that has passed */
14215 void
14216 DecrementClocks()
14217 {
14218     long timeRemaining;
14219     long lastTickLength, fudge;
14220     TimeMark now;
14221
14222     if (!appData.clockMode) return;
14223     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14224         
14225     GetTimeMark(&now);
14226
14227     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14228
14229     /* Fudge if we woke up a little too soon */
14230     fudge = intendedTickLength - lastTickLength;
14231     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14232
14233     if (WhiteOnMove(forwardMostMove)) {
14234         if(whiteNPS >= 0) lastTickLength = 0;
14235         timeRemaining = whiteTimeRemaining -= lastTickLength;
14236         DisplayWhiteClock(whiteTimeRemaining - fudge,
14237                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14238     } else {
14239         if(blackNPS >= 0) lastTickLength = 0;
14240         timeRemaining = blackTimeRemaining -= lastTickLength;
14241         DisplayBlackClock(blackTimeRemaining - fudge,
14242                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14243     }
14244
14245     if (CheckFlags()) return;
14246         
14247     tickStartTM = now;
14248     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14249     StartClockTimer(intendedTickLength);
14250
14251     /* if the time remaining has fallen below the alarm threshold, sound the
14252      * alarm. if the alarm has sounded and (due to a takeback or time control
14253      * with increment) the time remaining has increased to a level above the
14254      * threshold, reset the alarm so it can sound again. 
14255      */
14256     
14257     if (appData.icsActive && appData.icsAlarm) {
14258
14259         /* make sure we are dealing with the user's clock */
14260         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14261                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14262            )) return;
14263
14264         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14265             alarmSounded = FALSE;
14266         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14267             PlayAlarmSound();
14268             alarmSounded = TRUE;
14269         }
14270     }
14271 }
14272
14273
14274 /* A player has just moved, so stop the previously running
14275    clock and (if in clock mode) start the other one.
14276    We redisplay both clocks in case we're in ICS mode, because
14277    ICS gives us an update to both clocks after every move.
14278    Note that this routine is called *after* forwardMostMove
14279    is updated, so the last fractional tick must be subtracted
14280    from the color that is *not* on move now.
14281 */
14282 void
14283 SwitchClocks(int newMoveNr)
14284 {
14285     long lastTickLength;
14286     TimeMark now;
14287     int flagged = FALSE;
14288
14289     GetTimeMark(&now);
14290
14291     if (StopClockTimer() && appData.clockMode) {
14292         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14293         if (!WhiteOnMove(forwardMostMove)) {
14294             if(blackNPS >= 0) lastTickLength = 0;
14295             blackTimeRemaining -= lastTickLength;
14296            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14297 //         if(pvInfoList[forwardMostMove-1].time == -1)
14298                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14299                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14300         } else {
14301            if(whiteNPS >= 0) lastTickLength = 0;
14302            whiteTimeRemaining -= lastTickLength;
14303            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14304 //         if(pvInfoList[forwardMostMove-1].time == -1)
14305                  pvInfoList[forwardMostMove-1].time = 
14306                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14307         }
14308         flagged = CheckFlags();
14309     }
14310     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14311     CheckTimeControl();
14312
14313     if (flagged || !appData.clockMode) return;
14314
14315     switch (gameMode) {
14316       case MachinePlaysBlack:
14317       case MachinePlaysWhite:
14318       case BeginningOfGame:
14319         if (pausing) return;
14320         break;
14321
14322       case EditGame:
14323       case PlayFromGameFile:
14324       case IcsExamining:
14325         return;
14326
14327       default:
14328         break;
14329     }
14330
14331     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14332         if(WhiteOnMove(forwardMostMove))
14333              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14334         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14335     }
14336
14337     tickStartTM = now;
14338     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14339       whiteTimeRemaining : blackTimeRemaining);
14340     StartClockTimer(intendedTickLength);
14341 }
14342         
14343
14344 /* Stop both clocks */
14345 void
14346 StopClocks()
14347 {       
14348     long lastTickLength;
14349     TimeMark now;
14350
14351     if (!StopClockTimer()) return;
14352     if (!appData.clockMode) return;
14353
14354     GetTimeMark(&now);
14355
14356     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14357     if (WhiteOnMove(forwardMostMove)) {
14358         if(whiteNPS >= 0) lastTickLength = 0;
14359         whiteTimeRemaining -= lastTickLength;
14360         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14361     } else {
14362         if(blackNPS >= 0) lastTickLength = 0;
14363         blackTimeRemaining -= lastTickLength;
14364         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14365     }
14366     CheckFlags();
14367 }
14368         
14369 /* Start clock of player on move.  Time may have been reset, so
14370    if clock is already running, stop and restart it. */
14371 void
14372 StartClocks()
14373 {
14374     (void) StopClockTimer(); /* in case it was running already */
14375     DisplayBothClocks();
14376     if (CheckFlags()) return;
14377
14378     if (!appData.clockMode) return;
14379     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14380
14381     GetTimeMark(&tickStartTM);
14382     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14383       whiteTimeRemaining : blackTimeRemaining);
14384
14385    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14386     whiteNPS = blackNPS = -1; 
14387     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14388        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14389         whiteNPS = first.nps;
14390     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14391        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14392         blackNPS = first.nps;
14393     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14394         whiteNPS = second.nps;
14395     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14396         blackNPS = second.nps;
14397     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14398
14399     StartClockTimer(intendedTickLength);
14400 }
14401
14402 char *
14403 TimeString(ms)
14404      long ms;
14405 {
14406     long second, minute, hour, day;
14407     char *sign = "";
14408     static char buf[32];
14409     
14410     if (ms > 0 && ms <= 9900) {
14411       /* convert milliseconds to tenths, rounding up */
14412       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14413
14414       sprintf(buf, " %03.1f ", tenths/10.0);
14415       return buf;
14416     }
14417
14418     /* convert milliseconds to seconds, rounding up */
14419     /* use floating point to avoid strangeness of integer division
14420        with negative dividends on many machines */
14421     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14422
14423     if (second < 0) {
14424         sign = "-";
14425         second = -second;
14426     }
14427     
14428     day = second / (60 * 60 * 24);
14429     second = second % (60 * 60 * 24);
14430     hour = second / (60 * 60);
14431     second = second % (60 * 60);
14432     minute = second / 60;
14433     second = second % 60;
14434     
14435     if (day > 0)
14436       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14437               sign, day, hour, minute, second);
14438     else if (hour > 0)
14439       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14440     else
14441       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14442     
14443     return buf;
14444 }
14445
14446
14447 /*
14448  * This is necessary because some C libraries aren't ANSI C compliant yet.
14449  */
14450 char *
14451 StrStr(string, match)
14452      char *string, *match;
14453 {
14454     int i, length;
14455     
14456     length = strlen(match);
14457     
14458     for (i = strlen(string) - length; i >= 0; i--, string++)
14459       if (!strncmp(match, string, length))
14460         return string;
14461     
14462     return NULL;
14463 }
14464
14465 char *
14466 StrCaseStr(string, match)
14467      char *string, *match;
14468 {
14469     int i, j, length;
14470     
14471     length = strlen(match);
14472     
14473     for (i = strlen(string) - length; i >= 0; i--, string++) {
14474         for (j = 0; j < length; j++) {
14475             if (ToLower(match[j]) != ToLower(string[j]))
14476               break;
14477         }
14478         if (j == length) return string;
14479     }
14480
14481     return NULL;
14482 }
14483
14484 #ifndef _amigados
14485 int
14486 StrCaseCmp(s1, s2)
14487      char *s1, *s2;
14488 {
14489     char c1, c2;
14490     
14491     for (;;) {
14492         c1 = ToLower(*s1++);
14493         c2 = ToLower(*s2++);
14494         if (c1 > c2) return 1;
14495         if (c1 < c2) return -1;
14496         if (c1 == NULLCHAR) return 0;
14497     }
14498 }
14499
14500
14501 int
14502 ToLower(c)
14503      int c;
14504 {
14505     return isupper(c) ? tolower(c) : c;
14506 }
14507
14508
14509 int
14510 ToUpper(c)
14511      int c;
14512 {
14513     return islower(c) ? toupper(c) : c;
14514 }
14515 #endif /* !_amigados    */
14516
14517 char *
14518 StrSave(s)
14519      char *s;
14520 {
14521     char *ret;
14522
14523     if ((ret = (char *) malloc(strlen(s) + 1))) {
14524         strcpy(ret, s);
14525     }
14526     return ret;
14527 }
14528
14529 char *
14530 StrSavePtr(s, savePtr)
14531      char *s, **savePtr;
14532 {
14533     if (*savePtr) {
14534         free(*savePtr);
14535     }
14536     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14537         strcpy(*savePtr, s);
14538     }
14539     return(*savePtr);
14540 }
14541
14542 char *
14543 PGNDate()
14544 {
14545     time_t clock;
14546     struct tm *tm;
14547     char buf[MSG_SIZ];
14548
14549     clock = time((time_t *)NULL);
14550     tm = localtime(&clock);
14551     sprintf(buf, "%04d.%02d.%02d",
14552             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14553     return StrSave(buf);
14554 }
14555
14556
14557 char *
14558 PositionToFEN(move, overrideCastling)
14559      int move;
14560      char *overrideCastling;
14561 {
14562     int i, j, fromX, fromY, toX, toY;
14563     int whiteToPlay;
14564     char buf[128];
14565     char *p, *q;
14566     int emptycount;
14567     ChessSquare piece;
14568
14569     whiteToPlay = (gameMode == EditPosition) ?
14570       !blackPlaysFirst : (move % 2 == 0);
14571     p = buf;
14572
14573     /* Piece placement data */
14574     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14575         emptycount = 0;
14576         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14577             if (boards[move][i][j] == EmptySquare) {
14578                 emptycount++;
14579             } else { ChessSquare piece = boards[move][i][j];
14580                 if (emptycount > 0) {
14581                     if(emptycount<10) /* [HGM] can be >= 10 */
14582                         *p++ = '0' + emptycount;
14583                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14584                     emptycount = 0;
14585                 }
14586                 if(PieceToChar(piece) == '+') {
14587                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14588                     *p++ = '+';
14589                     piece = (ChessSquare)(DEMOTED piece);
14590                 } 
14591                 *p++ = PieceToChar(piece);
14592                 if(p[-1] == '~') {
14593                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14594                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14595                     *p++ = '~';
14596                 }
14597             }
14598         }
14599         if (emptycount > 0) {
14600             if(emptycount<10) /* [HGM] can be >= 10 */
14601                 *p++ = '0' + emptycount;
14602             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14603             emptycount = 0;
14604         }
14605         *p++ = '/';
14606     }
14607     *(p - 1) = ' ';
14608
14609     /* [HGM] print Crazyhouse or Shogi holdings */
14610     if( gameInfo.holdingsWidth ) {
14611         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14612         q = p;
14613         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14614             piece = boards[move][i][BOARD_WIDTH-1];
14615             if( piece != EmptySquare )
14616               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14617                   *p++ = PieceToChar(piece);
14618         }
14619         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14620             piece = boards[move][BOARD_HEIGHT-i-1][0];
14621             if( piece != EmptySquare )
14622               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14623                   *p++ = PieceToChar(piece);
14624         }
14625
14626         if( q == p ) *p++ = '-';
14627         *p++ = ']';
14628         *p++ = ' ';
14629     }
14630
14631     /* Active color */
14632     *p++ = whiteToPlay ? 'w' : 'b';
14633     *p++ = ' ';
14634
14635   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14636     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14637   } else {
14638   if(nrCastlingRights) {
14639      q = p;
14640      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14641        /* [HGM] write directly from rights */
14642            if(boards[move][CASTLING][2] != NoRights &&
14643               boards[move][CASTLING][0] != NoRights   )
14644                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14645            if(boards[move][CASTLING][2] != NoRights &&
14646               boards[move][CASTLING][1] != NoRights   )
14647                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14648            if(boards[move][CASTLING][5] != NoRights &&
14649               boards[move][CASTLING][3] != NoRights   )
14650                 *p++ = boards[move][CASTLING][3] + AAA;
14651            if(boards[move][CASTLING][5] != NoRights &&
14652               boards[move][CASTLING][4] != NoRights   )
14653                 *p++ = boards[move][CASTLING][4] + AAA;
14654      } else {
14655
14656         /* [HGM] write true castling rights */
14657         if( nrCastlingRights == 6 ) {
14658             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14659                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14660             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14661                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14662             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14663                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14664             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14665                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14666         }
14667      }
14668      if (q == p) *p++ = '-'; /* No castling rights */
14669      *p++ = ' ';
14670   }
14671
14672   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14673      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14674     /* En passant target square */
14675     if (move > backwardMostMove) {
14676         fromX = moveList[move - 1][0] - AAA;
14677         fromY = moveList[move - 1][1] - ONE;
14678         toX = moveList[move - 1][2] - AAA;
14679         toY = moveList[move - 1][3] - ONE;
14680         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14681             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14682             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14683             fromX == toX) {
14684             /* 2-square pawn move just happened */
14685             *p++ = toX + AAA;
14686             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14687         } else {
14688             *p++ = '-';
14689         }
14690     } else if(move == backwardMostMove) {
14691         // [HGM] perhaps we should always do it like this, and forget the above?
14692         if((signed char)boards[move][EP_STATUS] >= 0) {
14693             *p++ = boards[move][EP_STATUS] + AAA;
14694             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14695         } else {
14696             *p++ = '-';
14697         }
14698     } else {
14699         *p++ = '-';
14700     }
14701     *p++ = ' ';
14702   }
14703   }
14704
14705     /* [HGM] find reversible plies */
14706     {   int i = 0, j=move;
14707
14708         if (appData.debugMode) { int k;
14709             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14710             for(k=backwardMostMove; k<=forwardMostMove; k++)
14711                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14712
14713         }
14714
14715         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14716         if( j == backwardMostMove ) i += initialRulePlies;
14717         sprintf(p, "%d ", i);
14718         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14719     }
14720     /* Fullmove number */
14721     sprintf(p, "%d", (move / 2) + 1);
14722     
14723     return StrSave(buf);
14724 }
14725
14726 Boolean
14727 ParseFEN(board, blackPlaysFirst, fen)
14728     Board board;
14729      int *blackPlaysFirst;
14730      char *fen;
14731 {
14732     int i, j;
14733     char *p;
14734     int emptycount;
14735     ChessSquare piece;
14736
14737     p = fen;
14738
14739     /* [HGM] by default clear Crazyhouse holdings, if present */
14740     if(gameInfo.holdingsWidth) {
14741        for(i=0; i<BOARD_HEIGHT; i++) {
14742            board[i][0]             = EmptySquare; /* black holdings */
14743            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14744            board[i][1]             = (ChessSquare) 0; /* black counts */
14745            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14746        }
14747     }
14748
14749     /* Piece placement data */
14750     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14751         j = 0;
14752         for (;;) {
14753             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14754                 if (*p == '/') p++;
14755                 emptycount = gameInfo.boardWidth - j;
14756                 while (emptycount--)
14757                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14758                 break;
14759 #if(BOARD_FILES >= 10)
14760             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14761                 p++; emptycount=10;
14762                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14763                 while (emptycount--)
14764                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14765 #endif
14766             } else if (isdigit(*p)) {
14767                 emptycount = *p++ - '0';
14768                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14769                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14770                 while (emptycount--)
14771                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14772             } else if (*p == '+' || isalpha(*p)) {
14773                 if (j >= gameInfo.boardWidth) return FALSE;
14774                 if(*p=='+') {
14775                     piece = CharToPiece(*++p);
14776                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14777                     piece = (ChessSquare) (PROMOTED piece ); p++;
14778                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14779                 } else piece = CharToPiece(*p++);
14780
14781                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14782                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14783                     piece = (ChessSquare) (PROMOTED piece);
14784                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14785                     p++;
14786                 }
14787                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14788             } else {
14789                 return FALSE;
14790             }
14791         }
14792     }
14793     while (*p == '/' || *p == ' ') p++;
14794
14795     /* [HGM] look for Crazyhouse holdings here */
14796     while(*p==' ') p++;
14797     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14798         if(*p == '[') p++;
14799         if(*p == '-' ) *p++; /* empty holdings */ else {
14800             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14801             /* if we would allow FEN reading to set board size, we would   */
14802             /* have to add holdings and shift the board read so far here   */
14803             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14804                 *p++;
14805                 if((int) piece >= (int) BlackPawn ) {
14806                     i = (int)piece - (int)BlackPawn;
14807                     i = PieceToNumber((ChessSquare)i);
14808                     if( i >= gameInfo.holdingsSize ) return FALSE;
14809                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14810                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14811                 } else {
14812                     i = (int)piece - (int)WhitePawn;
14813                     i = PieceToNumber((ChessSquare)i);
14814                     if( i >= gameInfo.holdingsSize ) return FALSE;
14815                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14816                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14817                 }
14818             }
14819         }
14820         if(*p == ']') *p++;
14821     }
14822
14823     while(*p == ' ') p++;
14824
14825     /* Active color */
14826     switch (*p++) {
14827       case 'w':
14828         *blackPlaysFirst = FALSE;
14829         break;
14830       case 'b': 
14831         *blackPlaysFirst = TRUE;
14832         break;
14833       default:
14834         return FALSE;
14835     }
14836
14837     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14838     /* return the extra info in global variiables             */
14839
14840     /* set defaults in case FEN is incomplete */
14841     board[EP_STATUS] = EP_UNKNOWN;
14842     for(i=0; i<nrCastlingRights; i++ ) {
14843         board[CASTLING][i] =
14844             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14845     }   /* assume possible unless obviously impossible */
14846     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14847     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14848     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14849                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14850     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14851     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14852     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14853                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14854     FENrulePlies = 0;
14855
14856     while(*p==' ') p++;
14857     if(nrCastlingRights) {
14858       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14859           /* castling indicator present, so default becomes no castlings */
14860           for(i=0; i<nrCastlingRights; i++ ) {
14861                  board[CASTLING][i] = NoRights;
14862           }
14863       }
14864       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14865              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14866              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14867              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14868         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14869
14870         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14871             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14872             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14873         }
14874         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14875             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14876         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14877                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14878         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14879                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14880         switch(c) {
14881           case'K':
14882               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14883               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14884               board[CASTLING][2] = whiteKingFile;
14885               break;
14886           case'Q':
14887               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14888               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14889               board[CASTLING][2] = whiteKingFile;
14890               break;
14891           case'k':
14892               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14893               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14894               board[CASTLING][5] = blackKingFile;
14895               break;
14896           case'q':
14897               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14898               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14899               board[CASTLING][5] = blackKingFile;
14900           case '-':
14901               break;
14902           default: /* FRC castlings */
14903               if(c >= 'a') { /* black rights */
14904                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14905                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14906                   if(i == BOARD_RGHT) break;
14907                   board[CASTLING][5] = i;
14908                   c -= AAA;
14909                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14910                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14911                   if(c > i)
14912                       board[CASTLING][3] = c;
14913                   else
14914                       board[CASTLING][4] = c;
14915               } else { /* white rights */
14916                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14917                     if(board[0][i] == WhiteKing) break;
14918                   if(i == BOARD_RGHT) break;
14919                   board[CASTLING][2] = i;
14920                   c -= AAA - 'a' + 'A';
14921                   if(board[0][c] >= WhiteKing) break;
14922                   if(c > i)
14923                       board[CASTLING][0] = c;
14924                   else
14925                       board[CASTLING][1] = c;
14926               }
14927         }
14928       }
14929       for(i=0; i<nrCastlingRights; i++)
14930         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14931     if (appData.debugMode) {
14932         fprintf(debugFP, "FEN castling rights:");
14933         for(i=0; i<nrCastlingRights; i++)
14934         fprintf(debugFP, " %d", board[CASTLING][i]);
14935         fprintf(debugFP, "\n");
14936     }
14937
14938       while(*p==' ') p++;
14939     }
14940
14941     /* read e.p. field in games that know e.p. capture */
14942     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14943        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14944       if(*p=='-') {
14945         p++; board[EP_STATUS] = EP_NONE;
14946       } else {
14947          char c = *p++ - AAA;
14948
14949          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14950          if(*p >= '0' && *p <='9') *p++;
14951          board[EP_STATUS] = c;
14952       }
14953     }
14954
14955
14956     if(sscanf(p, "%d", &i) == 1) {
14957         FENrulePlies = i; /* 50-move ply counter */
14958         /* (The move number is still ignored)    */
14959     }
14960
14961     return TRUE;
14962 }
14963       
14964 void
14965 EditPositionPasteFEN(char *fen)
14966 {
14967   if (fen != NULL) {
14968     Board initial_position;
14969
14970     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14971       DisplayError(_("Bad FEN position in clipboard"), 0);
14972       return ;
14973     } else {
14974       int savedBlackPlaysFirst = blackPlaysFirst;
14975       EditPositionEvent();
14976       blackPlaysFirst = savedBlackPlaysFirst;
14977       CopyBoard(boards[0], initial_position);
14978       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14979       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14980       DisplayBothClocks();
14981       DrawPosition(FALSE, boards[currentMove]);
14982     }
14983   }
14984 }
14985
14986 static char cseq[12] = "\\   ";
14987
14988 Boolean set_cont_sequence(char *new_seq)
14989 {
14990     int len;
14991     Boolean ret;
14992
14993     // handle bad attempts to set the sequence
14994         if (!new_seq)
14995                 return 0; // acceptable error - no debug
14996
14997     len = strlen(new_seq);
14998     ret = (len > 0) && (len < sizeof(cseq));
14999     if (ret)
15000         strcpy(cseq, new_seq);
15001     else if (appData.debugMode)
15002         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15003     return ret;
15004 }
15005
15006 /*
15007     reformat a source message so words don't cross the width boundary.  internal
15008     newlines are not removed.  returns the wrapped size (no null character unless
15009     included in source message).  If dest is NULL, only calculate the size required
15010     for the dest buffer.  lp argument indicats line position upon entry, and it's
15011     passed back upon exit.
15012 */
15013 int wrap(char *dest, char *src, int count, int width, int *lp)
15014 {
15015     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15016
15017     cseq_len = strlen(cseq);
15018     old_line = line = *lp;
15019     ansi = len = clen = 0;
15020
15021     for (i=0; i < count; i++)
15022     {
15023         if (src[i] == '\033')
15024             ansi = 1;
15025
15026         // if we hit the width, back up
15027         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15028         {
15029             // store i & len in case the word is too long
15030             old_i = i, old_len = len;
15031
15032             // find the end of the last word
15033             while (i && src[i] != ' ' && src[i] != '\n')
15034             {
15035                 i--;
15036                 len--;
15037             }
15038
15039             // word too long?  restore i & len before splitting it
15040             if ((old_i-i+clen) >= width)
15041             {
15042                 i = old_i;
15043                 len = old_len;
15044             }
15045
15046             // extra space?
15047             if (i && src[i-1] == ' ')
15048                 len--;
15049
15050             if (src[i] != ' ' && src[i] != '\n')
15051             {
15052                 i--;
15053                 if (len)
15054                     len--;
15055             }
15056
15057             // now append the newline and continuation sequence
15058             if (dest)
15059                 dest[len] = '\n';
15060             len++;
15061             if (dest)
15062                 strncpy(dest+len, cseq, cseq_len);
15063             len += cseq_len;
15064             line = cseq_len;
15065             clen = cseq_len;
15066             continue;
15067         }
15068
15069         if (dest)
15070             dest[len] = src[i];
15071         len++;
15072         if (!ansi)
15073             line++;
15074         if (src[i] == '\n')
15075             line = 0;
15076         if (src[i] == 'm')
15077             ansi = 0;
15078     }
15079     if (dest && appData.debugMode)
15080     {
15081         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15082             count, width, line, len, *lp);
15083         show_bytes(debugFP, src, count);
15084         fprintf(debugFP, "\ndest: ");
15085         show_bytes(debugFP, dest, len);
15086         fprintf(debugFP, "\n");
15087     }
15088     *lp = dest ? line : old_line;
15089
15090     return len;
15091 }
15092
15093 // [HGM] vari: routines for shelving variations
15094
15095 void 
15096 PushTail(int firstMove, int lastMove)
15097 {
15098         int i, j, nrMoves = lastMove - firstMove;
15099
15100         if(appData.icsActive) { // only in local mode
15101                 forwardMostMove = currentMove; // mimic old ICS behavior
15102                 return;
15103         }
15104         if(storedGames >= MAX_VARIATIONS-1) return;
15105
15106         // push current tail of game on stack
15107         savedResult[storedGames] = gameInfo.result;
15108         savedDetails[storedGames] = gameInfo.resultDetails;
15109         gameInfo.resultDetails = NULL;
15110         savedFirst[storedGames] = firstMove;
15111         savedLast [storedGames] = lastMove;
15112         savedFramePtr[storedGames] = framePtr;
15113         framePtr -= nrMoves; // reserve space for the boards
15114         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15115             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15116             for(j=0; j<MOVE_LEN; j++)
15117                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15118             for(j=0; j<2*MOVE_LEN; j++)
15119                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15120             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15121             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15122             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15123             pvInfoList[firstMove+i-1].depth = 0;
15124             commentList[framePtr+i] = commentList[firstMove+i];
15125             commentList[firstMove+i] = NULL;
15126         }
15127
15128         storedGames++;
15129         forwardMostMove = firstMove; // truncate game so we can start variation
15130         if(storedGames == 1) GreyRevert(FALSE);
15131 }
15132
15133 Boolean
15134 PopTail(Boolean annotate)
15135 {
15136         int i, j, nrMoves;
15137         char buf[8000], moveBuf[20];
15138
15139         if(appData.icsActive) return FALSE; // only in local mode
15140         if(!storedGames) return FALSE; // sanity
15141         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15142
15143         storedGames--;
15144         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15145         nrMoves = savedLast[storedGames] - currentMove;
15146         if(annotate) {
15147                 int cnt = 10;
15148                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15149                 else strcpy(buf, "(");
15150                 for(i=currentMove; i<forwardMostMove; i++) {
15151                         if(WhiteOnMove(i))
15152                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15153                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15154                         strcat(buf, moveBuf);
15155                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15156                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15157                 }
15158                 strcat(buf, ")");
15159         }
15160         for(i=1; i<=nrMoves; i++) { // copy last variation back
15161             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15162             for(j=0; j<MOVE_LEN; j++)
15163                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15164             for(j=0; j<2*MOVE_LEN; j++)
15165                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15166             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15167             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15168             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15169             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15170             commentList[currentMove+i] = commentList[framePtr+i];
15171             commentList[framePtr+i] = NULL;
15172         }
15173         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15174         framePtr = savedFramePtr[storedGames];
15175         gameInfo.result = savedResult[storedGames];
15176         if(gameInfo.resultDetails != NULL) {
15177             free(gameInfo.resultDetails);
15178       }
15179         gameInfo.resultDetails = savedDetails[storedGames];
15180         forwardMostMove = currentMove + nrMoves;
15181         if(storedGames == 0) GreyRevert(TRUE);
15182         return TRUE;
15183 }
15184
15185 void 
15186 CleanupTail()
15187 {       // remove all shelved variations
15188         int i;
15189         for(i=0; i<storedGames; i++) {
15190             if(savedDetails[i])
15191                 free(savedDetails[i]);
15192             savedDetails[i] = NULL;
15193         }
15194         for(i=framePtr; i<MAX_MOVES; i++) {
15195                 if(commentList[i]) free(commentList[i]);
15196                 commentList[i] = NULL;
15197         }
15198         framePtr = MAX_MOVES-1;
15199         storedGames = 0;
15200 }
15201
15202 void
15203 LoadVariation(int index, char *text)
15204 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15205         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15206         int level = 0, move;
15207
15208         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15209         // first find outermost bracketing variation
15210         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15211             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15212                 if(*p == '{') wait = '}'; else
15213                 if(*p == '[') wait = ']'; else
15214                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15215                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15216             }
15217             if(*p == wait) wait = NULLCHAR; // closing ]} found
15218             p++;
15219         }
15220         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15221         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15222         end[1] = NULLCHAR; // clip off comment beyond variation
15223         ToNrEvent(currentMove-1);
15224         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15225         // kludge: use ParsePV() to append variation to game
15226         move = currentMove;
15227         ParsePV(start, TRUE);
15228         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15229         ClearPremoveHighlights();
15230         CommentPopDown();
15231         ToNrEvent(currentMove+1);
15232 }
15233