Fix crash on switching to ICS xiangqi game
[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
1355 show_bytes(fp, buf, count)
1356      FILE *fp;
1357      char *buf;
1358      int count;
1359 {
1360     while (count--) {
1361         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362             fprintf(fp, "\\%03o", *buf & 0xff);
1363         } else {
1364             putc(*buf, fp);
1365         }
1366         buf++;
1367     }
1368     fflush(fp);
1369 }
1370
1371 /* Returns an errno value */
1372 int
1373 OutputMaybeTelnet(pr, message, count, outError)
1374      ProcRef pr;
1375      char *message;
1376      int count;
1377      int *outError;
1378 {
1379     char buf[8192], *p, *q, *buflim;
1380     int left, newcount, outcount;
1381
1382     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383         *appData.gateway != NULLCHAR) {
1384         if (appData.debugMode) {
1385             fprintf(debugFP, ">ICS: ");
1386             show_bytes(debugFP, message, count);
1387             fprintf(debugFP, "\n");
1388         }
1389         return OutputToProcess(pr, message, count, outError);
1390     }
1391
1392     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393     p = message;
1394     q = buf;
1395     left = count;
1396     newcount = 0;
1397     while (left) {
1398         if (q >= buflim) {
1399             if (appData.debugMode) {
1400                 fprintf(debugFP, ">ICS: ");
1401                 show_bytes(debugFP, buf, newcount);
1402                 fprintf(debugFP, "\n");
1403             }
1404             outcount = OutputToProcess(pr, buf, newcount, outError);
1405             if (outcount < newcount) return -1; /* to be sure */
1406             q = buf;
1407             newcount = 0;
1408         }
1409         if (*p == '\n') {
1410             *q++ = '\r';
1411             newcount++;
1412         } else if (((unsigned char) *p) == TN_IAC) {
1413             *q++ = (char) TN_IAC;
1414             newcount ++;
1415         }
1416         *q++ = *p++;
1417         newcount++;
1418         left--;
1419     }
1420     if (appData.debugMode) {
1421         fprintf(debugFP, ">ICS: ");
1422         show_bytes(debugFP, buf, newcount);
1423         fprintf(debugFP, "\n");
1424     }
1425     outcount = OutputToProcess(pr, buf, newcount, outError);
1426     if (outcount < newcount) return -1; /* to be sure */
1427     return count;
1428 }
1429
1430 void
1431 read_from_player(isr, closure, message, count, error)
1432      InputSourceRef isr;
1433      VOIDSTAR closure;
1434      char *message;
1435      int count;
1436      int error;
1437 {
1438     int outError, outCount;
1439     static int gotEof = 0;
1440
1441     /* Pass data read from player on to ICS */
1442     if (count > 0) {
1443         gotEof = 0;
1444         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445         if (outCount < count) {
1446             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1447         }
1448     } else if (count < 0) {
1449         RemoveInputSource(isr);
1450         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451     } else if (gotEof++ > 0) {
1452         RemoveInputSource(isr);
1453         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1454     }
1455 }
1456
1457 void
1458 KeepAlive()
1459 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1461     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1462     SendToICS("date\n");
1463     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1464 }
1465
1466 /* added routine for printf style output to ics */
1467 void ics_printf(char *format, ...)
1468 {
1469     char buffer[MSG_SIZ];
1470     va_list args;
1471
1472     va_start(args, format);
1473     vsnprintf(buffer, sizeof(buffer), format, args);
1474     buffer[sizeof(buffer)-1] = '\0';
1475     SendToICS(buffer);
1476     va_end(args);
1477 }
1478
1479 void
1480 SendToICS(s)
1481      char *s;
1482 {
1483     int count, outCount, outError;
1484
1485     if (icsPR == NULL) return;
1486
1487     count = strlen(s);
1488     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1489     if (outCount < count) {
1490         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1491     }
1492 }
1493
1494 /* This is used for sending logon scripts to the ICS. Sending
1495    without a delay causes problems when using timestamp on ICC
1496    (at least on my machine). */
1497 void
1498 SendToICSDelayed(s,msdelay)
1499      char *s;
1500      long msdelay;
1501 {
1502     int count, outCount, outError;
1503
1504     if (icsPR == NULL) return;
1505
1506     count = strlen(s);
1507     if (appData.debugMode) {
1508         fprintf(debugFP, ">ICS: ");
1509         show_bytes(debugFP, s, count);
1510         fprintf(debugFP, "\n");
1511     }
1512     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1513                                       msdelay);
1514     if (outCount < count) {
1515         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1516     }
1517 }
1518
1519
1520 /* Remove all highlighting escape sequences in s
1521    Also deletes any suffix starting with '(' 
1522    */
1523 char *
1524 StripHighlightAndTitle(s)
1525      char *s;
1526 {
1527     static char retbuf[MSG_SIZ];
1528     char *p = retbuf;
1529
1530     while (*s != NULLCHAR) {
1531         while (*s == '\033') {
1532             while (*s != NULLCHAR && !isalpha(*s)) s++;
1533             if (*s != NULLCHAR) s++;
1534         }
1535         while (*s != NULLCHAR && *s != '\033') {
1536             if (*s == '(' || *s == '[') {
1537                 *p = NULLCHAR;
1538                 return retbuf;
1539             }
1540             *p++ = *s++;
1541         }
1542     }
1543     *p = NULLCHAR;
1544     return retbuf;
1545 }
1546
1547 /* Remove all highlighting escape sequences in s */
1548 char *
1549 StripHighlight(s)
1550      char *s;
1551 {
1552     static char retbuf[MSG_SIZ];
1553     char *p = retbuf;
1554
1555     while (*s != NULLCHAR) {
1556         while (*s == '\033') {
1557             while (*s != NULLCHAR && !isalpha(*s)) s++;
1558             if (*s != NULLCHAR) s++;
1559         }
1560         while (*s != NULLCHAR && *s != '\033') {
1561             *p++ = *s++;
1562         }
1563     }
1564     *p = NULLCHAR;
1565     return retbuf;
1566 }
1567
1568 char *variantNames[] = VARIANT_NAMES;
1569 char *
1570 VariantName(v)
1571      VariantClass v;
1572 {
1573     return variantNames[v];
1574 }
1575
1576
1577 /* Identify a variant from the strings the chess servers use or the
1578    PGN Variant tag names we use. */
1579 VariantClass
1580 StringToVariant(e)
1581      char *e;
1582 {
1583     char *p;
1584     int wnum = -1;
1585     VariantClass v = VariantNormal;
1586     int i, found = FALSE;
1587     char buf[MSG_SIZ];
1588
1589     if (!e) return v;
1590
1591     /* [HGM] skip over optional board-size prefixes */
1592     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1593         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1594         while( *e++ != '_');
1595     }
1596
1597     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1598         v = VariantNormal;
1599         found = TRUE;
1600     } else
1601     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1602       if (StrCaseStr(e, variantNames[i])) {
1603         v = (VariantClass) i;
1604         found = TRUE;
1605         break;
1606       }
1607     }
1608
1609     if (!found) {
1610       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1611           || StrCaseStr(e, "wild/fr") 
1612           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1613         v = VariantFischeRandom;
1614       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1615                  (i = 1, p = StrCaseStr(e, "w"))) {
1616         p += i;
1617         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1618         if (isdigit(*p)) {
1619           wnum = atoi(p);
1620         } else {
1621           wnum = -1;
1622         }
1623         switch (wnum) {
1624         case 0: /* FICS only, actually */
1625         case 1:
1626           /* Castling legal even if K starts on d-file */
1627           v = VariantWildCastle;
1628           break;
1629         case 2:
1630         case 3:
1631         case 4:
1632           /* Castling illegal even if K & R happen to start in
1633              normal positions. */
1634           v = VariantNoCastle;
1635           break;
1636         case 5:
1637         case 7:
1638         case 8:
1639         case 10:
1640         case 11:
1641         case 12:
1642         case 13:
1643         case 14:
1644         case 15:
1645         case 18:
1646         case 19:
1647           /* Castling legal iff K & R start in normal positions */
1648           v = VariantNormal;
1649           break;
1650         case 6:
1651         case 20:
1652         case 21:
1653           /* Special wilds for position setup; unclear what to do here */
1654           v = VariantLoadable;
1655           break;
1656         case 9:
1657           /* Bizarre ICC game */
1658           v = VariantTwoKings;
1659           break;
1660         case 16:
1661           v = VariantKriegspiel;
1662           break;
1663         case 17:
1664           v = VariantLosers;
1665           break;
1666         case 22:
1667           v = VariantFischeRandom;
1668           break;
1669         case 23:
1670           v = VariantCrazyhouse;
1671           break;
1672         case 24:
1673           v = VariantBughouse;
1674           break;
1675         case 25:
1676           v = Variant3Check;
1677           break;
1678         case 26:
1679           /* Not quite the same as FICS suicide! */
1680           v = VariantGiveaway;
1681           break;
1682         case 27:
1683           v = VariantAtomic;
1684           break;
1685         case 28:
1686           v = VariantShatranj;
1687           break;
1688
1689         /* Temporary names for future ICC types.  The name *will* change in 
1690            the next xboard/WinBoard release after ICC defines it. */
1691         case 29:
1692           v = Variant29;
1693           break;
1694         case 30:
1695           v = Variant30;
1696           break;
1697         case 31:
1698           v = Variant31;
1699           break;
1700         case 32:
1701           v = Variant32;
1702           break;
1703         case 33:
1704           v = Variant33;
1705           break;
1706         case 34:
1707           v = Variant34;
1708           break;
1709         case 35:
1710           v = Variant35;
1711           break;
1712         case 36:
1713           v = Variant36;
1714           break;
1715         case 37:
1716           v = VariantShogi;
1717           break;
1718         case 38:
1719           v = VariantXiangqi;
1720           break;
1721         case 39:
1722           v = VariantCourier;
1723           break;
1724         case 40:
1725           v = VariantGothic;
1726           break;
1727         case 41:
1728           v = VariantCapablanca;
1729           break;
1730         case 42:
1731           v = VariantKnightmate;
1732           break;
1733         case 43:
1734           v = VariantFairy;
1735           break;
1736         case 44:
1737           v = VariantCylinder;
1738           break;
1739         case 45:
1740           v = VariantFalcon;
1741           break;
1742         case 46:
1743           v = VariantCapaRandom;
1744           break;
1745         case 47:
1746           v = VariantBerolina;
1747           break;
1748         case 48:
1749           v = VariantJanus;
1750           break;
1751         case 49:
1752           v = VariantSuper;
1753           break;
1754         case 50:
1755           v = VariantGreat;
1756           break;
1757         case -1:
1758           /* Found "wild" or "w" in the string but no number;
1759              must assume it's normal chess. */
1760           v = VariantNormal;
1761           break;
1762         default:
1763           sprintf(buf, _("Unknown wild type %d"), wnum);
1764           DisplayError(buf, 0);
1765           v = VariantUnknown;
1766           break;
1767         }
1768       }
1769     }
1770     if (appData.debugMode) {
1771       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1772               e, wnum, VariantName(v));
1773     }
1774     return v;
1775 }
1776
1777 static int leftover_start = 0, leftover_len = 0;
1778 char star_match[STAR_MATCH_N][MSG_SIZ];
1779
1780 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1781    advance *index beyond it, and set leftover_start to the new value of
1782    *index; else return FALSE.  If pattern contains the character '*', it
1783    matches any sequence of characters not containing '\r', '\n', or the
1784    character following the '*' (if any), and the matched sequence(s) are
1785    copied into star_match.
1786    */
1787 int
1788 looking_at(buf, index, pattern)
1789      char *buf;
1790      int *index;
1791      char *pattern;
1792 {
1793     char *bufp = &buf[*index], *patternp = pattern;
1794     int star_count = 0;
1795     char *matchp = star_match[0];
1796     
1797     for (;;) {
1798         if (*patternp == NULLCHAR) {
1799             *index = leftover_start = bufp - buf;
1800             *matchp = NULLCHAR;
1801             return TRUE;
1802         }
1803         if (*bufp == NULLCHAR) return FALSE;
1804         if (*patternp == '*') {
1805             if (*bufp == *(patternp + 1)) {
1806                 *matchp = NULLCHAR;
1807                 matchp = star_match[++star_count];
1808                 patternp += 2;
1809                 bufp++;
1810                 continue;
1811             } else if (*bufp == '\n' || *bufp == '\r') {
1812                 patternp++;
1813                 if (*patternp == NULLCHAR)
1814                   continue;
1815                 else
1816                   return FALSE;
1817             } else {
1818                 *matchp++ = *bufp++;
1819                 continue;
1820             }
1821         }
1822         if (*patternp != *bufp) return FALSE;
1823         patternp++;
1824         bufp++;
1825     }
1826 }
1827
1828 void
1829 SendToPlayer(data, length)
1830      char *data;
1831      int length;
1832 {
1833     int error, outCount;
1834     outCount = OutputToProcess(NoProc, data, length, &error);
1835     if (outCount < length) {
1836         DisplayFatalError(_("Error writing to display"), error, 1);
1837     }
1838 }
1839
1840 void
1841 PackHolding(packed, holding)
1842      char packed[];
1843      char *holding;
1844 {
1845     char *p = holding;
1846     char *q = packed;
1847     int runlength = 0;
1848     int curr = 9999;
1849     do {
1850         if (*p == curr) {
1851             runlength++;
1852         } else {
1853             switch (runlength) {
1854               case 0:
1855                 break;
1856               case 1:
1857                 *q++ = curr;
1858                 break;
1859               case 2:
1860                 *q++ = curr;
1861                 *q++ = curr;
1862                 break;
1863               default:
1864                 sprintf(q, "%d", runlength);
1865                 while (*q) q++;
1866                 *q++ = curr;
1867                 break;
1868             }
1869             runlength = 1;
1870             curr = *p;
1871         }
1872     } while (*p++);
1873     *q = NULLCHAR;
1874 }
1875
1876 /* Telnet protocol requests from the front end */
1877 void
1878 TelnetRequest(ddww, option)
1879      unsigned char ddww, option;
1880 {
1881     unsigned char msg[3];
1882     int outCount, outError;
1883
1884     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1885
1886     if (appData.debugMode) {
1887         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1888         switch (ddww) {
1889           case TN_DO:
1890             ddwwStr = "DO";
1891             break;
1892           case TN_DONT:
1893             ddwwStr = "DONT";
1894             break;
1895           case TN_WILL:
1896             ddwwStr = "WILL";
1897             break;
1898           case TN_WONT:
1899             ddwwStr = "WONT";
1900             break;
1901           default:
1902             ddwwStr = buf1;
1903             sprintf(buf1, "%d", ddww);
1904             break;
1905         }
1906         switch (option) {
1907           case TN_ECHO:
1908             optionStr = "ECHO";
1909             break;
1910           default:
1911             optionStr = buf2;
1912             sprintf(buf2, "%d", option);
1913             break;
1914         }
1915         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1916     }
1917     msg[0] = TN_IAC;
1918     msg[1] = ddww;
1919     msg[2] = option;
1920     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1921     if (outCount < 3) {
1922         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1923     }
1924 }
1925
1926 void
1927 DoEcho()
1928 {
1929     if (!appData.icsActive) return;
1930     TelnetRequest(TN_DO, TN_ECHO);
1931 }
1932
1933 void
1934 DontEcho()
1935 {
1936     if (!appData.icsActive) return;
1937     TelnetRequest(TN_DONT, TN_ECHO);
1938 }
1939
1940 void
1941 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1942 {
1943     /* put the holdings sent to us by the server on the board holdings area */
1944     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1945     char p;
1946     ChessSquare piece;
1947
1948     if(gameInfo.holdingsWidth < 2)  return;
1949     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1950         return; // prevent overwriting by pre-board holdings
1951
1952     if( (int)lowestPiece >= BlackPawn ) {
1953         holdingsColumn = 0;
1954         countsColumn = 1;
1955         holdingsStartRow = BOARD_HEIGHT-1;
1956         direction = -1;
1957     } else {
1958         holdingsColumn = BOARD_WIDTH-1;
1959         countsColumn = BOARD_WIDTH-2;
1960         holdingsStartRow = 0;
1961         direction = 1;
1962     }
1963
1964     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1965         board[i][holdingsColumn] = EmptySquare;
1966         board[i][countsColumn]   = (ChessSquare) 0;
1967     }
1968     while( (p=*holdings++) != NULLCHAR ) {
1969         piece = CharToPiece( ToUpper(p) );
1970         if(piece == EmptySquare) continue;
1971         /*j = (int) piece - (int) WhitePawn;*/
1972         j = PieceToNumber(piece);
1973         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1974         if(j < 0) continue;               /* should not happen */
1975         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1976         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1977         board[holdingsStartRow+j*direction][countsColumn]++;
1978     }
1979 }
1980
1981
1982 void
1983 VariantSwitch(Board board, VariantClass newVariant)
1984 {
1985    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1986    static Board oldBoard;
1987
1988    startedFromPositionFile = FALSE;
1989    if(gameInfo.variant == newVariant) return;
1990
1991    /* [HGM] This routine is called each time an assignment is made to
1992     * gameInfo.variant during a game, to make sure the board sizes
1993     * are set to match the new variant. If that means adding or deleting
1994     * holdings, we shift the playing board accordingly
1995     * This kludge is needed because in ICS observe mode, we get boards
1996     * of an ongoing game without knowing the variant, and learn about the
1997     * latter only later. This can be because of the move list we requested,
1998     * in which case the game history is refilled from the beginning anyway,
1999     * but also when receiving holdings of a crazyhouse game. In the latter
2000     * case we want to add those holdings to the already received position.
2001     */
2002
2003    
2004    if (appData.debugMode) {
2005      fprintf(debugFP, "Switch board from %s to %s\n",
2006              VariantName(gameInfo.variant), VariantName(newVariant));
2007      setbuf(debugFP, NULL);
2008    }
2009    shuffleOpenings = 0;       /* [HGM] shuffle */
2010    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2011    switch(newVariant) 
2012      {
2013      case VariantShogi:
2014        newWidth = 9;  newHeight = 9;
2015        gameInfo.holdingsSize = 7;
2016      case VariantBughouse:
2017      case VariantCrazyhouse:
2018        newHoldingsWidth = 2; break;
2019      case VariantGreat:
2020        newWidth = 10;
2021      case VariantSuper:
2022        newHoldingsWidth = 2;
2023        gameInfo.holdingsSize = 8;
2024        break;
2025      case VariantGothic:
2026      case VariantCapablanca:
2027      case VariantCapaRandom:
2028        newWidth = 10;
2029      default:
2030        newHoldingsWidth = gameInfo.holdingsSize = 0;
2031      };
2032    
2033    if(newWidth  != gameInfo.boardWidth  ||
2034       newHeight != gameInfo.boardHeight ||
2035       newHoldingsWidth != gameInfo.holdingsWidth ) {
2036      
2037      /* shift position to new playing area, if needed */
2038      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2039        for(i=0; i<BOARD_HEIGHT; i++) 
2040          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2041            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2042              board[i][j];
2043        for(i=0; i<newHeight; i++) {
2044          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2045          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2046        }
2047      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2048        for(i=0; i<BOARD_HEIGHT; i++)
2049          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2050            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2051              board[i][j];
2052      }
2053      gameInfo.boardWidth  = newWidth;
2054      gameInfo.boardHeight = newHeight;
2055      gameInfo.holdingsWidth = newHoldingsWidth;
2056      gameInfo.variant = newVariant;
2057      InitDrawingSizes(-2, 0);
2058    } else gameInfo.variant = newVariant;
2059    CopyBoard(oldBoard, board);   // remember correctly formatted board
2060      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2061    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2062 }
2063
2064 static int loggedOn = FALSE;
2065
2066 /*-- Game start info cache: --*/
2067 int gs_gamenum;
2068 char gs_kind[MSG_SIZ];
2069 static char player1Name[128] = "";
2070 static char player2Name[128] = "";
2071 static char cont_seq[] = "\n\\   ";
2072 static int player1Rating = -1;
2073 static int player2Rating = -1;
2074 /*----------------------------*/
2075
2076 ColorClass curColor = ColorNormal;
2077 int suppressKibitz = 0;
2078
2079 // [HGM] seekgraph
2080 Boolean soughtPending = FALSE;
2081 Boolean seekGraphUp;
2082 #define MAX_SEEK_ADS 200
2083 #define SQUARE 0x80
2084 char *seekAdList[MAX_SEEK_ADS];
2085 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2086 float tcList[MAX_SEEK_ADS];
2087 char colorList[MAX_SEEK_ADS];
2088 int nrOfSeekAds = 0;
2089 int minRating = 1010, maxRating = 2800;
2090 int hMargin = 10, vMargin = 20, h, w;
2091 extern int squareSize, lineGap;
2092
2093 void
2094 PlotSeekAd(int i)
2095 {
2096         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2097         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2098         if(r < minRating+100 && r >=0 ) r = minRating+100;
2099         if(r > maxRating) r = maxRating;
2100         if(tc < 1.) tc = 1.;
2101         if(tc > 95.) tc = 95.;
2102         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2103         y = ((double)r - minRating)/(maxRating - minRating)
2104             * (h-vMargin-squareSize/8-1) + vMargin;
2105         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2106         if(strstr(seekAdList[i], " u ")) color = 1;
2107         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2108            !strstr(seekAdList[i], "bullet") &&
2109            !strstr(seekAdList[i], "blitz") &&
2110            !strstr(seekAdList[i], "standard") ) color = 2;
2111         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2112         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2113 }
2114
2115 void
2116 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2117 {
2118         char buf[MSG_SIZ], *ext = "";
2119         VariantClass v = StringToVariant(type);
2120         if(strstr(type, "wild")) {
2121             ext = type + 4; // append wild number
2122             if(v == VariantFischeRandom) type = "chess960"; else
2123             if(v == VariantLoadable) type = "setup"; else
2124             type = VariantName(v);
2125         }
2126         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2127         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2128             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2129             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2130             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2131             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2132             seekNrList[nrOfSeekAds] = nr;
2133             zList[nrOfSeekAds] = 0;
2134             seekAdList[nrOfSeekAds++] = StrSave(buf);
2135             if(plot) PlotSeekAd(nrOfSeekAds-1);
2136         }
2137 }
2138
2139 void
2140 EraseSeekDot(int i)
2141 {
2142     int x = xList[i], y = yList[i], d=squareSize/4, k;
2143     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2144     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2145     // now replot every dot that overlapped
2146     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2147         int xx = xList[k], yy = yList[k];
2148         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2149             DrawSeekDot(xx, yy, colorList[k]);
2150     }
2151 }
2152
2153 void
2154 RemoveSeekAd(int nr)
2155 {
2156         int i;
2157         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2158             EraseSeekDot(i);
2159             if(seekAdList[i]) free(seekAdList[i]);
2160             seekAdList[i] = seekAdList[--nrOfSeekAds];
2161             seekNrList[i] = seekNrList[nrOfSeekAds];
2162             ratingList[i] = ratingList[nrOfSeekAds];
2163             colorList[i]  = colorList[nrOfSeekAds];
2164             tcList[i] = tcList[nrOfSeekAds];
2165             xList[i]  = xList[nrOfSeekAds];
2166             yList[i]  = yList[nrOfSeekAds];
2167             zList[i]  = zList[nrOfSeekAds];
2168             seekAdList[nrOfSeekAds] = NULL;
2169             break;
2170         }
2171 }
2172
2173 Boolean
2174 MatchSoughtLine(char *line)
2175 {
2176     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2177     int nr, base, inc, u=0; char dummy;
2178
2179     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2180        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2181        (u=1) &&
2182        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2183         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2184         // match: compact and save the line
2185         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2186         return TRUE;
2187     }
2188     return FALSE;
2189 }
2190
2191 int
2192 DrawSeekGraph()
2193 {
2194     if(!seekGraphUp) return FALSE;
2195     int i;
2196     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2197     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2198
2199     DrawSeekBackground(0, 0, w, h);
2200     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2201     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2202     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2203         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2204         yy = h-1-yy;
2205         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2206         if(i%500 == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2210         }
2211     }
2212     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2213     for(i=1; i<100; i+=(i<10?1:5)) {
2214         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2215         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2216         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2217             char buf[MSG_SIZ];
2218             sprintf(buf, "%d", i);
2219             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2220         }
2221     }
2222     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2223     return TRUE;
2224 }
2225
2226 int SeekGraphClick(ClickType click, int x, int y, int moving)
2227 {
2228     static int lastDown = 0, displayed = 0, lastSecond;
2229     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2230         if(click == Release || moving) return FALSE;
2231         nrOfSeekAds = 0;
2232         soughtPending = TRUE;
2233         SendToICS(ics_prefix);
2234         SendToICS("sought\n"); // should this be "sought all"?
2235     } else { // issue challenge based on clicked ad
2236         int dist = 10000; int i, closest = 0, second = 0;
2237         for(i=0; i<nrOfSeekAds; i++) {
2238             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2239             if(d < dist) { dist = d; closest = i; }
2240             second += (d - zList[i] < 120); // count in-range ads
2241             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2242         }
2243         if(dist < 120) {
2244             char buf[MSG_SIZ];
2245             second = (second > 1);
2246             if(displayed != closest || second != lastSecond) {
2247                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2248                 lastSecond = second; displayed = closest;
2249             }
2250             if(click == Press) {
2251                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2252                 lastDown = closest;
2253                 return TRUE;
2254             } // on press 'hit', only show info
2255             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2256             sprintf(buf, "play %d\n", seekNrList[closest]);
2257             SendToICS(ics_prefix);
2258             SendToICS(buf);
2259             return TRUE; // let incoming board of started game pop down the graph
2260         } else if(click == Release) { // release 'miss' is ignored
2261             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2262             if(moving == 2) { // right up-click
2263                 nrOfSeekAds = 0; // refresh graph
2264                 soughtPending = TRUE;
2265                 SendToICS(ics_prefix);
2266                 SendToICS("sought\n"); // should this be "sought all"?
2267             }
2268             return TRUE;
2269         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2270         // press miss or release hit 'pop down' seek graph
2271         seekGraphUp = FALSE;
2272         DrawPosition(TRUE, NULL);
2273     }
2274     return TRUE;
2275 }
2276
2277 void
2278 read_from_ics(isr, closure, data, count, error)
2279      InputSourceRef isr;
2280      VOIDSTAR closure;
2281      char *data;
2282      int count;
2283      int error;
2284 {
2285 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2286 #define STARTED_NONE 0
2287 #define STARTED_MOVES 1
2288 #define STARTED_BOARD 2
2289 #define STARTED_OBSERVE 3
2290 #define STARTED_HOLDINGS 4
2291 #define STARTED_CHATTER 5
2292 #define STARTED_COMMENT 6
2293 #define STARTED_MOVES_NOHIDE 7
2294     
2295     static int started = STARTED_NONE;
2296     static char parse[20000];
2297     static int parse_pos = 0;
2298     static char buf[BUF_SIZE + 1];
2299     static int firstTime = TRUE, intfSet = FALSE;
2300     static ColorClass prevColor = ColorNormal;
2301     static int savingComment = FALSE;
2302     static int cmatch = 0; // continuation sequence match
2303     char *bp;
2304     char str[500];
2305     int i, oldi;
2306     int buf_len;
2307     int next_out;
2308     int tkind;
2309     int backup;    /* [DM] For zippy color lines */
2310     char *p;
2311     char talker[MSG_SIZ]; // [HGM] chat
2312     int channel;
2313
2314     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2315
2316     if (appData.debugMode) {
2317       if (!error) {
2318         fprintf(debugFP, "<ICS: ");
2319         show_bytes(debugFP, data, count);
2320         fprintf(debugFP, "\n");
2321       }
2322     }
2323
2324     if (appData.debugMode) { int f = forwardMostMove;
2325         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2326                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2327                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2328     }
2329     if (count > 0) {
2330         /* If last read ended with a partial line that we couldn't parse,
2331            prepend it to the new read and try again. */
2332         if (leftover_len > 0) {
2333             for (i=0; i<leftover_len; i++)
2334               buf[i] = buf[leftover_start + i];
2335         }
2336
2337     /* copy new characters into the buffer */
2338     bp = buf + leftover_len;
2339     buf_len=leftover_len;
2340     for (i=0; i<count; i++)
2341     {
2342         // ignore these
2343         if (data[i] == '\r')
2344             continue;
2345
2346         // join lines split by ICS?
2347         if (!appData.noJoin)
2348         {
2349             /*
2350                 Joining just consists of finding matches against the
2351                 continuation sequence, and discarding that sequence
2352                 if found instead of copying it.  So, until a match
2353                 fails, there's nothing to do since it might be the
2354                 complete sequence, and thus, something we don't want
2355                 copied.
2356             */
2357             if (data[i] == cont_seq[cmatch])
2358             {
2359                 cmatch++;
2360                 if (cmatch == strlen(cont_seq))
2361                 {
2362                     cmatch = 0; // complete match.  just reset the counter
2363
2364                     /*
2365                         it's possible for the ICS to not include the space
2366                         at the end of the last word, making our [correct]
2367                         join operation fuse two separate words.  the server
2368                         does this when the space occurs at the width setting.
2369                     */
2370                     if (!buf_len || buf[buf_len-1] != ' ')
2371                     {
2372                         *bp++ = ' ';
2373                         buf_len++;
2374                     }
2375                 }
2376                 continue;
2377             }
2378             else if (cmatch)
2379             {
2380                 /*
2381                     match failed, so we have to copy what matched before
2382                     falling through and copying this character.  In reality,
2383                     this will only ever be just the newline character, but
2384                     it doesn't hurt to be precise.
2385                 */
2386                 strncpy(bp, cont_seq, cmatch);
2387                 bp += cmatch;
2388                 buf_len += cmatch;
2389                 cmatch = 0;
2390             }
2391         }
2392
2393         // copy this char
2394         *bp++ = data[i];
2395         buf_len++;
2396     }
2397
2398         buf[buf_len] = NULLCHAR;
2399 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2400         next_out = 0;
2401         leftover_start = 0;
2402         
2403         i = 0;
2404         while (i < buf_len) {
2405             /* Deal with part of the TELNET option negotiation
2406                protocol.  We refuse to do anything beyond the
2407                defaults, except that we allow the WILL ECHO option,
2408                which ICS uses to turn off password echoing when we are
2409                directly connected to it.  We reject this option
2410                if localLineEditing mode is on (always on in xboard)
2411                and we are talking to port 23, which might be a real
2412                telnet server that will try to keep WILL ECHO on permanently.
2413              */
2414             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2415                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2416                 unsigned char option;
2417                 oldi = i;
2418                 switch ((unsigned char) buf[++i]) {
2419                   case TN_WILL:
2420                     if (appData.debugMode)
2421                       fprintf(debugFP, "\n<WILL ");
2422                     switch (option = (unsigned char) buf[++i]) {
2423                       case TN_ECHO:
2424                         if (appData.debugMode)
2425                           fprintf(debugFP, "ECHO ");
2426                         /* Reply only if this is a change, according
2427                            to the protocol rules. */
2428                         if (remoteEchoOption) break;
2429                         if (appData.localLineEditing &&
2430                             atoi(appData.icsPort) == TN_PORT) {
2431                             TelnetRequest(TN_DONT, TN_ECHO);
2432                         } else {
2433                             EchoOff();
2434                             TelnetRequest(TN_DO, TN_ECHO);
2435                             remoteEchoOption = TRUE;
2436                         }
2437                         break;
2438                       default:
2439                         if (appData.debugMode)
2440                           fprintf(debugFP, "%d ", option);
2441                         /* Whatever this is, we don't want it. */
2442                         TelnetRequest(TN_DONT, option);
2443                         break;
2444                     }
2445                     break;
2446                   case TN_WONT:
2447                     if (appData.debugMode)
2448                       fprintf(debugFP, "\n<WONT ");
2449                     switch (option = (unsigned char) buf[++i]) {
2450                       case TN_ECHO:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "ECHO ");
2453                         /* Reply only if this is a change, according
2454                            to the protocol rules. */
2455                         if (!remoteEchoOption) break;
2456                         EchoOn();
2457                         TelnetRequest(TN_DONT, TN_ECHO);
2458                         remoteEchoOption = FALSE;
2459                         break;
2460                       default:
2461                         if (appData.debugMode)
2462                           fprintf(debugFP, "%d ", (unsigned char) option);
2463                         /* Whatever this is, it must already be turned
2464                            off, because we never agree to turn on
2465                            anything non-default, so according to the
2466                            protocol rules, we don't reply. */
2467                         break;
2468                     }
2469                     break;
2470                   case TN_DO:
2471                     if (appData.debugMode)
2472                       fprintf(debugFP, "\n<DO ");
2473                     switch (option = (unsigned char) buf[++i]) {
2474                       default:
2475                         /* Whatever this is, we refuse to do it. */
2476                         if (appData.debugMode)
2477                           fprintf(debugFP, "%d ", option);
2478                         TelnetRequest(TN_WONT, option);
2479                         break;
2480                     }
2481                     break;
2482                   case TN_DONT:
2483                     if (appData.debugMode)
2484                       fprintf(debugFP, "\n<DONT ");
2485                     switch (option = (unsigned char) buf[++i]) {
2486                       default:
2487                         if (appData.debugMode)
2488                           fprintf(debugFP, "%d ", option);
2489                         /* Whatever this is, we are already not doing
2490                            it, because we never agree to do anything
2491                            non-default, so according to the protocol
2492                            rules, we don't reply. */
2493                         break;
2494                     }
2495                     break;
2496                   case TN_IAC:
2497                     if (appData.debugMode)
2498                       fprintf(debugFP, "\n<IAC ");
2499                     /* Doubled IAC; pass it through */
2500                     i--;
2501                     break;
2502                   default:
2503                     if (appData.debugMode)
2504                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2505                     /* Drop all other telnet commands on the floor */
2506                     break;
2507                 }
2508                 if (oldi > next_out)
2509                   SendToPlayer(&buf[next_out], oldi - next_out);
2510                 if (++i > next_out)
2511                   next_out = i;
2512                 continue;
2513             }
2514                 
2515             /* OK, this at least will *usually* work */
2516             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2517                 loggedOn = TRUE;
2518             }
2519             
2520             if (loggedOn && !intfSet) {
2521                 if (ics_type == ICS_ICC) {
2522                   sprintf(str,
2523                           "/set-quietly interface %s\n/set-quietly style 12\n",
2524                           programVersion);
2525                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2526                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2527                 } else if (ics_type == ICS_CHESSNET) {
2528                   sprintf(str, "/style 12\n");
2529                 } else {
2530                   strcpy(str, "alias $ @\n$set interface ");
2531                   strcat(str, programVersion);
2532                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2533                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2534                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2535 #ifdef WIN32
2536                   strcat(str, "$iset nohighlight 1\n");
2537 #endif
2538                   strcat(str, "$iset lock 1\n$style 12\n");
2539                 }
2540                 SendToICS(str);
2541                 NotifyFrontendLogin();
2542                 intfSet = TRUE;
2543             }
2544
2545             if (started == STARTED_COMMENT) {
2546                 /* Accumulate characters in comment */
2547                 parse[parse_pos++] = buf[i];
2548                 if (buf[i] == '\n') {
2549                     parse[parse_pos] = NULLCHAR;
2550                     if(chattingPartner>=0) {
2551                         char mess[MSG_SIZ];
2552                         sprintf(mess, "%s%s", talker, parse);
2553                         OutputChatMessage(chattingPartner, mess);
2554                         chattingPartner = -1;
2555                         next_out = i+1; // [HGM] suppress printing in ICS window
2556                     } else
2557                     if(!suppressKibitz) // [HGM] kibitz
2558                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2559                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2560                         int nrDigit = 0, nrAlph = 0, j;
2561                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2562                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2563                         parse[parse_pos] = NULLCHAR;
2564                         // try to be smart: if it does not look like search info, it should go to
2565                         // ICS interaction window after all, not to engine-output window.
2566                         for(j=0; j<parse_pos; j++) { // count letters and digits
2567                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2568                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2569                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2570                         }
2571                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2572                             int depth=0; float score;
2573                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2574                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2575                                 pvInfoList[forwardMostMove-1].depth = depth;
2576                                 pvInfoList[forwardMostMove-1].score = 100*score;
2577                             }
2578                             OutputKibitz(suppressKibitz, parse);
2579                         } else {
2580                             char tmp[MSG_SIZ];
2581                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2582                             SendToPlayer(tmp, strlen(tmp));
2583                         }
2584                         next_out = i+1; // [HGM] suppress printing in ICS window
2585                     }
2586                     started = STARTED_NONE;
2587                 } else {
2588                     /* Don't match patterns against characters in comment */
2589                     i++;
2590                     continue;
2591                 }
2592             }
2593             if (started == STARTED_CHATTER) {
2594                 if (buf[i] != '\n') {
2595                     /* Don't match patterns against characters in chatter */
2596                     i++;
2597                     continue;
2598                 }
2599                 started = STARTED_NONE;
2600                 if(suppressKibitz) next_out = i+1;
2601             }
2602
2603             /* Kludge to deal with rcmd protocol */
2604             if (firstTime && looking_at(buf, &i, "\001*")) {
2605                 DisplayFatalError(&buf[1], 0, 1);
2606                 continue;
2607             } else {
2608                 firstTime = FALSE;
2609             }
2610
2611             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2612                 ics_type = ICS_ICC;
2613                 ics_prefix = "/";
2614                 if (appData.debugMode)
2615                   fprintf(debugFP, "ics_type %d\n", ics_type);
2616                 continue;
2617             }
2618             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2619                 ics_type = ICS_FICS;
2620                 ics_prefix = "$";
2621                 if (appData.debugMode)
2622                   fprintf(debugFP, "ics_type %d\n", ics_type);
2623                 continue;
2624             }
2625             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2626                 ics_type = ICS_CHESSNET;
2627                 ics_prefix = "/";
2628                 if (appData.debugMode)
2629                   fprintf(debugFP, "ics_type %d\n", ics_type);
2630                 continue;
2631             }
2632
2633             if (!loggedOn &&
2634                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2635                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2636                  looking_at(buf, &i, "will be \"*\""))) {
2637               strcpy(ics_handle, star_match[0]);
2638               continue;
2639             }
2640
2641             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2642               char buf[MSG_SIZ];
2643               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2644               DisplayIcsInteractionTitle(buf);
2645               have_set_title = TRUE;
2646             }
2647
2648             /* skip finger notes */
2649             if (started == STARTED_NONE &&
2650                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2651                  (buf[i] == '1' && buf[i+1] == '0')) &&
2652                 buf[i+2] == ':' && buf[i+3] == ' ') {
2653               started = STARTED_CHATTER;
2654               i += 3;
2655               continue;
2656             }
2657
2658             oldi = i;
2659             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2660             if(appData.seekGraph) {
2661                 if(soughtPending && MatchSoughtLine(buf+i)) {
2662                     i = strstr(buf+i, "rated") - buf;
2663                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2664                     next_out = leftover_start = i;
2665                     started = STARTED_CHATTER;
2666                     suppressKibitz = TRUE;
2667                     continue;
2668                 }
2669                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2670                         && looking_at(buf, &i, "* ads displayed")) {
2671                     soughtPending = FALSE;
2672                     seekGraphUp = TRUE;
2673                     DrawSeekGraph();
2674                     continue;
2675                 }
2676                 if(appData.autoRefresh) {
2677                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2678                         int s = (ics_type == ICS_ICC); // ICC format differs
2679                         if(seekGraphUp)
2680                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2681                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2682                         looking_at(buf, &i, "*% "); // eat prompt
2683                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2684                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2685                         next_out = i; // suppress
2686                         continue;
2687                     }
2688                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2689                         char *p = star_match[0];
2690                         while(*p) {
2691                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2692                             while(*p && *p++ != ' '); // next
2693                         }
2694                         looking_at(buf, &i, "*% "); // eat prompt
2695                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2696                         next_out = i;
2697                         continue;
2698                     }
2699                 }
2700             }
2701
2702             /* skip formula vars */
2703             if (started == STARTED_NONE &&
2704                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2705               started = STARTED_CHATTER;
2706               i += 3;
2707               continue;
2708             }
2709
2710             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2711             if (appData.autoKibitz && started == STARTED_NONE && 
2712                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2713                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2714                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2715                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2716                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2717                         suppressKibitz = TRUE;
2718                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2719                         next_out = i;
2720                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2721                                 && (gameMode == IcsPlayingWhite)) ||
2722                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2723                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2724                             started = STARTED_CHATTER; // own kibitz we simply discard
2725                         else {
2726                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2727                             parse_pos = 0; parse[0] = NULLCHAR;
2728                             savingComment = TRUE;
2729                             suppressKibitz = gameMode != IcsObserving ? 2 :
2730                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2731                         } 
2732                         continue;
2733                 } else
2734                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2735                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2736                          && atoi(star_match[0])) {
2737                     // suppress the acknowledgements of our own autoKibitz
2738                     char *p;
2739                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2740                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2741                     SendToPlayer(star_match[0], strlen(star_match[0]));
2742                     if(looking_at(buf, &i, "*% ")) // eat prompt
2743                         suppressKibitz = FALSE;
2744                     next_out = i;
2745                     continue;
2746                 }
2747             } // [HGM] kibitz: end of patch
2748
2749             // [HGM] chat: intercept tells by users for which we have an open chat window
2750             channel = -1;
2751             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2752                                            looking_at(buf, &i, "* whispers:") ||
2753                                            looking_at(buf, &i, "* kibitzes:") ||
2754                                            looking_at(buf, &i, "* shouts:") ||
2755                                            looking_at(buf, &i, "* c-shouts:") ||
2756                                            looking_at(buf, &i, "--> * ") ||
2757                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2758                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2759                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2760                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2761                 int p;
2762                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2763                 chattingPartner = -1;
2764
2765                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2766                 for(p=0; p<MAX_CHAT; p++) {
2767                     if(channel == atoi(chatPartner[p])) {
2768                     talker[0] = '['; strcat(talker, "] ");
2769                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2770                     chattingPartner = p; break;
2771                     }
2772                 } else
2773                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2774                 for(p=0; p<MAX_CHAT; p++) {
2775                     if(!strcmp("kibitzes", chatPartner[p])) {
2776                         talker[0] = '['; strcat(talker, "] ");
2777                         chattingPartner = p; break;
2778                     }
2779                 } else
2780                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2781                 for(p=0; p<MAX_CHAT; p++) {
2782                     if(!strcmp("whispers", chatPartner[p])) {
2783                         talker[0] = '['; strcat(talker, "] ");
2784                         chattingPartner = p; break;
2785                     }
2786                 } else
2787                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2788                   if(buf[i-8] == '-' && buf[i-3] == 't')
2789                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2790                     if(!strcmp("c-shouts", chatPartner[p])) {
2791                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2792                         chattingPartner = p; break;
2793                     }
2794                   }
2795                   if(chattingPartner < 0)
2796                   for(p=0; p<MAX_CHAT; p++) {
2797                     if(!strcmp("shouts", chatPartner[p])) {
2798                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2799                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2800                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2801                         chattingPartner = p; break;
2802                     }
2803                   }
2804                 }
2805                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2806                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2807                     talker[0] = 0; Colorize(ColorTell, FALSE);
2808                     chattingPartner = p; break;
2809                 }
2810                 if(chattingPartner<0) i = oldi; else {
2811                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2812                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2813                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2814                     started = STARTED_COMMENT;
2815                     parse_pos = 0; parse[0] = NULLCHAR;
2816                     savingComment = 3 + chattingPartner; // counts as TRUE
2817                     suppressKibitz = TRUE;
2818                     continue;
2819                 }
2820             } // [HGM] chat: end of patch
2821
2822             if (appData.zippyTalk || appData.zippyPlay) {
2823                 /* [DM] Backup address for color zippy lines */
2824                 backup = i;
2825 #if ZIPPY
2826        #ifdef WIN32
2827                if (loggedOn == TRUE)
2828                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2829                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2830        #else
2831                 if (ZippyControl(buf, &i) ||
2832                     ZippyConverse(buf, &i) ||
2833                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2834                       loggedOn = TRUE;
2835                       if (!appData.colorize) continue;
2836                 }
2837        #endif
2838 #endif
2839             } // [DM] 'else { ' deleted
2840                 if (
2841                     /* Regular tells and says */
2842                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2843                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2844                     looking_at(buf, &i, "* says: ") ||
2845                     /* Don't color "message" or "messages" output */
2846                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2847                     looking_at(buf, &i, "*. * at *:*: ") ||
2848                     looking_at(buf, &i, "--* (*:*): ") ||
2849                     /* Message notifications (same color as tells) */
2850                     looking_at(buf, &i, "* has left a message ") ||
2851                     looking_at(buf, &i, "* just sent you a message:\n") ||
2852                     /* Whispers and kibitzes */
2853                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2854                     looking_at(buf, &i, "* kibitzes: ") ||
2855                     /* Channel tells */
2856                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2857
2858                   if (tkind == 1 && strchr(star_match[0], ':')) {
2859                       /* Avoid "tells you:" spoofs in channels */
2860                      tkind = 3;
2861                   }
2862                   if (star_match[0][0] == NULLCHAR ||
2863                       strchr(star_match[0], ' ') ||
2864                       (tkind == 3 && strchr(star_match[1], ' '))) {
2865                     /* Reject bogus matches */
2866                     i = oldi;
2867                   } else {
2868                     if (appData.colorize) {
2869                       if (oldi > next_out) {
2870                         SendToPlayer(&buf[next_out], oldi - next_out);
2871                         next_out = oldi;
2872                       }
2873                       switch (tkind) {
2874                       case 1:
2875                         Colorize(ColorTell, FALSE);
2876                         curColor = ColorTell;
2877                         break;
2878                       case 2:
2879                         Colorize(ColorKibitz, FALSE);
2880                         curColor = ColorKibitz;
2881                         break;
2882                       case 3:
2883                         p = strrchr(star_match[1], '(');
2884                         if (p == NULL) {
2885                           p = star_match[1];
2886                         } else {
2887                           p++;
2888                         }
2889                         if (atoi(p) == 1) {
2890                           Colorize(ColorChannel1, FALSE);
2891                           curColor = ColorChannel1;
2892                         } else {
2893                           Colorize(ColorChannel, FALSE);
2894                           curColor = ColorChannel;
2895                         }
2896                         break;
2897                       case 5:
2898                         curColor = ColorNormal;
2899                         break;
2900                       }
2901                     }
2902                     if (started == STARTED_NONE && appData.autoComment &&
2903                         (gameMode == IcsObserving ||
2904                          gameMode == IcsPlayingWhite ||
2905                          gameMode == IcsPlayingBlack)) {
2906                       parse_pos = i - oldi;
2907                       memcpy(parse, &buf[oldi], parse_pos);
2908                       parse[parse_pos] = NULLCHAR;
2909                       started = STARTED_COMMENT;
2910                       savingComment = TRUE;
2911                     } else {
2912                       started = STARTED_CHATTER;
2913                       savingComment = FALSE;
2914                     }
2915                     loggedOn = TRUE;
2916                     continue;
2917                   }
2918                 }
2919
2920                 if (looking_at(buf, &i, "* s-shouts: ") ||
2921                     looking_at(buf, &i, "* c-shouts: ")) {
2922                     if (appData.colorize) {
2923                         if (oldi > next_out) {
2924                             SendToPlayer(&buf[next_out], oldi - next_out);
2925                             next_out = oldi;
2926                         }
2927                         Colorize(ColorSShout, FALSE);
2928                         curColor = ColorSShout;
2929                     }
2930                     loggedOn = TRUE;
2931                     started = STARTED_CHATTER;
2932                     continue;
2933                 }
2934
2935                 if (looking_at(buf, &i, "--->")) {
2936                     loggedOn = TRUE;
2937                     continue;
2938                 }
2939
2940                 if (looking_at(buf, &i, "* shouts: ") ||
2941                     looking_at(buf, &i, "--> ")) {
2942                     if (appData.colorize) {
2943                         if (oldi > next_out) {
2944                             SendToPlayer(&buf[next_out], oldi - next_out);
2945                             next_out = oldi;
2946                         }
2947                         Colorize(ColorShout, FALSE);
2948                         curColor = ColorShout;
2949                     }
2950                     loggedOn = TRUE;
2951                     started = STARTED_CHATTER;
2952                     continue;
2953                 }
2954
2955                 if (looking_at( buf, &i, "Challenge:")) {
2956                     if (appData.colorize) {
2957                         if (oldi > next_out) {
2958                             SendToPlayer(&buf[next_out], oldi - next_out);
2959                             next_out = oldi;
2960                         }
2961                         Colorize(ColorChallenge, FALSE);
2962                         curColor = ColorChallenge;
2963                     }
2964                     loggedOn = TRUE;
2965                     continue;
2966                 }
2967
2968                 if (looking_at(buf, &i, "* offers you") ||
2969                     looking_at(buf, &i, "* offers to be") ||
2970                     looking_at(buf, &i, "* would like to") ||
2971                     looking_at(buf, &i, "* requests to") ||
2972                     looking_at(buf, &i, "Your opponent offers") ||
2973                     looking_at(buf, &i, "Your opponent requests")) {
2974
2975                     if (appData.colorize) {
2976                         if (oldi > next_out) {
2977                             SendToPlayer(&buf[next_out], oldi - next_out);
2978                             next_out = oldi;
2979                         }
2980                         Colorize(ColorRequest, FALSE);
2981                         curColor = ColorRequest;
2982                     }
2983                     continue;
2984                 }
2985
2986                 if (looking_at(buf, &i, "* (*) seeking")) {
2987                     if (appData.colorize) {
2988                         if (oldi > next_out) {
2989                             SendToPlayer(&buf[next_out], oldi - next_out);
2990                             next_out = oldi;
2991                         }
2992                         Colorize(ColorSeek, FALSE);
2993                         curColor = ColorSeek;
2994                     }
2995                     continue;
2996             }
2997
2998             if (looking_at(buf, &i, "\\   ")) {
2999                 if (prevColor != ColorNormal) {
3000                     if (oldi > next_out) {
3001                         SendToPlayer(&buf[next_out], oldi - next_out);
3002                         next_out = oldi;
3003                     }
3004                     Colorize(prevColor, TRUE);
3005                     curColor = prevColor;
3006                 }
3007                 if (savingComment) {
3008                     parse_pos = i - oldi;
3009                     memcpy(parse, &buf[oldi], parse_pos);
3010                     parse[parse_pos] = NULLCHAR;
3011                     started = STARTED_COMMENT;
3012                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3013                         chattingPartner = savingComment - 3; // kludge to remember the box
3014                 } else {
3015                     started = STARTED_CHATTER;
3016                 }
3017                 continue;
3018             }
3019
3020             if (looking_at(buf, &i, "Black Strength :") ||
3021                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3022                 looking_at(buf, &i, "<10>") ||
3023                 looking_at(buf, &i, "#@#")) {
3024                 /* Wrong board style */
3025                 loggedOn = TRUE;
3026                 SendToICS(ics_prefix);
3027                 SendToICS("set style 12\n");
3028                 SendToICS(ics_prefix);
3029                 SendToICS("refresh\n");
3030                 continue;
3031             }
3032             
3033             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3034                 ICSInitScript();
3035                 have_sent_ICS_logon = 1;
3036                 continue;
3037             }
3038               
3039             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3040                 (looking_at(buf, &i, "\n<12> ") ||
3041                  looking_at(buf, &i, "<12> "))) {
3042                 loggedOn = TRUE;
3043                 if (oldi > next_out) {
3044                     SendToPlayer(&buf[next_out], oldi - next_out);
3045                 }
3046                 next_out = i;
3047                 started = STARTED_BOARD;
3048                 parse_pos = 0;
3049                 continue;
3050             }
3051
3052             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3053                 looking_at(buf, &i, "<b1> ")) {
3054                 if (oldi > next_out) {
3055                     SendToPlayer(&buf[next_out], oldi - next_out);
3056                 }
3057                 next_out = i;
3058                 started = STARTED_HOLDINGS;
3059                 parse_pos = 0;
3060                 continue;
3061             }
3062
3063             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3064                 loggedOn = TRUE;
3065                 /* Header for a move list -- first line */
3066
3067                 switch (ics_getting_history) {
3068                   case H_FALSE:
3069                     switch (gameMode) {
3070                       case IcsIdle:
3071                       case BeginningOfGame:
3072                         /* User typed "moves" or "oldmoves" while we
3073                            were idle.  Pretend we asked for these
3074                            moves and soak them up so user can step
3075                            through them and/or save them.
3076                            */
3077                         Reset(FALSE, TRUE);
3078                         gameMode = IcsObserving;
3079                         ModeHighlight();
3080                         ics_gamenum = -1;
3081                         ics_getting_history = H_GOT_UNREQ_HEADER;
3082                         break;
3083                       case EditGame: /*?*/
3084                       case EditPosition: /*?*/
3085                         /* Should above feature work in these modes too? */
3086                         /* For now it doesn't */
3087                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3088                         break;
3089                       default:
3090                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3091                         break;
3092                     }
3093                     break;
3094                   case H_REQUESTED:
3095                     /* Is this the right one? */
3096                     if (gameInfo.white && gameInfo.black &&
3097                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3098                         strcmp(gameInfo.black, star_match[2]) == 0) {
3099                         /* All is well */
3100                         ics_getting_history = H_GOT_REQ_HEADER;
3101                     }
3102                     break;
3103                   case H_GOT_REQ_HEADER:
3104                   case H_GOT_UNREQ_HEADER:
3105                   case H_GOT_UNWANTED_HEADER:
3106                   case H_GETTING_MOVES:
3107                     /* Should not happen */
3108                     DisplayError(_("Error gathering move list: two headers"), 0);
3109                     ics_getting_history = H_FALSE;
3110                     break;
3111                 }
3112
3113                 /* Save player ratings into gameInfo if needed */
3114                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3115                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3116                     (gameInfo.whiteRating == -1 ||
3117                      gameInfo.blackRating == -1)) {
3118
3119                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3120                     gameInfo.blackRating = string_to_rating(star_match[3]);
3121                     if (appData.debugMode)
3122                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3123                               gameInfo.whiteRating, gameInfo.blackRating);
3124                 }
3125                 continue;
3126             }
3127
3128             if (looking_at(buf, &i,
3129               "* * match, initial time: * minute*, increment: * second")) {
3130                 /* Header for a move list -- second line */
3131                 /* Initial board will follow if this is a wild game */
3132                 if (gameInfo.event != NULL) free(gameInfo.event);
3133                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3134                 gameInfo.event = StrSave(str);
3135                 /* [HGM] we switched variant. Translate boards if needed. */
3136                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3137                 continue;
3138             }
3139
3140             if (looking_at(buf, &i, "Move  ")) {
3141                 /* Beginning of a move list */
3142                 switch (ics_getting_history) {
3143                   case H_FALSE:
3144                     /* Normally should not happen */
3145                     /* Maybe user hit reset while we were parsing */
3146                     break;
3147                   case H_REQUESTED:
3148                     /* Happens if we are ignoring a move list that is not
3149                      * the one we just requested.  Common if the user
3150                      * tries to observe two games without turning off
3151                      * getMoveList */
3152                     break;
3153                   case H_GETTING_MOVES:
3154                     /* Should not happen */
3155                     DisplayError(_("Error gathering move list: nested"), 0);
3156                     ics_getting_history = H_FALSE;
3157                     break;
3158                   case H_GOT_REQ_HEADER:
3159                     ics_getting_history = H_GETTING_MOVES;
3160                     started = STARTED_MOVES;
3161                     parse_pos = 0;
3162                     if (oldi > next_out) {
3163                         SendToPlayer(&buf[next_out], oldi - next_out);
3164                     }
3165                     break;
3166                   case H_GOT_UNREQ_HEADER:
3167                     ics_getting_history = H_GETTING_MOVES;
3168                     started = STARTED_MOVES_NOHIDE;
3169                     parse_pos = 0;
3170                     break;
3171                   case H_GOT_UNWANTED_HEADER:
3172                     ics_getting_history = H_FALSE;
3173                     break;
3174                 }
3175                 continue;
3176             }                           
3177             
3178             if (looking_at(buf, &i, "% ") ||
3179                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3180                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3181                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3182                     soughtPending = FALSE;
3183                     seekGraphUp = TRUE;
3184                     DrawSeekGraph();
3185                 }
3186                 if(suppressKibitz) next_out = i;
3187                 savingComment = FALSE;
3188                 suppressKibitz = 0;
3189                 switch (started) {
3190                   case STARTED_MOVES:
3191                   case STARTED_MOVES_NOHIDE:
3192                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3193                     parse[parse_pos + i - oldi] = NULLCHAR;
3194                     ParseGameHistory(parse);
3195 #if ZIPPY
3196                     if (appData.zippyPlay && first.initDone) {
3197                         FeedMovesToProgram(&first, forwardMostMove);
3198                         if (gameMode == IcsPlayingWhite) {
3199                             if (WhiteOnMove(forwardMostMove)) {
3200                                 if (first.sendTime) {
3201                                   if (first.useColors) {
3202                                     SendToProgram("black\n", &first); 
3203                                   }
3204                                   SendTimeRemaining(&first, TRUE);
3205                                 }
3206                                 if (first.useColors) {
3207                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3208                                 }
3209                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3210                                 first.maybeThinking = TRUE;
3211                             } else {
3212                                 if (first.usePlayother) {
3213                                   if (first.sendTime) {
3214                                     SendTimeRemaining(&first, TRUE);
3215                                   }
3216                                   SendToProgram("playother\n", &first);
3217                                   firstMove = FALSE;
3218                                 } else {
3219                                   firstMove = TRUE;
3220                                 }
3221                             }
3222                         } else if (gameMode == IcsPlayingBlack) {
3223                             if (!WhiteOnMove(forwardMostMove)) {
3224                                 if (first.sendTime) {
3225                                   if (first.useColors) {
3226                                     SendToProgram("white\n", &first);
3227                                   }
3228                                   SendTimeRemaining(&first, FALSE);
3229                                 }
3230                                 if (first.useColors) {
3231                                   SendToProgram("black\n", &first);
3232                                 }
3233                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3234                                 first.maybeThinking = TRUE;
3235                             } else {
3236                                 if (first.usePlayother) {
3237                                   if (first.sendTime) {
3238                                     SendTimeRemaining(&first, FALSE);
3239                                   }
3240                                   SendToProgram("playother\n", &first);
3241                                   firstMove = FALSE;
3242                                 } else {
3243                                   firstMove = TRUE;
3244                                 }
3245                             }
3246                         }                       
3247                     }
3248 #endif
3249                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3250                         /* Moves came from oldmoves or moves command
3251                            while we weren't doing anything else.
3252                            */
3253                         currentMove = forwardMostMove;
3254                         ClearHighlights();/*!!could figure this out*/
3255                         flipView = appData.flipView;
3256                         DrawPosition(TRUE, boards[currentMove]);
3257                         DisplayBothClocks();
3258                         sprintf(str, "%s vs. %s",
3259                                 gameInfo.white, gameInfo.black);
3260                         DisplayTitle(str);
3261                         gameMode = IcsIdle;
3262                     } else {
3263                         /* Moves were history of an active game */
3264                         if (gameInfo.resultDetails != NULL) {
3265                             free(gameInfo.resultDetails);
3266                             gameInfo.resultDetails = NULL;
3267                         }
3268                     }
3269                     HistorySet(parseList, backwardMostMove,
3270                                forwardMostMove, currentMove-1);
3271                     DisplayMove(currentMove - 1);
3272                     if (started == STARTED_MOVES) next_out = i;
3273                     started = STARTED_NONE;
3274                     ics_getting_history = H_FALSE;
3275                     break;
3276
3277                   case STARTED_OBSERVE:
3278                     started = STARTED_NONE;
3279                     SendToICS(ics_prefix);
3280                     SendToICS("refresh\n");
3281                     break;
3282
3283                   default:
3284                     break;
3285                 }
3286                 if(bookHit) { // [HGM] book: simulate book reply
3287                     static char bookMove[MSG_SIZ]; // a bit generous?
3288
3289                     programStats.nodes = programStats.depth = programStats.time = 
3290                     programStats.score = programStats.got_only_move = 0;
3291                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3292
3293                     strcpy(bookMove, "move ");
3294                     strcat(bookMove, bookHit);
3295                     HandleMachineMove(bookMove, &first);
3296                 }
3297                 continue;
3298             }
3299             
3300             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3301                  started == STARTED_HOLDINGS ||
3302                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3303                 /* Accumulate characters in move list or board */
3304                 parse[parse_pos++] = buf[i];
3305             }
3306             
3307             /* Start of game messages.  Mostly we detect start of game
3308                when the first board image arrives.  On some versions
3309                of the ICS, though, we need to do a "refresh" after starting
3310                to observe in order to get the current board right away. */
3311             if (looking_at(buf, &i, "Adding game * to observation list")) {
3312                 started = STARTED_OBSERVE;
3313                 continue;
3314             }
3315
3316             /* Handle auto-observe */
3317             if (appData.autoObserve &&
3318                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3319                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3320                 char *player;
3321                 /* Choose the player that was highlighted, if any. */
3322                 if (star_match[0][0] == '\033' ||
3323                     star_match[1][0] != '\033') {
3324                     player = star_match[0];
3325                 } else {
3326                     player = star_match[2];
3327                 }
3328                 sprintf(str, "%sobserve %s\n",
3329                         ics_prefix, StripHighlightAndTitle(player));
3330                 SendToICS(str);
3331
3332                 /* Save ratings from notify string */
3333                 strcpy(player1Name, star_match[0]);
3334                 player1Rating = string_to_rating(star_match[1]);
3335                 strcpy(player2Name, star_match[2]);
3336                 player2Rating = string_to_rating(star_match[3]);
3337
3338                 if (appData.debugMode)
3339                   fprintf(debugFP, 
3340                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3341                           player1Name, player1Rating,
3342                           player2Name, player2Rating);
3343
3344                 continue;
3345             }
3346
3347             /* Deal with automatic examine mode after a game,
3348                and with IcsObserving -> IcsExamining transition */
3349             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3350                 looking_at(buf, &i, "has made you an examiner of game *")) {
3351
3352                 int gamenum = atoi(star_match[0]);
3353                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3354                     gamenum == ics_gamenum) {
3355                     /* We were already playing or observing this game;
3356                        no need to refetch history */
3357                     gameMode = IcsExamining;
3358                     if (pausing) {
3359                         pauseExamForwardMostMove = forwardMostMove;
3360                     } else if (currentMove < forwardMostMove) {
3361                         ForwardInner(forwardMostMove);
3362                     }
3363                 } else {
3364                     /* I don't think this case really can happen */
3365                     SendToICS(ics_prefix);
3366                     SendToICS("refresh\n");
3367                 }
3368                 continue;
3369             }    
3370             
3371             /* Error messages */
3372 //          if (ics_user_moved) {
3373             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3374                 if (looking_at(buf, &i, "Illegal move") ||
3375                     looking_at(buf, &i, "Not a legal move") ||
3376                     looking_at(buf, &i, "Your king is in check") ||
3377                     looking_at(buf, &i, "It isn't your turn") ||
3378                     looking_at(buf, &i, "It is not your move")) {
3379                     /* Illegal move */
3380                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3381                         currentMove = forwardMostMove-1;
3382                         DisplayMove(currentMove - 1); /* before DMError */
3383                         DrawPosition(FALSE, boards[currentMove]);
3384                         SwitchClocks(forwardMostMove-1); // [HGM] race
3385                         DisplayBothClocks();
3386                     }
3387                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3388                     ics_user_moved = 0;
3389                     continue;
3390                 }
3391             }
3392
3393             if (looking_at(buf, &i, "still have time") ||
3394                 looking_at(buf, &i, "not out of time") ||
3395                 looking_at(buf, &i, "either player is out of time") ||
3396                 looking_at(buf, &i, "has timeseal; checking")) {
3397                 /* We must have called his flag a little too soon */
3398                 whiteFlag = blackFlag = FALSE;
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "added * seconds to") ||
3403                 looking_at(buf, &i, "seconds were added to")) {
3404                 /* Update the clocks */
3405                 SendToICS(ics_prefix);
3406                 SendToICS("refresh\n");
3407                 continue;
3408             }
3409
3410             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3411                 ics_clock_paused = TRUE;
3412                 StopClocks();
3413                 continue;
3414             }
3415
3416             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3417                 ics_clock_paused = FALSE;
3418                 StartClocks();
3419                 continue;
3420             }
3421
3422             /* Grab player ratings from the Creating: message.
3423                Note we have to check for the special case when
3424                the ICS inserts things like [white] or [black]. */
3425             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3426                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3427                 /* star_matches:
3428                    0    player 1 name (not necessarily white)
3429                    1    player 1 rating
3430                    2    empty, white, or black (IGNORED)
3431                    3    player 2 name (not necessarily black)
3432                    4    player 2 rating
3433                    
3434                    The names/ratings are sorted out when the game
3435                    actually starts (below).
3436                 */
3437                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3438                 player1Rating = string_to_rating(star_match[1]);
3439                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3440                 player2Rating = string_to_rating(star_match[4]);
3441
3442                 if (appData.debugMode)
3443                   fprintf(debugFP, 
3444                           "Ratings from 'Creating:' %s %d, %s %d\n",
3445                           player1Name, player1Rating,
3446                           player2Name, player2Rating);
3447
3448                 continue;
3449             }
3450             
3451             /* Improved generic start/end-of-game messages */
3452             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3453                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3454                 /* If tkind == 0: */
3455                 /* star_match[0] is the game number */
3456                 /*           [1] is the white player's name */
3457                 /*           [2] is the black player's name */
3458                 /* For end-of-game: */
3459                 /*           [3] is the reason for the game end */
3460                 /*           [4] is a PGN end game-token, preceded by " " */
3461                 /* For start-of-game: */
3462                 /*           [3] begins with "Creating" or "Continuing" */
3463                 /*           [4] is " *" or empty (don't care). */
3464                 int gamenum = atoi(star_match[0]);
3465                 char *whitename, *blackname, *why, *endtoken;
3466                 ChessMove endtype = (ChessMove) 0;
3467
3468                 if (tkind == 0) {
3469                   whitename = star_match[1];
3470                   blackname = star_match[2];
3471                   why = star_match[3];
3472                   endtoken = star_match[4];
3473                 } else {
3474                   whitename = star_match[1];
3475                   blackname = star_match[3];
3476                   why = star_match[5];
3477                   endtoken = star_match[6];
3478                 }
3479
3480                 /* Game start messages */
3481                 if (strncmp(why, "Creating ", 9) == 0 ||
3482                     strncmp(why, "Continuing ", 11) == 0) {
3483                     gs_gamenum = gamenum;
3484                     strcpy(gs_kind, strchr(why, ' ') + 1);
3485                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3486 #if ZIPPY
3487                     if (appData.zippyPlay) {
3488                         ZippyGameStart(whitename, blackname);
3489                     }
3490 #endif /*ZIPPY*/
3491                     partnerBoardValid = FALSE; // [HGM] bughouse
3492                     continue;
3493                 }
3494
3495                 /* Game end messages */
3496                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3497                     ics_gamenum != gamenum) {
3498                     continue;
3499                 }
3500                 while (endtoken[0] == ' ') endtoken++;
3501                 switch (endtoken[0]) {
3502                   case '*':
3503                   default:
3504                     endtype = GameUnfinished;
3505                     break;
3506                   case '0':
3507                     endtype = BlackWins;
3508                     break;
3509                   case '1':
3510                     if (endtoken[1] == '/')
3511                       endtype = GameIsDrawn;
3512                     else
3513                       endtype = WhiteWins;
3514                     break;
3515                 }
3516                 GameEnds(endtype, why, GE_ICS);
3517 #if ZIPPY
3518                 if (appData.zippyPlay && first.initDone) {
3519                     ZippyGameEnd(endtype, why);
3520                     if (first.pr == NULL) {
3521                       /* Start the next process early so that we'll
3522                          be ready for the next challenge */
3523                       StartChessProgram(&first);
3524                     }
3525                     /* Send "new" early, in case this command takes
3526                        a long time to finish, so that we'll be ready
3527                        for the next challenge. */
3528                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3529                     Reset(TRUE, TRUE);
3530                 }
3531 #endif /*ZIPPY*/
3532                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i, "Removing game * from observation") ||
3537                 looking_at(buf, &i, "no longer observing game *") ||
3538                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3539                 if (gameMode == IcsObserving &&
3540                     atoi(star_match[0]) == ics_gamenum)
3541                   {
3542                       /* icsEngineAnalyze */
3543                       if (appData.icsEngineAnalyze) {
3544                             ExitAnalyzeMode();
3545                             ModeHighlight();
3546                       }
3547                       StopClocks();
3548                       gameMode = IcsIdle;
3549                       ics_gamenum = -1;
3550                       ics_user_moved = FALSE;
3551                   }
3552                 continue;
3553             }
3554
3555             if (looking_at(buf, &i, "no longer examining game *")) {
3556                 if (gameMode == IcsExamining &&
3557                     atoi(star_match[0]) == ics_gamenum)
3558                   {
3559                       gameMode = IcsIdle;
3560                       ics_gamenum = -1;
3561                       ics_user_moved = FALSE;
3562                   }
3563                 continue;
3564             }
3565
3566             /* Advance leftover_start past any newlines we find,
3567                so only partial lines can get reparsed */
3568             if (looking_at(buf, &i, "\n")) {
3569                 prevColor = curColor;
3570                 if (curColor != ColorNormal) {
3571                     if (oldi > next_out) {
3572                         SendToPlayer(&buf[next_out], oldi - next_out);
3573                         next_out = oldi;
3574                     }
3575                     Colorize(ColorNormal, FALSE);
3576                     curColor = ColorNormal;
3577                 }
3578                 if (started == STARTED_BOARD) {
3579                     started = STARTED_NONE;
3580                     parse[parse_pos] = NULLCHAR;
3581                     ParseBoard12(parse);
3582                     ics_user_moved = 0;
3583
3584                     /* Send premove here */
3585                     if (appData.premove) {
3586                       char str[MSG_SIZ];
3587                       if (currentMove == 0 &&
3588                           gameMode == IcsPlayingWhite &&
3589                           appData.premoveWhite) {
3590                         sprintf(str, "%s\n", appData.premoveWhiteText);
3591                         if (appData.debugMode)
3592                           fprintf(debugFP, "Sending premove:\n");
3593                         SendToICS(str);
3594                       } else if (currentMove == 1 &&
3595                                  gameMode == IcsPlayingBlack &&
3596                                  appData.premoveBlack) {
3597                         sprintf(str, "%s\n", appData.premoveBlackText);
3598                         if (appData.debugMode)
3599                           fprintf(debugFP, "Sending premove:\n");
3600                         SendToICS(str);
3601                       } else if (gotPremove) {
3602                         gotPremove = 0;
3603                         ClearPremoveHighlights();
3604                         if (appData.debugMode)
3605                           fprintf(debugFP, "Sending premove:\n");
3606                           UserMoveEvent(premoveFromX, premoveFromY, 
3607                                         premoveToX, premoveToY, 
3608                                         premovePromoChar);
3609                       }
3610                     }
3611
3612                     /* Usually suppress following prompt */
3613                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3614                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3615                         if (looking_at(buf, &i, "*% ")) {
3616                             savingComment = FALSE;
3617                             suppressKibitz = 0;
3618                         }
3619                     }
3620                     next_out = i;
3621                 } else if (started == STARTED_HOLDINGS) {
3622                     int gamenum;
3623                     char new_piece[MSG_SIZ];
3624                     started = STARTED_NONE;
3625                     parse[parse_pos] = NULLCHAR;
3626                     if (appData.debugMode)
3627                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3628                                                         parse, currentMove);
3629                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3630                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3631                         if (gameInfo.variant == VariantNormal) {
3632                           /* [HGM] We seem to switch variant during a game!
3633                            * Presumably no holdings were displayed, so we have
3634                            * to move the position two files to the right to
3635                            * create room for them!
3636                            */
3637                           VariantClass newVariant;
3638                           switch(gameInfo.boardWidth) { // base guess on board width
3639                                 case 9:  newVariant = VariantShogi; break;
3640                                 case 10: newVariant = VariantGreat; break;
3641                                 default: newVariant = VariantCrazyhouse; break;
3642                           }
3643                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3644                           /* Get a move list just to see the header, which
3645                              will tell us whether this is really bug or zh */
3646                           if (ics_getting_history == H_FALSE) {
3647                             ics_getting_history = H_REQUESTED;
3648                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3649                             SendToICS(str);
3650                           }
3651                         }
3652                         new_piece[0] = NULLCHAR;
3653                         sscanf(parse, "game %d white [%s black [%s <- %s",
3654                                &gamenum, white_holding, black_holding,
3655                                new_piece);
3656                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3657                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3658                         /* [HGM] copy holdings to board holdings area */
3659                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3660                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3661                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3662 #if ZIPPY
3663                         if (appData.zippyPlay && first.initDone) {
3664                             ZippyHoldings(white_holding, black_holding,
3665                                           new_piece);
3666                         }
3667 #endif /*ZIPPY*/
3668                         if (tinyLayout || smallLayout) {
3669                             char wh[16], bh[16];
3670                             PackHolding(wh, white_holding);
3671                             PackHolding(bh, black_holding);
3672                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3673                                     gameInfo.white, gameInfo.black);
3674                         } else {
3675                             sprintf(str, "%s [%s] vs. %s [%s]",
3676                                     gameInfo.white, white_holding,
3677                                     gameInfo.black, black_holding);
3678                         }
3679                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3680                         DrawPosition(FALSE, boards[currentMove]);
3681                         DisplayTitle(str);
3682                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3683                         sscanf(parse, "game %d white [%s black [%s <- %s",
3684                                &gamenum, white_holding, black_holding,
3685                                new_piece);
3686                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3687                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3688                         /* [HGM] copy holdings to partner-board holdings area */
3689                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3690                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3691                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3692                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3693                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3694                       }
3695                     }
3696                     /* Suppress following prompt */
3697                     if (looking_at(buf, &i, "*% ")) {
3698                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3699                         savingComment = FALSE;
3700                         suppressKibitz = 0;
3701                     }
3702                     next_out = i;
3703                 }
3704                 continue;
3705             }
3706
3707             i++;                /* skip unparsed character and loop back */
3708         }
3709         
3710         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3711 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3712 //          SendToPlayer(&buf[next_out], i - next_out);
3713             started != STARTED_HOLDINGS && leftover_start > next_out) {
3714             SendToPlayer(&buf[next_out], leftover_start - next_out);
3715             next_out = i;
3716         }
3717         
3718         leftover_len = buf_len - leftover_start;
3719         /* if buffer ends with something we couldn't parse,
3720            reparse it after appending the next read */
3721         
3722     } else if (count == 0) {
3723         RemoveInputSource(isr);
3724         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3725     } else {
3726         DisplayFatalError(_("Error reading from ICS"), error, 1);
3727     }
3728 }
3729
3730
3731 /* Board style 12 looks like this:
3732    
3733    <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
3734    
3735  * The "<12> " is stripped before it gets to this routine.  The two
3736  * trailing 0's (flip state and clock ticking) are later addition, and
3737  * some chess servers may not have them, or may have only the first.
3738  * Additional trailing fields may be added in the future.  
3739  */
3740
3741 #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"
3742
3743 #define RELATION_OBSERVING_PLAYED    0
3744 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3745 #define RELATION_PLAYING_MYMOVE      1
3746 #define RELATION_PLAYING_NOTMYMOVE  -1
3747 #define RELATION_EXAMINING           2
3748 #define RELATION_ISOLATED_BOARD     -3
3749 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3750
3751 void
3752 ParseBoard12(string)
3753      char *string;
3754
3755     GameMode newGameMode;
3756     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3757     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3758     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3759     char to_play, board_chars[200];
3760     char move_str[500], str[500], elapsed_time[500];
3761     char black[32], white[32];
3762     Board board;
3763     int prevMove = currentMove;
3764     int ticking = 2;
3765     ChessMove moveType;
3766     int fromX, fromY, toX, toY;
3767     char promoChar;
3768     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3769     char *bookHit = NULL; // [HGM] book
3770     Boolean weird = FALSE, reqFlag = FALSE;
3771
3772     fromX = fromY = toX = toY = -1;
3773     
3774     newGame = FALSE;
3775
3776     if (appData.debugMode)
3777       fprintf(debugFP, _("Parsing board: %s\n"), string);
3778
3779     move_str[0] = NULLCHAR;
3780     elapsed_time[0] = NULLCHAR;
3781     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3782         int  i = 0, j;
3783         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3784             if(string[i] == ' ') { ranks++; files = 0; }
3785             else files++;
3786             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3787             i++;
3788         }
3789         for(j = 0; j <i; j++) board_chars[j] = string[j];
3790         board_chars[i] = '\0';
3791         string += i + 1;
3792     }
3793     n = sscanf(string, PATTERN, &to_play, &double_push,
3794                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3795                &gamenum, white, black, &relation, &basetime, &increment,
3796                &white_stren, &black_stren, &white_time, &black_time,
3797                &moveNum, str, elapsed_time, move_str, &ics_flip,
3798                &ticking);
3799
3800     if (n < 21) {
3801         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3802         DisplayError(str, 0);
3803         return;
3804     }
3805
3806     /* Convert the move number to internal form */
3807     moveNum = (moveNum - 1) * 2;
3808     if (to_play == 'B') moveNum++;
3809     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3810       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3811                         0, 1);
3812       return;
3813     }
3814     
3815     switch (relation) {
3816       case RELATION_OBSERVING_PLAYED:
3817       case RELATION_OBSERVING_STATIC:
3818         if (gamenum == -1) {
3819             /* Old ICC buglet */
3820             relation = RELATION_OBSERVING_STATIC;
3821         }
3822         newGameMode = IcsObserving;
3823         break;
3824       case RELATION_PLAYING_MYMOVE:
3825       case RELATION_PLAYING_NOTMYMOVE:
3826         newGameMode =
3827           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3828             IcsPlayingWhite : IcsPlayingBlack;
3829         break;
3830       case RELATION_EXAMINING:
3831         newGameMode = IcsExamining;
3832         break;
3833       case RELATION_ISOLATED_BOARD:
3834       default:
3835         /* Just display this board.  If user was doing something else,
3836            we will forget about it until the next board comes. */ 
3837         newGameMode = IcsIdle;
3838         break;
3839       case RELATION_STARTING_POSITION:
3840         newGameMode = gameMode;
3841         break;
3842     }
3843     
3844     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3845          && newGameMode == IcsObserving && appData.bgObserve) {
3846       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3847       char *toSqr;
3848       for (k = 0; k < ranks; k++) {
3849         for (j = 0; j < files; j++)
3850           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3851         if(gameInfo.holdingsWidth > 1) {
3852              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3853              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3854         }
3855       }
3856       CopyBoard(partnerBoard, board);
3857       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3858         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3859         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3860       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3861       if(toSqr = strchr(str, '-')) {
3862         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3863         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3864       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3865       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3866       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3867       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3868       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3869       sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3870                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3871       DisplayMessage(partnerStatus, "");
3872         partnerBoardValid = TRUE;
3873       return;
3874     }
3875
3876     /* Modify behavior for initial board display on move listing
3877        of wild games.
3878        */
3879     switch (ics_getting_history) {
3880       case H_FALSE:
3881       case H_REQUESTED:
3882         break;
3883       case H_GOT_REQ_HEADER:
3884       case H_GOT_UNREQ_HEADER:
3885         /* This is the initial position of the current game */
3886         gamenum = ics_gamenum;
3887         moveNum = 0;            /* old ICS bug workaround */
3888         if (to_play == 'B') {
3889           startedFromSetupPosition = TRUE;
3890           blackPlaysFirst = TRUE;
3891           moveNum = 1;
3892           if (forwardMostMove == 0) forwardMostMove = 1;
3893           if (backwardMostMove == 0) backwardMostMove = 1;
3894           if (currentMove == 0) currentMove = 1;
3895         }
3896         newGameMode = gameMode;
3897         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3898         break;
3899       case H_GOT_UNWANTED_HEADER:
3900         /* This is an initial board that we don't want */
3901         return;
3902       case H_GETTING_MOVES:
3903         /* Should not happen */
3904         DisplayError(_("Error gathering move list: extra board"), 0);
3905         ics_getting_history = H_FALSE;
3906         return;
3907     }
3908
3909    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3910                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3911      /* [HGM] We seem to have switched variant unexpectedly
3912       * Try to guess new variant from board size
3913       */
3914           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3915           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3916           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3917           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3918           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3919           if(!weird) newVariant = VariantNormal;
3920           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3921           /* Get a move list just to see the header, which
3922              will tell us whether this is really bug or zh */
3923           if (ics_getting_history == H_FALSE) {
3924             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3925             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3926             SendToICS(str);
3927           }
3928     }
3929     
3930     /* Take action if this is the first board of a new game, or of a
3931        different game than is currently being displayed.  */
3932     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3933         relation == RELATION_ISOLATED_BOARD) {
3934         
3935         /* Forget the old game and get the history (if any) of the new one */
3936         if (gameMode != BeginningOfGame) {
3937           Reset(TRUE, TRUE);
3938         }
3939         newGame = TRUE;
3940         if (appData.autoRaiseBoard) BoardToTop();
3941         prevMove = -3;
3942         if (gamenum == -1) {
3943             newGameMode = IcsIdle;
3944         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3945                    appData.getMoveList && !reqFlag) {
3946             /* Need to get game history */
3947             ics_getting_history = H_REQUESTED;
3948             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3949             SendToICS(str);
3950         }
3951         
3952         /* Initially flip the board to have black on the bottom if playing
3953            black or if the ICS flip flag is set, but let the user change
3954            it with the Flip View button. */
3955         flipView = appData.autoFlipView ? 
3956           (newGameMode == IcsPlayingBlack) || ics_flip :
3957           appData.flipView;
3958         
3959         /* Done with values from previous mode; copy in new ones */
3960         gameMode = newGameMode;
3961         ModeHighlight();
3962         ics_gamenum = gamenum;
3963         if (gamenum == gs_gamenum) {
3964             int klen = strlen(gs_kind);
3965             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3966             sprintf(str, "ICS %s", gs_kind);
3967             gameInfo.event = StrSave(str);
3968         } else {
3969             gameInfo.event = StrSave("ICS game");
3970         }
3971         gameInfo.site = StrSave(appData.icsHost);
3972         gameInfo.date = PGNDate();
3973         gameInfo.round = StrSave("-");
3974         gameInfo.white = StrSave(white);
3975         gameInfo.black = StrSave(black);
3976         timeControl = basetime * 60 * 1000;
3977         timeControl_2 = 0;
3978         timeIncrement = increment * 1000;
3979         movesPerSession = 0;
3980         gameInfo.timeControl = TimeControlTagValue();
3981         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3982   if (appData.debugMode) {
3983     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3984     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3985     setbuf(debugFP, NULL);
3986   }
3987
3988         gameInfo.outOfBook = NULL;
3989         
3990         /* Do we have the ratings? */
3991         if (strcmp(player1Name, white) == 0 &&
3992             strcmp(player2Name, black) == 0) {
3993             if (appData.debugMode)
3994               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3995                       player1Rating, player2Rating);
3996             gameInfo.whiteRating = player1Rating;
3997             gameInfo.blackRating = player2Rating;
3998         } else if (strcmp(player2Name, white) == 0 &&
3999                    strcmp(player1Name, black) == 0) {
4000             if (appData.debugMode)
4001               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4002                       player2Rating, player1Rating);
4003             gameInfo.whiteRating = player2Rating;
4004             gameInfo.blackRating = player1Rating;
4005         }
4006         player1Name[0] = player2Name[0] = NULLCHAR;
4007
4008         /* Silence shouts if requested */
4009         if (appData.quietPlay &&
4010             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4011             SendToICS(ics_prefix);
4012             SendToICS("set shout 0\n");
4013         }
4014     }
4015     
4016     /* Deal with midgame name changes */
4017     if (!newGame) {
4018         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4019             if (gameInfo.white) free(gameInfo.white);
4020             gameInfo.white = StrSave(white);
4021         }
4022         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4023             if (gameInfo.black) free(gameInfo.black);
4024             gameInfo.black = StrSave(black);
4025         }
4026     }
4027     
4028     /* Throw away game result if anything actually changes in examine mode */
4029     if (gameMode == IcsExamining && !newGame) {
4030         gameInfo.result = GameUnfinished;
4031         if (gameInfo.resultDetails != NULL) {
4032             free(gameInfo.resultDetails);
4033             gameInfo.resultDetails = NULL;
4034         }
4035     }
4036     
4037     /* In pausing && IcsExamining mode, we ignore boards coming
4038        in if they are in a different variation than we are. */
4039     if (pauseExamInvalid) return;
4040     if (pausing && gameMode == IcsExamining) {
4041         if (moveNum <= pauseExamForwardMostMove) {
4042             pauseExamInvalid = TRUE;
4043             forwardMostMove = pauseExamForwardMostMove;
4044             return;
4045         }
4046     }
4047     
4048   if (appData.debugMode) {
4049     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4050   }
4051     /* Parse the board */
4052     for (k = 0; k < ranks; k++) {
4053       for (j = 0; j < files; j++)
4054         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4055       if(gameInfo.holdingsWidth > 1) {
4056            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4057            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4058       }
4059     }
4060     CopyBoard(boards[moveNum], board);
4061     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4062     if (moveNum == 0) {
4063         startedFromSetupPosition =
4064           !CompareBoards(board, initialPosition);
4065         if(startedFromSetupPosition)
4066             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4067     }
4068
4069     /* [HGM] Set castling rights. Take the outermost Rooks,
4070        to make it also work for FRC opening positions. Note that board12
4071        is really defective for later FRC positions, as it has no way to
4072        indicate which Rook can castle if they are on the same side of King.
4073        For the initial position we grant rights to the outermost Rooks,
4074        and remember thos rights, and we then copy them on positions
4075        later in an FRC game. This means WB might not recognize castlings with
4076        Rooks that have moved back to their original position as illegal,
4077        but in ICS mode that is not its job anyway.
4078     */
4079     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4080     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4081
4082         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4083             if(board[0][i] == WhiteRook) j = i;
4084         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4085         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4086             if(board[0][i] == WhiteRook) j = i;
4087         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4088         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4089             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4090         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4091         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4092             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4093         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4094
4095         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4096         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4097             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4098         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4099             if(board[BOARD_HEIGHT-1][k] == bKing)
4100                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4101         if(gameInfo.variant == VariantTwoKings) {
4102             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4103             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4104             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4105         }
4106     } else { int r;
4107         r = boards[moveNum][CASTLING][0] = initialRights[0];
4108         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4109         r = boards[moveNum][CASTLING][1] = initialRights[1];
4110         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4111         r = boards[moveNum][CASTLING][3] = initialRights[3];
4112         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4113         r = boards[moveNum][CASTLING][4] = initialRights[4];
4114         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4115         /* wildcastle kludge: always assume King has rights */
4116         r = boards[moveNum][CASTLING][2] = initialRights[2];
4117         r = boards[moveNum][CASTLING][5] = initialRights[5];
4118     }
4119     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4120     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4121
4122     
4123     if (ics_getting_history == H_GOT_REQ_HEADER ||
4124         ics_getting_history == H_GOT_UNREQ_HEADER) {
4125         /* This was an initial position from a move list, not
4126            the current position */
4127         return;
4128     }
4129     
4130     /* Update currentMove and known move number limits */
4131     newMove = newGame || moveNum > forwardMostMove;
4132
4133     if (newGame) {
4134         forwardMostMove = backwardMostMove = currentMove = moveNum;
4135         if (gameMode == IcsExamining && moveNum == 0) {
4136           /* Workaround for ICS limitation: we are not told the wild
4137              type when starting to examine a game.  But if we ask for
4138              the move list, the move list header will tell us */
4139             ics_getting_history = H_REQUESTED;
4140             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4141             SendToICS(str);
4142         }
4143     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4144                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4145 #if ZIPPY
4146         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4147         /* [HGM] applied this also to an engine that is silently watching        */
4148         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4149             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4150             gameInfo.variant == currentlyInitializedVariant) {
4151           takeback = forwardMostMove - moveNum;
4152           for (i = 0; i < takeback; i++) {
4153             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4154             SendToProgram("undo\n", &first);
4155           }
4156         }
4157 #endif
4158
4159         forwardMostMove = moveNum;
4160         if (!pausing || currentMove > forwardMostMove)
4161           currentMove = forwardMostMove;
4162     } else {
4163         /* New part of history that is not contiguous with old part */ 
4164         if (pausing && gameMode == IcsExamining) {
4165             pauseExamInvalid = TRUE;
4166             forwardMostMove = pauseExamForwardMostMove;
4167             return;
4168         }
4169         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4170 #if ZIPPY
4171             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4172                 // [HGM] when we will receive the move list we now request, it will be
4173                 // fed to the engine from the first move on. So if the engine is not
4174                 // in the initial position now, bring it there.
4175                 InitChessProgram(&first, 0);
4176             }
4177 #endif
4178             ics_getting_history = H_REQUESTED;
4179             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4180             SendToICS(str);
4181         }
4182         forwardMostMove = backwardMostMove = currentMove = moveNum;
4183     }
4184     
4185     /* Update the clocks */
4186     if (strchr(elapsed_time, '.')) {
4187       /* Time is in ms */
4188       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4189       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4190     } else {
4191       /* Time is in seconds */
4192       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4193       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4194     }
4195       
4196
4197 #if ZIPPY
4198     if (appData.zippyPlay && newGame &&
4199         gameMode != IcsObserving && gameMode != IcsIdle &&
4200         gameMode != IcsExamining)
4201       ZippyFirstBoard(moveNum, basetime, increment);
4202 #endif
4203     
4204     /* Put the move on the move list, first converting
4205        to canonical algebraic form. */
4206     if (moveNum > 0) {
4207   if (appData.debugMode) {
4208     if (appData.debugMode) { int f = forwardMostMove;
4209         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4210                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4211                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4212     }
4213     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4214     fprintf(debugFP, "moveNum = %d\n", moveNum);
4215     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4216     setbuf(debugFP, NULL);
4217   }
4218         if (moveNum <= backwardMostMove) {
4219             /* We don't know what the board looked like before
4220                this move.  Punt. */
4221             strcpy(parseList[moveNum - 1], move_str);
4222             strcat(parseList[moveNum - 1], " ");
4223             strcat(parseList[moveNum - 1], elapsed_time);
4224             moveList[moveNum - 1][0] = NULLCHAR;
4225         } else if (strcmp(move_str, "none") == 0) {
4226             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4227             /* Again, we don't know what the board looked like;
4228                this is really the start of the game. */
4229             parseList[moveNum - 1][0] = NULLCHAR;
4230             moveList[moveNum - 1][0] = NULLCHAR;
4231             backwardMostMove = moveNum;
4232             startedFromSetupPosition = TRUE;
4233             fromX = fromY = toX = toY = -1;
4234         } else {
4235           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4236           //                 So we parse the long-algebraic move string in stead of the SAN move
4237           int valid; char buf[MSG_SIZ], *prom;
4238
4239           // str looks something like "Q/a1-a2"; kill the slash
4240           if(str[1] == '/') 
4241                 sprintf(buf, "%c%s", str[0], str+2);
4242           else  strcpy(buf, str); // might be castling
4243           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4244                 strcat(buf, prom); // long move lacks promo specification!
4245           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4246                 if(appData.debugMode) 
4247                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4248                 strcpy(move_str, buf);
4249           }
4250           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4251                                 &fromX, &fromY, &toX, &toY, &promoChar)
4252                || ParseOneMove(buf, moveNum - 1, &moveType,
4253                                 &fromX, &fromY, &toX, &toY, &promoChar);
4254           // end of long SAN patch
4255           if (valid) {
4256             (void) CoordsToAlgebraic(boards[moveNum - 1],
4257                                      PosFlags(moveNum - 1),
4258                                      fromY, fromX, toY, toX, promoChar,
4259                                      parseList[moveNum-1]);
4260             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4261               case MT_NONE:
4262               case MT_STALEMATE:
4263               default:
4264                 break;
4265               case MT_CHECK:
4266                 if(gameInfo.variant != VariantShogi)
4267                     strcat(parseList[moveNum - 1], "+");
4268                 break;
4269               case MT_CHECKMATE:
4270               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4271                 strcat(parseList[moveNum - 1], "#");
4272                 break;
4273             }
4274             strcat(parseList[moveNum - 1], " ");
4275             strcat(parseList[moveNum - 1], elapsed_time);
4276             /* currentMoveString is set as a side-effect of ParseOneMove */
4277             strcpy(moveList[moveNum - 1], currentMoveString);
4278             strcat(moveList[moveNum - 1], "\n");
4279           } else {
4280             /* Move from ICS was illegal!?  Punt. */
4281   if (appData.debugMode) {
4282     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4283     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4284   }
4285             strcpy(parseList[moveNum - 1], move_str);
4286             strcat(parseList[moveNum - 1], " ");
4287             strcat(parseList[moveNum - 1], elapsed_time);
4288             moveList[moveNum - 1][0] = NULLCHAR;
4289             fromX = fromY = toX = toY = -1;
4290           }
4291         }
4292   if (appData.debugMode) {
4293     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4294     setbuf(debugFP, NULL);
4295   }
4296
4297 #if ZIPPY
4298         /* Send move to chess program (BEFORE animating it). */
4299         if (appData.zippyPlay && !newGame && newMove && 
4300            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4301
4302             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4303                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4304                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4305                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4306                             move_str);
4307                     DisplayError(str, 0);
4308                 } else {
4309                     if (first.sendTime) {
4310                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4311                     }
4312                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4313                     if (firstMove && !bookHit) {
4314                         firstMove = FALSE;
4315                         if (first.useColors) {
4316                           SendToProgram(gameMode == IcsPlayingWhite ?
4317                                         "white\ngo\n" :
4318                                         "black\ngo\n", &first);
4319                         } else {
4320                           SendToProgram("go\n", &first);
4321                         }
4322                         first.maybeThinking = TRUE;
4323                     }
4324                 }
4325             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4326               if (moveList[moveNum - 1][0] == NULLCHAR) {
4327                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4328                 DisplayError(str, 0);
4329               } else {
4330                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4331                 SendMoveToProgram(moveNum - 1, &first);
4332               }
4333             }
4334         }
4335 #endif
4336     }
4337
4338     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4339         /* If move comes from a remote source, animate it.  If it
4340            isn't remote, it will have already been animated. */
4341         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4342             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4343         }
4344         if (!pausing && appData.highlightLastMove) {
4345             SetHighlights(fromX, fromY, toX, toY);
4346         }
4347     }
4348     
4349     /* Start the clocks */
4350     whiteFlag = blackFlag = FALSE;
4351     appData.clockMode = !(basetime == 0 && increment == 0);
4352     if (ticking == 0) {
4353       ics_clock_paused = TRUE;
4354       StopClocks();
4355     } else if (ticking == 1) {
4356       ics_clock_paused = FALSE;
4357     }
4358     if (gameMode == IcsIdle ||
4359         relation == RELATION_OBSERVING_STATIC ||
4360         relation == RELATION_EXAMINING ||
4361         ics_clock_paused)
4362       DisplayBothClocks();
4363     else
4364       StartClocks();
4365     
4366     /* Display opponents and material strengths */
4367     if (gameInfo.variant != VariantBughouse &&
4368         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4369         if (tinyLayout || smallLayout) {
4370             if(gameInfo.variant == VariantNormal)
4371                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4372                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4373                     basetime, increment);
4374             else
4375                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4376                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4377                     basetime, increment, (int) gameInfo.variant);
4378         } else {
4379             if(gameInfo.variant == VariantNormal)
4380                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4381                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4382                     basetime, increment);
4383             else
4384                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4385                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4386                     basetime, increment, VariantName(gameInfo.variant));
4387         }
4388         DisplayTitle(str);
4389   if (appData.debugMode) {
4390     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4391   }
4392     }
4393
4394
4395     /* Display the board */
4396     if (!pausing && !appData.noGUI) {
4397       
4398       if (appData.premove)
4399           if (!gotPremove || 
4400              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4401              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4402               ClearPremoveHighlights();
4403
4404       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4405         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4406       DrawPosition(j, boards[currentMove]);
4407
4408       DisplayMove(moveNum - 1);
4409       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4410             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4411               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4412         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4413       }
4414     }
4415
4416     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4417 #if ZIPPY
4418     if(bookHit) { // [HGM] book: simulate book reply
4419         static char bookMove[MSG_SIZ]; // a bit generous?
4420
4421         programStats.nodes = programStats.depth = programStats.time = 
4422         programStats.score = programStats.got_only_move = 0;
4423         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4424
4425         strcpy(bookMove, "move ");
4426         strcat(bookMove, bookHit);
4427         HandleMachineMove(bookMove, &first);
4428     }
4429 #endif
4430 }
4431
4432 void
4433 GetMoveListEvent()
4434 {
4435     char buf[MSG_SIZ];
4436     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4437         ics_getting_history = H_REQUESTED;
4438         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4439         SendToICS(buf);
4440     }
4441 }
4442
4443 void
4444 AnalysisPeriodicEvent(force)
4445      int force;
4446 {
4447     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4448          && !force) || !appData.periodicUpdates)
4449       return;
4450
4451     /* Send . command to Crafty to collect stats */
4452     SendToProgram(".\n", &first);
4453
4454     /* Don't send another until we get a response (this makes
4455        us stop sending to old Crafty's which don't understand
4456        the "." command (sending illegal cmds resets node count & time,
4457        which looks bad)) */
4458     programStats.ok_to_send = 0;
4459 }
4460
4461 void ics_update_width(new_width)
4462         int new_width;
4463 {
4464         ics_printf("set width %d\n", new_width);
4465 }
4466
4467 void
4468 SendMoveToProgram(moveNum, cps)
4469      int moveNum;
4470      ChessProgramState *cps;
4471 {
4472     char buf[MSG_SIZ];
4473
4474     if (cps->useUsermove) {
4475       SendToProgram("usermove ", cps);
4476     }
4477     if (cps->useSAN) {
4478       char *space;
4479       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4480         int len = space - parseList[moveNum];
4481         memcpy(buf, parseList[moveNum], len);
4482         buf[len++] = '\n';
4483         buf[len] = NULLCHAR;
4484       } else {
4485         sprintf(buf, "%s\n", parseList[moveNum]);
4486       }
4487       SendToProgram(buf, cps);
4488     } else {
4489       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4490         AlphaRank(moveList[moveNum], 4);
4491         SendToProgram(moveList[moveNum], cps);
4492         AlphaRank(moveList[moveNum], 4); // and back
4493       } else
4494       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4495        * the engine. It would be nice to have a better way to identify castle 
4496        * moves here. */
4497       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4498                                                                          && cps->useOOCastle) {
4499         int fromX = moveList[moveNum][0] - AAA; 
4500         int fromY = moveList[moveNum][1] - ONE;
4501         int toX = moveList[moveNum][2] - AAA; 
4502         int toY = moveList[moveNum][3] - ONE;
4503         if((boards[moveNum][fromY][fromX] == WhiteKing 
4504             && boards[moveNum][toY][toX] == WhiteRook)
4505            || (boards[moveNum][fromY][fromX] == BlackKing 
4506                && boards[moveNum][toY][toX] == BlackRook)) {
4507           if(toX > fromX) SendToProgram("O-O\n", cps);
4508           else SendToProgram("O-O-O\n", cps);
4509         }
4510         else SendToProgram(moveList[moveNum], cps);
4511       }
4512       else SendToProgram(moveList[moveNum], cps);
4513       /* End of additions by Tord */
4514     }
4515
4516     /* [HGM] setting up the opening has brought engine in force mode! */
4517     /*       Send 'go' if we are in a mode where machine should play. */
4518     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4519         (gameMode == TwoMachinesPlay   ||
4520 #ifdef ZIPPY
4521          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4522 #endif
4523          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4524         SendToProgram("go\n", cps);
4525   if (appData.debugMode) {
4526     fprintf(debugFP, "(extra)\n");
4527   }
4528     }
4529     setboardSpoiledMachineBlack = 0;
4530 }
4531
4532 void
4533 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4534      ChessMove moveType;
4535      int fromX, fromY, toX, toY;
4536 {
4537     char user_move[MSG_SIZ];
4538
4539     switch (moveType) {
4540       default:
4541         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4542                 (int)moveType, fromX, fromY, toX, toY);
4543         DisplayError(user_move + strlen("say "), 0);
4544         break;
4545       case WhiteKingSideCastle:
4546       case BlackKingSideCastle:
4547       case WhiteQueenSideCastleWild:
4548       case BlackQueenSideCastleWild:
4549       /* PUSH Fabien */
4550       case WhiteHSideCastleFR:
4551       case BlackHSideCastleFR:
4552       /* POP Fabien */
4553         sprintf(user_move, "o-o\n");
4554         break;
4555       case WhiteQueenSideCastle:
4556       case BlackQueenSideCastle:
4557       case WhiteKingSideCastleWild:
4558       case BlackKingSideCastleWild:
4559       /* PUSH Fabien */
4560       case WhiteASideCastleFR:
4561       case BlackASideCastleFR:
4562       /* POP Fabien */
4563         sprintf(user_move, "o-o-o\n");
4564         break;
4565       case WhitePromotionQueen:
4566       case BlackPromotionQueen:
4567       case WhitePromotionRook:
4568       case BlackPromotionRook:
4569       case WhitePromotionBishop:
4570       case BlackPromotionBishop:
4571       case WhitePromotionKnight:
4572       case BlackPromotionKnight:
4573       case WhitePromotionKing:
4574       case BlackPromotionKing:
4575       case WhitePromotionChancellor:
4576       case BlackPromotionChancellor:
4577       case WhitePromotionArchbishop:
4578       case BlackPromotionArchbishop:
4579         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4580             sprintf(user_move, "%c%c%c%c=%c\n",
4581                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4582                 PieceToChar(WhiteFerz));
4583         else if(gameInfo.variant == VariantGreat)
4584             sprintf(user_move, "%c%c%c%c=%c\n",
4585                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4586                 PieceToChar(WhiteMan));
4587         else
4588             sprintf(user_move, "%c%c%c%c=%c\n",
4589                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4590                 PieceToChar(PromoPiece(moveType)));
4591         break;
4592       case WhiteDrop:
4593       case BlackDrop:
4594         sprintf(user_move, "%c@%c%c\n",
4595                 ToUpper(PieceToChar((ChessSquare) fromX)),
4596                 AAA + toX, ONE + toY);
4597         break;
4598       case NormalMove:
4599       case WhiteCapturesEnPassant:
4600       case BlackCapturesEnPassant:
4601       case IllegalMove:  /* could be a variant we don't quite understand */
4602         sprintf(user_move, "%c%c%c%c\n",
4603                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4604         break;
4605     }
4606     SendToICS(user_move);
4607     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4608         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4609 }
4610
4611 void
4612 UploadGameEvent()
4613 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4614     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4615     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4616     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4617         DisplayError("You cannot do this while you are playing or observing", 0);
4618         return;
4619     }
4620     if(gameMode != IcsExamining) { // is this ever not the case?
4621         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4622
4623         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4624             sprintf(command, "match %s", ics_handle);
4625         } else { // on FICS we must first go to general examine mode
4626             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4627         }
4628         if(gameInfo.variant != VariantNormal) {
4629             // try figure out wild number, as xboard names are not always valid on ICS
4630             for(i=1; i<=36; i++) {
4631                 sprintf(buf, "wild/%d", i);
4632                 if(StringToVariant(buf) == gameInfo.variant) break;
4633             }
4634             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4635             else if(i == 22) sprintf(buf, "%s fr\n", command);
4636             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4637         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4638         SendToICS(ics_prefix);
4639         SendToICS(buf);
4640         if(startedFromSetupPosition || backwardMostMove != 0) {
4641           fen = PositionToFEN(backwardMostMove, NULL);
4642           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4643             sprintf(buf, "loadfen %s\n", fen);
4644             SendToICS(buf);
4645           } else { // FICS: everything has to set by separate bsetup commands
4646             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4647             sprintf(buf, "bsetup fen %s\n", fen);
4648             SendToICS(buf);
4649             if(!WhiteOnMove(backwardMostMove)) {
4650                 SendToICS("bsetup tomove black\n");
4651             }
4652             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4653             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4654             SendToICS(buf);
4655             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4656             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4657             SendToICS(buf);
4658             i = boards[backwardMostMove][EP_STATUS];
4659             if(i >= 0) { // set e.p.
4660                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4661                 SendToICS(buf);
4662             }
4663             bsetup++;
4664           }
4665         }
4666       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4667             SendToICS("bsetup done\n"); // switch to normal examining.
4668     }
4669     for(i = backwardMostMove; i<last; i++) {
4670         char buf[20];
4671         sprintf(buf, "%s\n", parseList[i]);
4672         SendToICS(buf);
4673     }
4674     SendToICS(ics_prefix);
4675     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4676 }
4677
4678 void
4679 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4680      int rf, ff, rt, ft;
4681      char promoChar;
4682      char move[7];
4683 {
4684     if (rf == DROP_RANK) {
4685         sprintf(move, "%c@%c%c\n",
4686                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4687     } else {
4688         if (promoChar == 'x' || promoChar == NULLCHAR) {
4689             sprintf(move, "%c%c%c%c\n",
4690                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4691         } else {
4692             sprintf(move, "%c%c%c%c%c\n",
4693                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4694         }
4695     }
4696 }
4697
4698 void
4699 ProcessICSInitScript(f)
4700      FILE *f;
4701 {
4702     char buf[MSG_SIZ];
4703
4704     while (fgets(buf, MSG_SIZ, f)) {
4705         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4706     }
4707
4708     fclose(f);
4709 }
4710
4711
4712 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4713 void
4714 AlphaRank(char *move, int n)
4715 {
4716 //    char *p = move, c; int x, y;
4717
4718     if (appData.debugMode) {
4719         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4720     }
4721
4722     if(move[1]=='*' && 
4723        move[2]>='0' && move[2]<='9' &&
4724        move[3]>='a' && move[3]<='x'    ) {
4725         move[1] = '@';
4726         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4727         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4728     } else
4729     if(move[0]>='0' && move[0]<='9' &&
4730        move[1]>='a' && move[1]<='x' &&
4731        move[2]>='0' && move[2]<='9' &&
4732        move[3]>='a' && move[3]<='x'    ) {
4733         /* input move, Shogi -> normal */
4734         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4735         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4736         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4737         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4738     } else
4739     if(move[1]=='@' &&
4740        move[3]>='0' && move[3]<='9' &&
4741        move[2]>='a' && move[2]<='x'    ) {
4742         move[1] = '*';
4743         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4744         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4745     } else
4746     if(
4747        move[0]>='a' && move[0]<='x' &&
4748        move[3]>='0' && move[3]<='9' &&
4749        move[2]>='a' && move[2]<='x'    ) {
4750          /* output move, normal -> Shogi */
4751         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4752         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4753         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4754         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4755         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4756     }
4757     if (appData.debugMode) {
4758         fprintf(debugFP, "   out = '%s'\n", move);
4759     }
4760 }
4761
4762 char yy_textstr[8000];
4763
4764 /* Parser for moves from gnuchess, ICS, or user typein box */
4765 Boolean
4766 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4767      char *move;
4768      int moveNum;
4769      ChessMove *moveType;
4770      int *fromX, *fromY, *toX, *toY;
4771      char *promoChar;
4772 {       
4773     if (appData.debugMode) {
4774         fprintf(debugFP, "move to parse: %s\n", move);
4775     }
4776     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4777
4778     switch (*moveType) {
4779       case WhitePromotionChancellor:
4780       case BlackPromotionChancellor:
4781       case WhitePromotionArchbishop:
4782       case BlackPromotionArchbishop:
4783       case WhitePromotionQueen:
4784       case BlackPromotionQueen:
4785       case WhitePromotionRook:
4786       case BlackPromotionRook:
4787       case WhitePromotionBishop:
4788       case BlackPromotionBishop:
4789       case WhitePromotionKnight:
4790       case BlackPromotionKnight:
4791       case WhitePromotionKing:
4792       case BlackPromotionKing:
4793       case NormalMove:
4794       case WhiteCapturesEnPassant:
4795       case BlackCapturesEnPassant:
4796       case WhiteKingSideCastle:
4797       case WhiteQueenSideCastle:
4798       case BlackKingSideCastle:
4799       case BlackQueenSideCastle:
4800       case WhiteKingSideCastleWild:
4801       case WhiteQueenSideCastleWild:
4802       case BlackKingSideCastleWild:
4803       case BlackQueenSideCastleWild:
4804       /* Code added by Tord: */
4805       case WhiteHSideCastleFR:
4806       case WhiteASideCastleFR:
4807       case BlackHSideCastleFR:
4808       case BlackASideCastleFR:
4809       /* End of code added by Tord */
4810       case IllegalMove:         /* bug or odd chess variant */
4811         *fromX = currentMoveString[0] - AAA;
4812         *fromY = currentMoveString[1] - ONE;
4813         *toX = currentMoveString[2] - AAA;
4814         *toY = currentMoveString[3] - ONE;
4815         *promoChar = currentMoveString[4];
4816         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4817             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4818     if (appData.debugMode) {
4819         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4820     }
4821             *fromX = *fromY = *toX = *toY = 0;
4822             return FALSE;
4823         }
4824         if (appData.testLegality) {
4825           return (*moveType != IllegalMove);
4826         } else {
4827           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4828                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4829         }
4830
4831       case WhiteDrop:
4832       case BlackDrop:
4833         *fromX = *moveType == WhiteDrop ?
4834           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4835           (int) CharToPiece(ToLower(currentMoveString[0]));
4836         *fromY = DROP_RANK;
4837         *toX = currentMoveString[2] - AAA;
4838         *toY = currentMoveString[3] - ONE;
4839         *promoChar = NULLCHAR;
4840         return TRUE;
4841
4842       case AmbiguousMove:
4843       case ImpossibleMove:
4844       case (ChessMove) 0:       /* end of file */
4845       case ElapsedTime:
4846       case Comment:
4847       case PGNTag:
4848       case NAG:
4849       case WhiteWins:
4850       case BlackWins:
4851       case GameIsDrawn:
4852       default:
4853     if (appData.debugMode) {
4854         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4855     }
4856         /* bug? */
4857         *fromX = *fromY = *toX = *toY = 0;
4858         *promoChar = NULLCHAR;
4859         return FALSE;
4860     }
4861 }
4862
4863
4864 void
4865 ParsePV(char *pv, Boolean storeComments)
4866 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4867   int fromX, fromY, toX, toY; char promoChar;
4868   ChessMove moveType;
4869   Boolean valid;
4870   int nr = 0;
4871
4872   endPV = forwardMostMove;
4873   do {
4874     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4875     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4876     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4877 if(appData.debugMode){
4878 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);
4879 }
4880     if(!valid && nr == 0 &&
4881        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4882         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4883         // Hande case where played move is different from leading PV move
4884         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4885         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4886         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4887         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4888           endPV += 2; // if position different, keep this
4889           moveList[endPV-1][0] = fromX + AAA;
4890           moveList[endPV-1][1] = fromY + ONE;
4891           moveList[endPV-1][2] = toX + AAA;
4892           moveList[endPV-1][3] = toY + ONE;
4893           parseList[endPV-1][0] = NULLCHAR;
4894           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4895         }
4896       }
4897     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4898     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4899     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4900     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4901         valid++; // allow comments in PV
4902         continue;
4903     }
4904     nr++;
4905     if(endPV+1 > framePtr) break; // no space, truncate
4906     if(!valid) break;
4907     endPV++;
4908     CopyBoard(boards[endPV], boards[endPV-1]);
4909     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4910     moveList[endPV-1][0] = fromX + AAA;
4911     moveList[endPV-1][1] = fromY + ONE;
4912     moveList[endPV-1][2] = toX + AAA;
4913     moveList[endPV-1][3] = toY + ONE;
4914     if(storeComments)
4915         CoordsToAlgebraic(boards[endPV - 1],
4916                              PosFlags(endPV - 1),
4917                              fromY, fromX, toY, toX, promoChar,
4918                              parseList[endPV - 1]);
4919     else
4920         parseList[endPV-1][0] = NULLCHAR;
4921   } while(valid);
4922   currentMove = endPV;
4923   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4924   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4925                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4926   DrawPosition(TRUE, boards[currentMove]);
4927 }
4928
4929 static int lastX, lastY;
4930
4931 Boolean
4932 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4933 {
4934         int startPV;
4935         char *p;
4936
4937         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4938         lastX = x; lastY = y;
4939         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4940         startPV = index;
4941         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4942         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4943         index = startPV;
4944         do{ while(buf[index] && buf[index] != '\n') index++;
4945         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4946         buf[index] = 0;
4947         ParsePV(buf+startPV, FALSE);
4948         *start = startPV; *end = index-1;
4949         return TRUE;
4950 }
4951
4952 Boolean
4953 LoadPV(int x, int y)
4954 { // called on right mouse click to load PV
4955   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4956   lastX = x; lastY = y;
4957   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4958   return TRUE;
4959 }
4960
4961 void
4962 UnLoadPV()
4963 {
4964   if(endPV < 0) return;
4965   endPV = -1;
4966   currentMove = forwardMostMove;
4967   ClearPremoveHighlights();
4968   DrawPosition(TRUE, boards[currentMove]);
4969 }
4970
4971 void
4972 MovePV(int x, int y, int h)
4973 { // step through PV based on mouse coordinates (called on mouse move)
4974   int margin = h>>3, step = 0;
4975
4976   if(endPV < 0) return;
4977   // we must somehow check if right button is still down (might be released off board!)
4978   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4979   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4980   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4981   if(!step) return;
4982   lastX = x; lastY = y;
4983   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4984   currentMove += step;
4985   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4986   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4987                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4988   DrawPosition(FALSE, boards[currentMove]);
4989 }
4990
4991
4992 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4993 // All positions will have equal probability, but the current method will not provide a unique
4994 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4995 #define DARK 1
4996 #define LITE 2
4997 #define ANY 3
4998
4999 int squaresLeft[4];
5000 int piecesLeft[(int)BlackPawn];
5001 int seed, nrOfShuffles;
5002
5003 void GetPositionNumber()
5004 {       // sets global variable seed
5005         int i;
5006
5007         seed = appData.defaultFrcPosition;
5008         if(seed < 0) { // randomize based on time for negative FRC position numbers
5009                 for(i=0; i<50; i++) seed += random();
5010                 seed = random() ^ random() >> 8 ^ random() << 8;
5011                 if(seed<0) seed = -seed;
5012         }
5013 }
5014
5015 int put(Board board, int pieceType, int rank, int n, int shade)
5016 // put the piece on the (n-1)-th empty squares of the given shade
5017 {
5018         int i;
5019
5020         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5021                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5022                         board[rank][i] = (ChessSquare) pieceType;
5023                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5024                         squaresLeft[ANY]--;
5025                         piecesLeft[pieceType]--; 
5026                         return i;
5027                 }
5028         }
5029         return -1;
5030 }
5031
5032
5033 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5034 // calculate where the next piece goes, (any empty square), and put it there
5035 {
5036         int i;
5037
5038         i = seed % squaresLeft[shade];
5039         nrOfShuffles *= squaresLeft[shade];
5040         seed /= squaresLeft[shade];
5041         put(board, pieceType, rank, i, shade);
5042 }
5043
5044 void AddTwoPieces(Board board, int pieceType, int rank)
5045 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5046 {
5047         int i, n=squaresLeft[ANY], j=n-1, k;
5048
5049         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5050         i = seed % k;  // pick one
5051         nrOfShuffles *= k;
5052         seed /= k;
5053         while(i >= j) i -= j--;
5054         j = n - 1 - j; i += j;
5055         put(board, pieceType, rank, j, ANY);
5056         put(board, pieceType, rank, i, ANY);
5057 }
5058
5059 void SetUpShuffle(Board board, int number)
5060 {
5061         int i, p, first=1;
5062
5063         GetPositionNumber(); nrOfShuffles = 1;
5064
5065         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5066         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5067         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5068
5069         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5070
5071         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5072             p = (int) board[0][i];
5073             if(p < (int) BlackPawn) piecesLeft[p] ++;
5074             board[0][i] = EmptySquare;
5075         }
5076
5077         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5078             // shuffles restricted to allow normal castling put KRR first
5079             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5080                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5081             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5082                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5083             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5084                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5085             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5086                 put(board, WhiteRook, 0, 0, ANY);
5087             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5088         }
5089
5090         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5091             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5092             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5093                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5094                 while(piecesLeft[p] >= 2) {
5095                     AddOnePiece(board, p, 0, LITE);
5096                     AddOnePiece(board, p, 0, DARK);
5097                 }
5098                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5099             }
5100
5101         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5102             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5103             // but we leave King and Rooks for last, to possibly obey FRC restriction
5104             if(p == (int)WhiteRook) continue;
5105             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5106             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5107         }
5108
5109         // now everything is placed, except perhaps King (Unicorn) and Rooks
5110
5111         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5112             // Last King gets castling rights
5113             while(piecesLeft[(int)WhiteUnicorn]) {
5114                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5115                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5116             }
5117
5118             while(piecesLeft[(int)WhiteKing]) {
5119                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5120                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5121             }
5122
5123
5124         } else {
5125             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5126             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5127         }
5128
5129         // Only Rooks can be left; simply place them all
5130         while(piecesLeft[(int)WhiteRook]) {
5131                 i = put(board, WhiteRook, 0, 0, ANY);
5132                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5133                         if(first) {
5134                                 first=0;
5135                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5136                         }
5137                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5138                 }
5139         }
5140         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5141             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5142         }
5143
5144         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5145 }
5146
5147 int SetCharTable( char *table, const char * map )
5148 /* [HGM] moved here from winboard.c because of its general usefulness */
5149 /*       Basically a safe strcpy that uses the last character as King */
5150 {
5151     int result = FALSE; int NrPieces;
5152
5153     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5154                     && NrPieces >= 12 && !(NrPieces&1)) {
5155         int i; /* [HGM] Accept even length from 12 to 34 */
5156
5157         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5158         for( i=0; i<NrPieces/2-1; i++ ) {
5159             table[i] = map[i];
5160             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5161         }
5162         table[(int) WhiteKing]  = map[NrPieces/2-1];
5163         table[(int) BlackKing]  = map[NrPieces-1];
5164
5165         result = TRUE;
5166     }
5167
5168     return result;
5169 }
5170
5171 void Prelude(Board board)
5172 {       // [HGM] superchess: random selection of exo-pieces
5173         int i, j, k; ChessSquare p; 
5174         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5175
5176         GetPositionNumber(); // use FRC position number
5177
5178         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5179             SetCharTable(pieceToChar, appData.pieceToCharTable);
5180             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5181                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5182         }
5183
5184         j = seed%4;                 seed /= 4; 
5185         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5186         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5187         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5188         j = seed%3 + (seed%3 >= j); seed /= 3; 
5189         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5190         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5191         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5192         j = seed%3;                 seed /= 3; 
5193         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5194         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5195         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5196         j = seed%2 + (seed%2 >= j); seed /= 2; 
5197         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5198         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5199         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5200         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5201         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5202         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5203         put(board, exoPieces[0],    0, 0, ANY);
5204         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5205 }
5206
5207 void
5208 InitPosition(redraw)
5209      int redraw;
5210 {
5211     ChessSquare (* pieces)[BOARD_FILES];
5212     int i, j, pawnRow, overrule,
5213     oldx = gameInfo.boardWidth,
5214     oldy = gameInfo.boardHeight,
5215     oldh = gameInfo.holdingsWidth,
5216     oldv = gameInfo.variant;
5217
5218     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5219
5220     /* [AS] Initialize pv info list [HGM] and game status */
5221     {
5222         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5223             pvInfoList[i].depth = 0;
5224             boards[i][EP_STATUS] = EP_NONE;
5225             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5226         }
5227
5228         initialRulePlies = 0; /* 50-move counter start */
5229
5230         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5231         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5232     }
5233
5234     
5235     /* [HGM] logic here is completely changed. In stead of full positions */
5236     /* the initialized data only consist of the two backranks. The switch */
5237     /* selects which one we will use, which is than copied to the Board   */
5238     /* initialPosition, which for the rest is initialized by Pawns and    */
5239     /* empty squares. This initial position is then copied to boards[0],  */
5240     /* possibly after shuffling, so that it remains available.            */
5241
5242     gameInfo.holdingsWidth = 0; /* default board sizes */
5243     gameInfo.boardWidth    = 8;
5244     gameInfo.boardHeight   = 8;
5245     gameInfo.holdingsSize  = 0;
5246     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5247     for(i=0; i<BOARD_FILES-2; i++)
5248       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5249     initialPosition[EP_STATUS] = EP_NONE;
5250     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5251
5252     switch (gameInfo.variant) {
5253     case VariantFischeRandom:
5254       shuffleOpenings = TRUE;
5255     default:
5256       pieces = FIDEArray;
5257       break;
5258     case VariantShatranj:
5259       pieces = ShatranjArray;
5260       nrCastlingRights = 0;
5261       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5262       break;
5263     case VariantMakruk:
5264       pieces = makrukArray;
5265       nrCastlingRights = 0;
5266       startedFromSetupPosition = TRUE;
5267       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5268       break;
5269     case VariantTwoKings:
5270       pieces = twoKingsArray;
5271       break;
5272     case VariantCapaRandom:
5273       shuffleOpenings = TRUE;
5274     case VariantCapablanca:
5275       pieces = CapablancaArray;
5276       gameInfo.boardWidth = 10;
5277       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5278       break;
5279     case VariantGothic:
5280       pieces = GothicArray;
5281       gameInfo.boardWidth = 10;
5282       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5283       break;
5284     case VariantJanus:
5285       pieces = JanusArray;
5286       gameInfo.boardWidth = 10;
5287       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5288       nrCastlingRights = 6;
5289         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5290         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5291         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5292         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5293         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5294         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5295       break;
5296     case VariantFalcon:
5297       pieces = FalconArray;
5298       gameInfo.boardWidth = 10;
5299       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5300       break;
5301     case VariantXiangqi:
5302       pieces = XiangqiArray;
5303       gameInfo.boardWidth  = 9;
5304       gameInfo.boardHeight = 10;
5305       nrCastlingRights = 0;
5306       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5307       break;
5308     case VariantShogi:
5309       pieces = ShogiArray;
5310       gameInfo.boardWidth  = 9;
5311       gameInfo.boardHeight = 9;
5312       gameInfo.holdingsSize = 7;
5313       nrCastlingRights = 0;
5314       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5315       break;
5316     case VariantCourier:
5317       pieces = CourierArray;
5318       gameInfo.boardWidth  = 12;
5319       nrCastlingRights = 0;
5320       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5321       break;
5322     case VariantKnightmate:
5323       pieces = KnightmateArray;
5324       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5325       break;
5326     case VariantFairy:
5327       pieces = fairyArray;
5328       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5329       break;
5330     case VariantGreat:
5331       pieces = GreatArray;
5332       gameInfo.boardWidth = 10;
5333       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5334       gameInfo.holdingsSize = 8;
5335       break;
5336     case VariantSuper:
5337       pieces = FIDEArray;
5338       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5339       gameInfo.holdingsSize = 8;
5340       startedFromSetupPosition = TRUE;
5341       break;
5342     case VariantCrazyhouse:
5343     case VariantBughouse:
5344       pieces = FIDEArray;
5345       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5346       gameInfo.holdingsSize = 5;
5347       break;
5348     case VariantWildCastle:
5349       pieces = FIDEArray;
5350       /* !!?shuffle with kings guaranteed to be on d or e file */
5351       shuffleOpenings = 1;
5352       break;
5353     case VariantNoCastle:
5354       pieces = FIDEArray;
5355       nrCastlingRights = 0;
5356       /* !!?unconstrained back-rank shuffle */
5357       shuffleOpenings = 1;
5358       break;
5359     }
5360
5361     overrule = 0;
5362     if(appData.NrFiles >= 0) {
5363         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5364         gameInfo.boardWidth = appData.NrFiles;
5365     }
5366     if(appData.NrRanks >= 0) {
5367         gameInfo.boardHeight = appData.NrRanks;
5368     }
5369     if(appData.holdingsSize >= 0) {
5370         i = appData.holdingsSize;
5371         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5372         gameInfo.holdingsSize = i;
5373     }
5374     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5375     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5376         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5377
5378     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5379     if(pawnRow < 1) pawnRow = 1;
5380     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5381
5382     /* User pieceToChar list overrules defaults */
5383     if(appData.pieceToCharTable != NULL)
5384         SetCharTable(pieceToChar, appData.pieceToCharTable);
5385
5386     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5387
5388         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5389             s = (ChessSquare) 0; /* account holding counts in guard band */
5390         for( i=0; i<BOARD_HEIGHT; i++ )
5391             initialPosition[i][j] = s;
5392
5393         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5394         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5395         initialPosition[pawnRow][j] = WhitePawn;
5396         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5397         if(gameInfo.variant == VariantXiangqi) {
5398             if(j&1) {
5399                 initialPosition[pawnRow][j] = 
5400                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5401                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5402                    initialPosition[2][j] = WhiteCannon;
5403                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5404                 }
5405             }
5406         }
5407         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5408     }
5409     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5410
5411             j=BOARD_LEFT+1;
5412             initialPosition[1][j] = WhiteBishop;
5413             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5414             j=BOARD_RGHT-2;
5415             initialPosition[1][j] = WhiteRook;
5416             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5417     }
5418
5419     if( nrCastlingRights == -1) {
5420         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5421         /*       This sets default castling rights from none to normal corners   */
5422         /* Variants with other castling rights must set them themselves above    */
5423         nrCastlingRights = 6;
5424        
5425         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5426         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5427         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5428         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5429         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5430         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5431      }
5432
5433      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5434      if(gameInfo.variant == VariantGreat) { // promotion commoners
5435         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5437         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5439      }
5440   if (appData.debugMode) {
5441     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5442   }
5443     if(shuffleOpenings) {
5444         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5445         startedFromSetupPosition = TRUE;
5446     }
5447     if(startedFromPositionFile) {
5448       /* [HGM] loadPos: use PositionFile for every new game */
5449       CopyBoard(initialPosition, filePosition);
5450       for(i=0; i<nrCastlingRights; i++)
5451           initialRights[i] = filePosition[CASTLING][i];
5452       startedFromSetupPosition = TRUE;
5453     }
5454
5455     CopyBoard(boards[0], initialPosition);
5456
5457     if(oldx != gameInfo.boardWidth ||
5458        oldy != gameInfo.boardHeight ||
5459        oldh != gameInfo.holdingsWidth
5460 #ifdef GOTHIC
5461        || oldv == VariantGothic ||        // For licensing popups
5462        gameInfo.variant == VariantGothic
5463 #endif
5464 #ifdef FALCON
5465        || oldv == VariantFalcon ||
5466        gameInfo.variant == VariantFalcon
5467 #endif
5468                                          )
5469             InitDrawingSizes(-2 ,0);
5470
5471     if (redraw)
5472       DrawPosition(TRUE, boards[currentMove]);
5473 }
5474
5475 void
5476 SendBoard(cps, moveNum)
5477      ChessProgramState *cps;
5478      int moveNum;
5479 {
5480     char message[MSG_SIZ];
5481     
5482     if (cps->useSetboard) {
5483       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5484       sprintf(message, "setboard %s\n", fen);
5485       SendToProgram(message, cps);
5486       free(fen);
5487
5488     } else {
5489       ChessSquare *bp;
5490       int i, j;
5491       /* Kludge to set black to move, avoiding the troublesome and now
5492        * deprecated "black" command.
5493        */
5494       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5495
5496       SendToProgram("edit\n", cps);
5497       SendToProgram("#\n", cps);
5498       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5499         bp = &boards[moveNum][i][BOARD_LEFT];
5500         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5501           if ((int) *bp < (int) BlackPawn) {
5502             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5503                     AAA + j, ONE + i);
5504             if(message[0] == '+' || message[0] == '~') {
5505                 sprintf(message, "%c%c%c+\n",
5506                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5507                         AAA + j, ONE + i);
5508             }
5509             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5510                 message[1] = BOARD_RGHT   - 1 - j + '1';
5511                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5512             }
5513             SendToProgram(message, cps);
5514           }
5515         }
5516       }
5517     
5518       SendToProgram("c\n", cps);
5519       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5520         bp = &boards[moveNum][i][BOARD_LEFT];
5521         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5522           if (((int) *bp != (int) EmptySquare)
5523               && ((int) *bp >= (int) BlackPawn)) {
5524             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5525                     AAA + j, ONE + i);
5526             if(message[0] == '+' || message[0] == '~') {
5527                 sprintf(message, "%c%c%c+\n",
5528                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5529                         AAA + j, ONE + i);
5530             }
5531             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5532                 message[1] = BOARD_RGHT   - 1 - j + '1';
5533                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5534             }
5535             SendToProgram(message, cps);
5536           }
5537         }
5538       }
5539     
5540       SendToProgram(".\n", cps);
5541     }
5542     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5543 }
5544
5545 static int autoQueen; // [HGM] oneclick
5546
5547 int
5548 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5549 {
5550     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5551     /* [HGM] add Shogi promotions */
5552     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5553     ChessSquare piece;
5554     ChessMove moveType;
5555     Boolean premove;
5556
5557     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5558     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5559
5560     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5561       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5562         return FALSE;
5563
5564     piece = boards[currentMove][fromY][fromX];
5565     if(gameInfo.variant == VariantShogi) {
5566         promotionZoneSize = 3;
5567         highestPromotingPiece = (int)WhiteFerz;
5568     } else if(gameInfo.variant == VariantMakruk) {
5569         promotionZoneSize = 3;
5570     }
5571
5572     // next weed out all moves that do not touch the promotion zone at all
5573     if((int)piece >= BlackPawn) {
5574         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5575              return FALSE;
5576         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5577     } else {
5578         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5579            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5580     }
5581
5582     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5583
5584     // weed out mandatory Shogi promotions
5585     if(gameInfo.variant == VariantShogi) {
5586         if(piece >= BlackPawn) {
5587             if(toY == 0 && piece == BlackPawn ||
5588                toY == 0 && piece == BlackQueen ||
5589                toY <= 1 && piece == BlackKnight) {
5590                 *promoChoice = '+';
5591                 return FALSE;
5592             }
5593         } else {
5594             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5595                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5596                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5597                 *promoChoice = '+';
5598                 return FALSE;
5599             }
5600         }
5601     }
5602
5603     // weed out obviously illegal Pawn moves
5604     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5605         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5606         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5607         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5608         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5609         // note we are not allowed to test for valid (non-)capture, due to premove
5610     }
5611
5612     // we either have a choice what to promote to, or (in Shogi) whether to promote
5613     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5614         *promoChoice = PieceToChar(BlackFerz);  // no choice
5615         return FALSE;
5616     }
5617     if(autoQueen) { // predetermined
5618         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5619              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5620         else *promoChoice = PieceToChar(BlackQueen);
5621         return FALSE;
5622     }
5623
5624     // suppress promotion popup on illegal moves that are not premoves
5625     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5626               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5627     if(appData.testLegality && !premove) {
5628         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5629                         fromY, fromX, toY, toX, NULLCHAR);
5630         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5631            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5632             return FALSE;
5633     }
5634
5635     return TRUE;
5636 }
5637
5638 int
5639 InPalace(row, column)
5640      int row, column;
5641 {   /* [HGM] for Xiangqi */
5642     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5643          column < (BOARD_WIDTH + 4)/2 &&
5644          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5645     return FALSE;
5646 }
5647
5648 int
5649 PieceForSquare (x, y)
5650      int x;
5651      int y;
5652 {
5653   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5654      return -1;
5655   else
5656      return boards[currentMove][y][x];
5657 }
5658
5659 int
5660 OKToStartUserMove(x, y)
5661      int x, y;
5662 {
5663     ChessSquare from_piece;
5664     int white_piece;
5665
5666     if (matchMode) return FALSE;
5667     if (gameMode == EditPosition) return TRUE;
5668
5669     if (x >= 0 && y >= 0)
5670       from_piece = boards[currentMove][y][x];
5671     else
5672       from_piece = EmptySquare;
5673
5674     if (from_piece == EmptySquare) return FALSE;
5675
5676     white_piece = (int)from_piece >= (int)WhitePawn &&
5677       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5678
5679     switch (gameMode) {
5680       case PlayFromGameFile:
5681       case AnalyzeFile:
5682       case TwoMachinesPlay:
5683       case EndOfGame:
5684         return FALSE;
5685
5686       case IcsObserving:
5687       case IcsIdle:
5688         return FALSE;
5689
5690       case MachinePlaysWhite:
5691       case IcsPlayingBlack:
5692         if (appData.zippyPlay) return FALSE;
5693         if (white_piece) {
5694             DisplayMoveError(_("You are playing Black"));
5695             return FALSE;
5696         }
5697         break;
5698
5699       case MachinePlaysBlack:
5700       case IcsPlayingWhite:
5701         if (appData.zippyPlay) return FALSE;
5702         if (!white_piece) {
5703             DisplayMoveError(_("You are playing White"));
5704             return FALSE;
5705         }
5706         break;
5707
5708       case EditGame:
5709         if (!white_piece && WhiteOnMove(currentMove)) {
5710             DisplayMoveError(_("It is White's turn"));
5711             return FALSE;
5712         }           
5713         if (white_piece && !WhiteOnMove(currentMove)) {
5714             DisplayMoveError(_("It is Black's turn"));
5715             return FALSE;
5716         }           
5717         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5718             /* Editing correspondence game history */
5719             /* Could disallow this or prompt for confirmation */
5720             cmailOldMove = -1;
5721         }
5722         break;
5723
5724       case BeginningOfGame:
5725         if (appData.icsActive) return FALSE;
5726         if (!appData.noChessProgram) {
5727             if (!white_piece) {
5728                 DisplayMoveError(_("You are playing White"));
5729                 return FALSE;
5730             }
5731         }
5732         break;
5733         
5734       case Training:
5735         if (!white_piece && WhiteOnMove(currentMove)) {
5736             DisplayMoveError(_("It is White's turn"));
5737             return FALSE;
5738         }           
5739         if (white_piece && !WhiteOnMove(currentMove)) {
5740             DisplayMoveError(_("It is Black's turn"));
5741             return FALSE;
5742         }           
5743         break;
5744
5745       default:
5746       case IcsExamining:
5747         break;
5748     }
5749     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5750         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5751         && gameMode != AnalyzeFile && gameMode != Training) {
5752         DisplayMoveError(_("Displayed position is not current"));
5753         return FALSE;
5754     }
5755     return TRUE;
5756 }
5757
5758 Boolean
5759 OnlyMove(int *x, int *y, Boolean captures) {
5760     DisambiguateClosure cl;
5761     if (appData.zippyPlay) return FALSE;
5762     switch(gameMode) {
5763       case MachinePlaysBlack:
5764       case IcsPlayingWhite:
5765       case BeginningOfGame:
5766         if(!WhiteOnMove(currentMove)) return FALSE;
5767         break;
5768       case MachinePlaysWhite:
5769       case IcsPlayingBlack:
5770         if(WhiteOnMove(currentMove)) return FALSE;
5771         break;
5772       default:
5773         return FALSE;
5774     }
5775     cl.pieceIn = EmptySquare; 
5776     cl.rfIn = *y;
5777     cl.ffIn = *x;
5778     cl.rtIn = -1;
5779     cl.ftIn = -1;
5780     cl.promoCharIn = NULLCHAR;
5781     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5782     if( cl.kind == NormalMove ||
5783         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5784         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5785         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5786         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5787       fromX = cl.ff;
5788       fromY = cl.rf;
5789       *x = cl.ft;
5790       *y = cl.rt;
5791       return TRUE;
5792     }
5793     if(cl.kind != ImpossibleMove) return FALSE;
5794     cl.pieceIn = EmptySquare;
5795     cl.rfIn = -1;
5796     cl.ffIn = -1;
5797     cl.rtIn = *y;
5798     cl.ftIn = *x;
5799     cl.promoCharIn = NULLCHAR;
5800     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5801     if( cl.kind == NormalMove ||
5802         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5803         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5804         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5805         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5806       fromX = cl.ff;
5807       fromY = cl.rf;
5808       *x = cl.ft;
5809       *y = cl.rt;
5810       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5811       return TRUE;
5812     }
5813     return FALSE;
5814 }
5815
5816 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5817 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5818 int lastLoadGameUseList = FALSE;
5819 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5820 ChessMove lastLoadGameStart = (ChessMove) 0;
5821
5822 ChessMove
5823 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5824      int fromX, fromY, toX, toY;
5825      int promoChar;
5826      Boolean captureOwn;
5827 {
5828     ChessMove moveType;
5829     ChessSquare pdown, pup;
5830
5831     /* Check if the user is playing in turn.  This is complicated because we
5832        let the user "pick up" a piece before it is his turn.  So the piece he
5833        tried to pick up may have been captured by the time he puts it down!
5834        Therefore we use the color the user is supposed to be playing in this
5835        test, not the color of the piece that is currently on the starting
5836        square---except in EditGame mode, where the user is playing both
5837        sides; fortunately there the capture race can't happen.  (It can
5838        now happen in IcsExamining mode, but that's just too bad.  The user
5839        will get a somewhat confusing message in that case.)
5840        */
5841
5842     switch (gameMode) {
5843       case PlayFromGameFile:
5844       case AnalyzeFile:
5845       case TwoMachinesPlay:
5846       case EndOfGame:
5847       case IcsObserving:
5848       case IcsIdle:
5849         /* We switched into a game mode where moves are not accepted,
5850            perhaps while the mouse button was down. */
5851         return ImpossibleMove;
5852
5853       case MachinePlaysWhite:
5854         /* User is moving for Black */
5855         if (WhiteOnMove(currentMove)) {
5856             DisplayMoveError(_("It is White's turn"));
5857             return ImpossibleMove;
5858         }
5859         break;
5860
5861       case MachinePlaysBlack:
5862         /* User is moving for White */
5863         if (!WhiteOnMove(currentMove)) {
5864             DisplayMoveError(_("It is Black's turn"));
5865             return ImpossibleMove;
5866         }
5867         break;
5868
5869       case EditGame:
5870       case IcsExamining:
5871       case BeginningOfGame:
5872       case AnalyzeMode:
5873       case Training:
5874         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5875             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5876             /* User is moving for Black */
5877             if (WhiteOnMove(currentMove)) {
5878                 DisplayMoveError(_("It is White's turn"));
5879                 return ImpossibleMove;
5880             }
5881         } else {
5882             /* User is moving for White */
5883             if (!WhiteOnMove(currentMove)) {
5884                 DisplayMoveError(_("It is Black's turn"));
5885                 return ImpossibleMove;
5886             }
5887         }
5888         break;
5889
5890       case IcsPlayingBlack:
5891         /* User is moving for Black */
5892         if (WhiteOnMove(currentMove)) {
5893             if (!appData.premove) {
5894                 DisplayMoveError(_("It is White's turn"));
5895             } else if (toX >= 0 && toY >= 0) {
5896                 premoveToX = toX;
5897                 premoveToY = toY;
5898                 premoveFromX = fromX;
5899                 premoveFromY = fromY;
5900                 premovePromoChar = promoChar;
5901                 gotPremove = 1;
5902                 if (appData.debugMode) 
5903                     fprintf(debugFP, "Got premove: fromX %d,"
5904                             "fromY %d, toX %d, toY %d\n",
5905                             fromX, fromY, toX, toY);
5906             }
5907             return ImpossibleMove;
5908         }
5909         break;
5910
5911       case IcsPlayingWhite:
5912         /* User is moving for White */
5913         if (!WhiteOnMove(currentMove)) {
5914             if (!appData.premove) {
5915                 DisplayMoveError(_("It is Black's turn"));
5916             } else if (toX >= 0 && toY >= 0) {
5917                 premoveToX = toX;
5918                 premoveToY = toY;
5919                 premoveFromX = fromX;
5920                 premoveFromY = fromY;
5921                 premovePromoChar = promoChar;
5922                 gotPremove = 1;
5923                 if (appData.debugMode) 
5924                     fprintf(debugFP, "Got premove: fromX %d,"
5925                             "fromY %d, toX %d, toY %d\n",
5926                             fromX, fromY, toX, toY);
5927             }
5928             return ImpossibleMove;
5929         }
5930         break;
5931
5932       default:
5933         break;
5934
5935       case EditPosition:
5936         /* EditPosition, empty square, or different color piece;
5937            click-click move is possible */
5938         if (toX == -2 || toY == -2) {
5939             boards[0][fromY][fromX] = EmptySquare;
5940             return AmbiguousMove;
5941         } else if (toX >= 0 && toY >= 0) {
5942             boards[0][toY][toX] = boards[0][fromY][fromX];
5943             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5944                 if(boards[0][fromY][0] != EmptySquare) {
5945                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5946                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5947                 }
5948             } else
5949             if(fromX == BOARD_RGHT+1) {
5950                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5951                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5952                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5953                 }
5954             } else
5955             boards[0][fromY][fromX] = EmptySquare;
5956             return AmbiguousMove;
5957         }
5958         return ImpossibleMove;
5959     }
5960
5961     if(toX < 0 || toY < 0) return ImpossibleMove;
5962     pdown = boards[currentMove][fromY][fromX];
5963     pup = boards[currentMove][toY][toX];
5964
5965     /* [HGM] If move started in holdings, it means a drop */
5966     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5967          if( pup != EmptySquare ) return ImpossibleMove;
5968          if(appData.testLegality) {
5969              /* it would be more logical if LegalityTest() also figured out
5970               * which drops are legal. For now we forbid pawns on back rank.
5971               * Shogi is on its own here...
5972               */
5973              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5974                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5975                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5976          }
5977          return WhiteDrop; /* Not needed to specify white or black yet */
5978     }
5979
5980     /* [HGM] always test for legality, to get promotion info */
5981     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5982                                          fromY, fromX, toY, toX, promoChar);
5983     /* [HGM] but possibly ignore an IllegalMove result */
5984     if (appData.testLegality) {
5985         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5986             DisplayMoveError(_("Illegal move"));
5987             return ImpossibleMove;
5988         }
5989     }
5990
5991     return moveType;
5992     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5993        function is made into one that returns an OK move type if FinishMove
5994        should be called. This to give the calling driver routine the
5995        opportunity to finish the userMove input with a promotion popup,
5996        without bothering the user with this for invalid or illegal moves */
5997
5998 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5999 }
6000
6001 /* Common tail of UserMoveEvent and DropMenuEvent */
6002 int
6003 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6004      ChessMove moveType;
6005      int fromX, fromY, toX, toY;
6006      /*char*/int promoChar;
6007 {
6008     char *bookHit = 0;
6009
6010     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
6011         // [HGM] superchess: suppress promotions to non-available piece
6012         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6013         if(WhiteOnMove(currentMove)) {
6014             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6015         } else {
6016             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6017         }
6018     }
6019
6020     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6021        move type in caller when we know the move is a legal promotion */
6022     if(moveType == NormalMove && promoChar)
6023         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
6024
6025     /* [HGM] convert drag-and-drop piece drops to standard form */
6026     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
6027          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6028            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6029                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6030            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6031            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6032            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6033            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6034          fromY = DROP_RANK;
6035     }
6036
6037     /* [HGM] <popupFix> The following if has been moved here from
6038        UserMoveEvent(). Because it seemed to belong here (why not allow
6039        piece drops in training games?), and because it can only be
6040        performed after it is known to what we promote. */
6041     if (gameMode == Training) {
6042       /* compare the move played on the board to the next move in the
6043        * game. If they match, display the move and the opponent's response. 
6044        * If they don't match, display an error message.
6045        */
6046       int saveAnimate;
6047       Board testBoard;
6048       CopyBoard(testBoard, boards[currentMove]);
6049       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6050
6051       if (CompareBoards(testBoard, boards[currentMove+1])) {
6052         ForwardInner(currentMove+1);
6053
6054         /* Autoplay the opponent's response.
6055          * if appData.animate was TRUE when Training mode was entered,
6056          * the response will be animated.
6057          */
6058         saveAnimate = appData.animate;
6059         appData.animate = animateTraining;
6060         ForwardInner(currentMove+1);
6061         appData.animate = saveAnimate;
6062
6063         /* check for the end of the game */
6064         if (currentMove >= forwardMostMove) {
6065           gameMode = PlayFromGameFile;
6066           ModeHighlight();
6067           SetTrainingModeOff();
6068           DisplayInformation(_("End of game"));
6069         }
6070       } else {
6071         DisplayError(_("Incorrect move"), 0);
6072       }
6073       return 1;
6074     }
6075
6076   /* Ok, now we know that the move is good, so we can kill
6077      the previous line in Analysis Mode */
6078   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6079                                 && currentMove < forwardMostMove) {
6080     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6081   }
6082
6083   /* If we need the chess program but it's dead, restart it */
6084   ResurrectChessProgram();
6085
6086   /* A user move restarts a paused game*/
6087   if (pausing)
6088     PauseEvent();
6089
6090   thinkOutput[0] = NULLCHAR;
6091
6092   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6093
6094   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6095
6096   if (gameMode == BeginningOfGame) {
6097     if (appData.noChessProgram) {
6098       gameMode = EditGame;
6099       SetGameInfo();
6100     } else {
6101       char buf[MSG_SIZ];
6102       gameMode = MachinePlaysBlack;
6103       StartClocks();
6104       SetGameInfo();
6105       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6106       DisplayTitle(buf);
6107       if (first.sendName) {
6108         sprintf(buf, "name %s\n", gameInfo.white);
6109         SendToProgram(buf, &first);
6110       }
6111       StartClocks();
6112     }
6113     ModeHighlight();
6114   }
6115
6116   /* Relay move to ICS or chess engine */
6117   if (appData.icsActive) {
6118     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6119         gameMode == IcsExamining) {
6120       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6121         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6122         SendToICS("draw ");
6123         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6124       }
6125       // also send plain move, in case ICS does not understand atomic claims
6126       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6127       ics_user_moved = 1;
6128     }
6129   } else {
6130     if (first.sendTime && (gameMode == BeginningOfGame ||
6131                            gameMode == MachinePlaysWhite ||
6132                            gameMode == MachinePlaysBlack)) {
6133       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6134     }
6135     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6136          // [HGM] book: if program might be playing, let it use book
6137         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6138         first.maybeThinking = TRUE;
6139     } else SendMoveToProgram(forwardMostMove-1, &first);
6140     if (currentMove == cmailOldMove + 1) {
6141       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6142     }
6143   }
6144
6145   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146
6147   switch (gameMode) {
6148   case EditGame:
6149     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6150     case MT_NONE:
6151     case MT_CHECK:
6152       break;
6153     case MT_CHECKMATE:
6154     case MT_STAINMATE:
6155       if (WhiteOnMove(currentMove)) {
6156         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6157       } else {
6158         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6159       }
6160       break;
6161     case MT_STALEMATE:
6162       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6163       break;
6164     }
6165     break;
6166     
6167   case MachinePlaysBlack:
6168   case MachinePlaysWhite:
6169     /* disable certain menu options while machine is thinking */
6170     SetMachineThinkingEnables();
6171     break;
6172
6173   default:
6174     break;
6175   }
6176
6177   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6178         
6179   if(bookHit) { // [HGM] book: simulate book reply
6180         static char bookMove[MSG_SIZ]; // a bit generous?
6181
6182         programStats.nodes = programStats.depth = programStats.time = 
6183         programStats.score = programStats.got_only_move = 0;
6184         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6185
6186         strcpy(bookMove, "move ");
6187         strcat(bookMove, bookHit);
6188         HandleMachineMove(bookMove, &first);
6189   }
6190   return 1;
6191 }
6192
6193 void
6194 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6195      int fromX, fromY, toX, toY;
6196      int promoChar;
6197 {
6198     /* [HGM] This routine was added to allow calling of its two logical
6199        parts from other modules in the old way. Before, UserMoveEvent()
6200        automatically called FinishMove() if the move was OK, and returned
6201        otherwise. I separated the two, in order to make it possible to
6202        slip a promotion popup in between. But that it always needs two
6203        calls, to the first part, (now called UserMoveTest() ), and to
6204        FinishMove if the first part succeeded. Calls that do not need
6205        to do anything in between, can call this routine the old way. 
6206     */
6207     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6208 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6209     if(moveType == AmbiguousMove)
6210         DrawPosition(FALSE, boards[currentMove]);
6211     else if(moveType != ImpossibleMove && moveType != Comment)
6212         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6213 }
6214
6215 void
6216 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6217      Board board;
6218      int flags;
6219      ChessMove kind;
6220      int rf, ff, rt, ft;
6221      VOIDSTAR closure;
6222 {
6223     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6224     Markers *m = (Markers *) closure;
6225     if(rf == fromY && ff == fromX)
6226         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6227                          || kind == WhiteCapturesEnPassant
6228                          || kind == BlackCapturesEnPassant);
6229     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6230 }
6231
6232 void
6233 MarkTargetSquares(int clear)
6234 {
6235   int x, y;
6236   if(!appData.markers || !appData.highlightDragging || 
6237      !appData.testLegality || gameMode == EditPosition) return;
6238   if(clear) {
6239     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6240   } else {
6241     int capt = 0;
6242     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6243     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6244       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6245       if(capt)
6246       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6247     }
6248   }
6249   DrawPosition(TRUE, NULL);
6250 }
6251
6252 void LeftClick(ClickType clickType, int xPix, int yPix)
6253 {
6254     int x, y;
6255     Boolean saveAnimate;
6256     static int second = 0, promotionChoice = 0;
6257     char promoChoice = NULLCHAR;
6258
6259     if(appData.seekGraph && appData.icsActive && loggedOn &&
6260         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6261         SeekGraphClick(clickType, xPix, yPix, 0);
6262         return;
6263     }
6264
6265     if (clickType == Press) ErrorPopDown();
6266     MarkTargetSquares(1);
6267
6268     x = EventToSquare(xPix, BOARD_WIDTH);
6269     y = EventToSquare(yPix, BOARD_HEIGHT);
6270     if (!flipView && y >= 0) {
6271         y = BOARD_HEIGHT - 1 - y;
6272     }
6273     if (flipView && x >= 0) {
6274         x = BOARD_WIDTH - 1 - x;
6275     }
6276
6277     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6278         if(clickType == Release) return; // ignore upclick of click-click destination
6279         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6280         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6281         if(gameInfo.holdingsWidth && 
6282                 (WhiteOnMove(currentMove) 
6283                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6284                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6285             // click in right holdings, for determining promotion piece
6286             ChessSquare p = boards[currentMove][y][x];
6287             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6288             if(p != EmptySquare) {
6289                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6290                 fromX = fromY = -1;
6291                 return;
6292             }
6293         }
6294         DrawPosition(FALSE, boards[currentMove]);
6295         return;
6296     }
6297
6298     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6299     if(clickType == Press
6300             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6301               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6302               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6303         return;
6304
6305     autoQueen = appData.alwaysPromoteToQueen;
6306
6307     if (fromX == -1) {
6308       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6309         if (clickType == Press) {
6310             /* First square */
6311             if (OKToStartUserMove(x, y)) {
6312                 fromX = x;
6313                 fromY = y;
6314                 second = 0;
6315                 MarkTargetSquares(0);
6316                 DragPieceBegin(xPix, yPix);
6317                 if (appData.highlightDragging) {
6318                     SetHighlights(x, y, -1, -1);
6319                 }
6320             }
6321         }
6322         return;
6323       }
6324     }
6325
6326     /* fromX != -1 */
6327     if (clickType == Press && gameMode != EditPosition) {
6328         ChessSquare fromP;
6329         ChessSquare toP;
6330         int frc;
6331
6332         // ignore off-board to clicks
6333         if(y < 0 || x < 0) return;
6334
6335         /* Check if clicking again on the same color piece */
6336         fromP = boards[currentMove][fromY][fromX];
6337         toP = boards[currentMove][y][x];
6338         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6339         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6340              WhitePawn <= toP && toP <= WhiteKing &&
6341              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6342              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6343             (BlackPawn <= fromP && fromP <= BlackKing && 
6344              BlackPawn <= toP && toP <= BlackKing &&
6345              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6346              !(fromP == BlackKing && toP == BlackRook && frc))) {
6347             /* Clicked again on same color piece -- changed his mind */
6348             second = (x == fromX && y == fromY);
6349            if(!second || !OnlyMove(&x, &y, TRUE)) {
6350             if (appData.highlightDragging) {
6351                 SetHighlights(x, y, -1, -1);
6352             } else {
6353                 ClearHighlights();
6354             }
6355             if (OKToStartUserMove(x, y)) {
6356                 fromX = x;
6357                 fromY = y;
6358                 MarkTargetSquares(0);
6359                 DragPieceBegin(xPix, yPix);
6360             }
6361             return;
6362            }
6363         }
6364         // ignore clicks on holdings
6365         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6366     }
6367
6368     if (clickType == Release && x == fromX && y == fromY) {
6369         DragPieceEnd(xPix, yPix);
6370         if (appData.animateDragging) {
6371             /* Undo animation damage if any */
6372             DrawPosition(FALSE, NULL);
6373         }
6374         if (second) {
6375             /* Second up/down in same square; just abort move */
6376             second = 0;
6377             fromX = fromY = -1;
6378             ClearHighlights();
6379             gotPremove = 0;
6380             ClearPremoveHighlights();
6381         } else {
6382             /* First upclick in same square; start click-click mode */
6383             SetHighlights(x, y, -1, -1);
6384         }
6385         return;
6386     }
6387
6388     /* we now have a different from- and (possibly off-board) to-square */
6389     /* Completed move */
6390     toX = x;
6391     toY = y;
6392     saveAnimate = appData.animate;
6393     if (clickType == Press) {
6394         /* Finish clickclick move */
6395         if (appData.animate || appData.highlightLastMove) {
6396             SetHighlights(fromX, fromY, toX, toY);
6397         } else {
6398             ClearHighlights();
6399         }
6400     } else {
6401         /* Finish drag move */
6402         if (appData.highlightLastMove) {
6403             SetHighlights(fromX, fromY, toX, toY);
6404         } else {
6405             ClearHighlights();
6406         }
6407         DragPieceEnd(xPix, yPix);
6408         /* Don't animate move and drag both */
6409         appData.animate = FALSE;
6410     }
6411
6412     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6413     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6414         ChessSquare piece = boards[currentMove][fromY][fromX];
6415         if(gameMode == EditPosition && piece != EmptySquare &&
6416            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6417             int n;
6418              
6419             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6420                 n = PieceToNumber(piece - (int)BlackPawn);
6421                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6422                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6423                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6424             } else
6425             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6426                 n = PieceToNumber(piece);
6427                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6428                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6429                 boards[currentMove][n][BOARD_WIDTH-2]++;
6430             }
6431             boards[currentMove][fromY][fromX] = EmptySquare;
6432         }
6433         ClearHighlights();
6434         fromX = fromY = -1;
6435         DrawPosition(TRUE, boards[currentMove]);
6436         return;
6437     }
6438
6439     // off-board moves should not be highlighted
6440     if(x < 0 || x < 0) ClearHighlights();
6441
6442     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6443         SetHighlights(fromX, fromY, toX, toY);
6444         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6445             // [HGM] super: promotion to captured piece selected from holdings
6446             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6447             promotionChoice = TRUE;
6448             // kludge follows to temporarily execute move on display, without promoting yet
6449             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6450             boards[currentMove][toY][toX] = p;
6451             DrawPosition(FALSE, boards[currentMove]);
6452             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6453             boards[currentMove][toY][toX] = q;
6454             DisplayMessage("Click in holdings to choose piece", "");
6455             return;
6456         }
6457         PromotionPopUp();
6458     } else {
6459         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6460         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6461         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6462         fromX = fromY = -1;
6463     }
6464     appData.animate = saveAnimate;
6465     if (appData.animate || appData.animateDragging) {
6466         /* Undo animation damage if needed */
6467         DrawPosition(FALSE, NULL);
6468     }
6469 }
6470
6471 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6472 {   // front-end-free part taken out of PieceMenuPopup
6473     int whichMenu; int xSqr, ySqr;
6474
6475     if(seekGraphUp) { // [HGM] seekgraph
6476         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6477         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6478         return -2;
6479     }
6480
6481     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6482          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6483         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6484         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6485         if(action == Press)   {
6486             originalFlip = flipView;
6487             flipView = !flipView; // temporarily flip board to see game from partners perspective
6488             DrawPosition(TRUE, partnerBoard);
6489             DisplayMessage(partnerStatus, "");
6490             partnerUp = TRUE;
6491         } else if(action == Release) {
6492             flipView = originalFlip;
6493             DrawPosition(TRUE, boards[currentMove]);
6494             partnerUp = FALSE;
6495         }
6496         return -2;
6497     }
6498
6499     xSqr = EventToSquare(x, BOARD_WIDTH);
6500     ySqr = EventToSquare(y, BOARD_HEIGHT);
6501     if (action == Release) UnLoadPV(); // [HGM] pv
6502     if (action != Press) return -2; // return code to be ignored
6503     switch (gameMode) {
6504       case IcsExamining:
6505         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6506       case EditPosition:
6507         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6508         if (xSqr < 0 || ySqr < 0) return -1;\r
6509         whichMenu = 0; // edit-position menu
6510         break;
6511       case IcsObserving:
6512         if(!appData.icsEngineAnalyze) return -1;
6513       case IcsPlayingWhite:
6514       case IcsPlayingBlack:
6515         if(!appData.zippyPlay) goto noZip;
6516       case AnalyzeMode:
6517       case AnalyzeFile:
6518       case MachinePlaysWhite:
6519       case MachinePlaysBlack:
6520       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6521         if (!appData.dropMenu) {
6522           LoadPV(x, y);
6523           return 2; // flag front-end to grab mouse events
6524         }
6525         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6526            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6527       case EditGame:
6528       noZip:
6529         if (xSqr < 0 || ySqr < 0) return -1;
6530         if (!appData.dropMenu || appData.testLegality &&
6531             gameInfo.variant != VariantBughouse &&
6532             gameInfo.variant != VariantCrazyhouse) return -1;
6533         whichMenu = 1; // drop menu
6534         break;
6535       default:
6536         return -1;
6537     }
6538
6539     if (((*fromX = xSqr) < 0) ||
6540         ((*fromY = ySqr) < 0)) {
6541         *fromX = *fromY = -1;
6542         return -1;
6543     }
6544     if (flipView)
6545       *fromX = BOARD_WIDTH - 1 - *fromX;
6546     else
6547       *fromY = BOARD_HEIGHT - 1 - *fromY;
6548
6549     return whichMenu;
6550 }
6551
6552 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6553 {
6554 //    char * hint = lastHint;
6555     FrontEndProgramStats stats;
6556
6557     stats.which = cps == &first ? 0 : 1;
6558     stats.depth = cpstats->depth;
6559     stats.nodes = cpstats->nodes;
6560     stats.score = cpstats->score;
6561     stats.time = cpstats->time;
6562     stats.pv = cpstats->movelist;
6563     stats.hint = lastHint;
6564     stats.an_move_index = 0;
6565     stats.an_move_count = 0;
6566
6567     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6568         stats.hint = cpstats->move_name;
6569         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6570         stats.an_move_count = cpstats->nr_moves;
6571     }
6572
6573     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6574
6575     SetProgramStats( &stats );
6576 }
6577
6578 int
6579 Adjudicate(ChessProgramState *cps)
6580 {       // [HGM] some adjudications useful with buggy engines
6581         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6582         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6583         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6584         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6585         int k, count = 0; static int bare = 1;
6586         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6587         Boolean canAdjudicate = !appData.icsActive;
6588
6589         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6590         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6591             if( appData.testLegality )
6592             {   /* [HGM] Some more adjudications for obstinate engines */
6593                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6594                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6595                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6596                 static int moveCount = 6;
6597                 ChessMove result;
6598                 char *reason = NULL;
6599
6600                 /* Count what is on board. */
6601                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6602                 {   ChessSquare p = boards[forwardMostMove][i][j];
6603                     int m=i;
6604
6605                     switch((int) p)
6606                     {   /* count B,N,R and other of each side */
6607                         case WhiteKing:
6608                         case BlackKing:
6609                              NrK++; break; // [HGM] atomic: count Kings
6610                         case WhiteKnight:
6611                              NrWN++; break;
6612                         case WhiteBishop:
6613                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6614                              bishopsColor |= 1 << ((i^j)&1);
6615                              NrWB++; break;
6616                         case BlackKnight:
6617                              NrBN++; break;
6618                         case BlackBishop:
6619                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6620                              bishopsColor |= 1 << ((i^j)&1);
6621                              NrBB++; break;
6622                         case WhiteRook:
6623                              NrWR++; break;
6624                         case BlackRook:
6625                              NrBR++; break;
6626                         case WhiteQueen:
6627                              NrWQ++; break;
6628                         case BlackQueen:
6629                              NrBQ++; break;
6630                         case EmptySquare: 
6631                              break;
6632                         case BlackPawn:
6633                              m = 7-i;
6634                         case WhitePawn:
6635                              PawnAdvance += m; NrPawns++;
6636                     }
6637                     NrPieces += (p != EmptySquare);
6638                     NrW += ((int)p < (int)BlackPawn);
6639                     if(gameInfo.variant == VariantXiangqi && 
6640                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6641                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6642                         NrW -= ((int)p < (int)BlackPawn);
6643                     }
6644                 }
6645
6646                 /* Some material-based adjudications that have to be made before stalemate test */
6647                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6648                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6649                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6650                      if(canAdjudicate && appData.checkMates) {
6651                          if(engineOpponent)
6652                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6653                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6654                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6655                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6656                          return 1;
6657                      }
6658                 }
6659
6660                 /* Bare King in Shatranj (loses) or Losers (wins) */
6661                 if( NrW == 1 || NrPieces - NrW == 1) {
6662                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6663                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6664                      if(canAdjudicate && appData.checkMates) {
6665                          if(engineOpponent)
6666                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6667                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6668                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6669                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6670                          return 1;
6671                      }
6672                   } else
6673                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6674                   {    /* bare King */
6675                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6676                         if(canAdjudicate && appData.checkMates) {
6677                             /* but only adjudicate if adjudication enabled */
6678                             if(engineOpponent)
6679                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6680                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6681                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6682                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6683                             return 1;
6684                         }
6685                   }
6686                 } else bare = 1;
6687
6688
6689             // don't wait for engine to announce game end if we can judge ourselves
6690             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6691               case MT_CHECK:
6692                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6693                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6694                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6695                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6696                             checkCnt++;
6697                         if(checkCnt >= 2) {
6698                             reason = "Xboard adjudication: 3rd check";
6699                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6700                             break;
6701                         }
6702                     }
6703                 }
6704               case MT_NONE:
6705               default:
6706                 break;
6707               case MT_STALEMATE:
6708               case MT_STAINMATE:
6709                 reason = "Xboard adjudication: Stalemate";
6710                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6711                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6712                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6713                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6714                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6715                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6716                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6717                                                                         EP_CHECKMATE : EP_WINS);
6718                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6719                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6720                 }
6721                 break;
6722               case MT_CHECKMATE:
6723                 reason = "Xboard adjudication: Checkmate";
6724                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6725                 break;
6726             }
6727
6728                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6729                     case EP_STALEMATE:
6730                         result = GameIsDrawn; break;
6731                     case EP_CHECKMATE:
6732                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6733                     case EP_WINS:
6734                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6735                     default:
6736                         result = (ChessMove) 0;
6737                 }
6738                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6739                     if(engineOpponent)
6740                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6741                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6742                     GameEnds( result, reason, GE_XBOARD );
6743                     return 1;
6744                 }
6745
6746                 /* Next absolutely insufficient mating material. */
6747                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6748                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6749                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6750                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6751                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6752
6753                      /* always flag draws, for judging claims */
6754                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6755
6756                      if(canAdjudicate && appData.materialDraws) {
6757                          /* but only adjudicate them if adjudication enabled */
6758                          if(engineOpponent) {
6759                            SendToProgram("force\n", engineOpponent); // suppress reply
6760                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6761                          }
6762                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6763                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6764                          return 1;
6765                      }
6766                 }
6767
6768                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6769                 if(NrPieces == 4 && 
6770                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6771                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6772                    || NrWN==2 || NrBN==2     /* KNNK */
6773                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6774                   ) ) {
6775                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6776                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6777                           if(engineOpponent) {
6778                             SendToProgram("force\n", engineOpponent); // suppress reply
6779                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6780                           }
6781                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6782                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6783                           return 1;
6784                      }
6785                 } else moveCount = 6;
6786             }
6787         }
6788           
6789         if (appData.debugMode) { int i;
6790             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6791                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6792                     appData.drawRepeats);
6793             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6794               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6795             
6796         }
6797
6798         // Repetition draws and 50-move rule can be applied independently of legality testing
6799
6800                 /* Check for rep-draws */
6801                 count = 0;
6802                 for(k = forwardMostMove-2;
6803                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6804                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6805                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6806                     k-=2)
6807                 {   int rights=0;
6808                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6809                         /* compare castling rights */
6810                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6811                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6812                                 rights++; /* King lost rights, while rook still had them */
6813                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6814                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6815                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6816                                    rights++; /* but at least one rook lost them */
6817                         }
6818                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6819                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6820                                 rights++; 
6821                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6822                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6823                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6824                                    rights++;
6825                         }
6826                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6827                             && appData.drawRepeats > 1) {
6828                              /* adjudicate after user-specified nr of repeats */
6829                              if(engineOpponent) {
6830                                SendToProgram("force\n", engineOpponent); // suppress reply
6831                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6832                              }
6833                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6834                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6835                                 // [HGM] xiangqi: check for forbidden perpetuals
6836                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6837                                 for(m=forwardMostMove; m>k; m-=2) {
6838                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6839                                         ourPerpetual = 0; // the current mover did not always check
6840                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6841                                         hisPerpetual = 0; // the opponent did not always check
6842                                 }
6843                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6844                                                                         ourPerpetual, hisPerpetual);
6845                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6846                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6847                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6848                                     return 1;
6849                                 }
6850                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6851                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6852                                 // Now check for perpetual chases
6853                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6854                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6855                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6856                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6857                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6858                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6859                                         return 1;
6860                                     }
6861                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6862                                         break; // Abort repetition-checking loop.
6863                                 }
6864                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6865                              }
6866                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6867                              return 1;
6868                         }
6869                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6870                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6871                     }
6872                 }
6873
6874                 /* Now we test for 50-move draws. Determine ply count */
6875                 count = forwardMostMove;
6876                 /* look for last irreversble move */
6877                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6878                     count--;
6879                 /* if we hit starting position, add initial plies */
6880                 if( count == backwardMostMove )
6881                     count -= initialRulePlies;
6882                 count = forwardMostMove - count; 
6883                 if( count >= 100)
6884                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6885                          /* this is used to judge if draw claims are legal */
6886                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6887                          if(engineOpponent) {
6888                            SendToProgram("force\n", engineOpponent); // suppress reply
6889                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6890                          }
6891                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6892                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6893                          return 1;
6894                 }
6895
6896                 /* if draw offer is pending, treat it as a draw claim
6897                  * when draw condition present, to allow engines a way to
6898                  * claim draws before making their move to avoid a race
6899                  * condition occurring after their move
6900                  */
6901                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6902                          char *p = NULL;
6903                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6904                              p = "Draw claim: 50-move rule";
6905                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6906                              p = "Draw claim: 3-fold repetition";
6907                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6908                              p = "Draw claim: insufficient mating material";
6909                          if( p != NULL && canAdjudicate) {
6910                              if(engineOpponent) {
6911                                SendToProgram("force\n", engineOpponent); // suppress reply
6912                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6913                              }
6914                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6915                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6916                              return 1;
6917                          }
6918                 }
6919
6920                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6921                     if(engineOpponent) {
6922                       SendToProgram("force\n", engineOpponent); // suppress reply
6923                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6924                     }
6925                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6926                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6927                     return 1;
6928                 }
6929         return 0;
6930 }
6931
6932 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6933 {   // [HGM] book: this routine intercepts moves to simulate book replies
6934     char *bookHit = NULL;
6935
6936     //first determine if the incoming move brings opponent into his book
6937     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6938         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6939     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6940     if(bookHit != NULL && !cps->bookSuspend) {
6941         // make sure opponent is not going to reply after receiving move to book position
6942         SendToProgram("force\n", cps);
6943         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6944     }
6945     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6946     // now arrange restart after book miss
6947     if(bookHit) {
6948         // after a book hit we never send 'go', and the code after the call to this routine
6949         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6950         char buf[MSG_SIZ];
6951         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6952         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6953         SendToProgram(buf, cps);
6954         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6955     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6956         SendToProgram("go\n", cps);
6957         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6958     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6959         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6960             SendToProgram("go\n", cps); 
6961         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6962     }
6963     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6964 }
6965
6966 char *savedMessage;
6967 ChessProgramState *savedState;
6968 void DeferredBookMove(void)
6969 {
6970         if(savedState->lastPing != savedState->lastPong)
6971                     ScheduleDelayedEvent(DeferredBookMove, 10);
6972         else
6973         HandleMachineMove(savedMessage, savedState);
6974 }
6975
6976 void
6977 HandleMachineMove(message, cps)
6978      char *message;
6979      ChessProgramState *cps;
6980 {
6981     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6982     char realname[MSG_SIZ];
6983     int fromX, fromY, toX, toY;
6984     ChessMove moveType;
6985     char promoChar;
6986     char *p;
6987     int machineWhite;
6988     char *bookHit;
6989
6990     cps->userError = 0;
6991
6992 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6993     /*
6994      * Kludge to ignore BEL characters
6995      */
6996     while (*message == '\007') message++;
6997
6998     /*
6999      * [HGM] engine debug message: ignore lines starting with '#' character
7000      */
7001     if(cps->debug && *message == '#') return;
7002
7003     /*
7004      * Look for book output
7005      */
7006     if (cps == &first && bookRequested) {
7007         if (message[0] == '\t' || message[0] == ' ') {
7008             /* Part of the book output is here; append it */
7009             strcat(bookOutput, message);
7010             strcat(bookOutput, "  \n");
7011             return;
7012         } else if (bookOutput[0] != NULLCHAR) {
7013             /* All of book output has arrived; display it */
7014             char *p = bookOutput;
7015             while (*p != NULLCHAR) {
7016                 if (*p == '\t') *p = ' ';
7017                 p++;
7018             }
7019             DisplayInformation(bookOutput);
7020             bookRequested = FALSE;
7021             /* Fall through to parse the current output */
7022         }
7023     }
7024
7025     /*
7026      * Look for machine move.
7027      */
7028     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7029         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
7030     {
7031         /* This method is only useful on engines that support ping */
7032         if (cps->lastPing != cps->lastPong) {
7033           if (gameMode == BeginningOfGame) {
7034             /* Extra move from before last new; ignore */
7035             if (appData.debugMode) {
7036                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7037             }
7038           } else {
7039             if (appData.debugMode) {
7040                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7041                         cps->which, gameMode);
7042             }
7043
7044             SendToProgram("undo\n", cps);
7045           }
7046           return;
7047         }
7048
7049         switch (gameMode) {
7050           case BeginningOfGame:
7051             /* Extra move from before last reset; ignore */
7052             if (appData.debugMode) {
7053                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7054             }
7055             return;
7056
7057           case EndOfGame:
7058           case IcsIdle:
7059           default:
7060             /* Extra move after we tried to stop.  The mode test is
7061                not a reliable way of detecting this problem, but it's
7062                the best we can do on engines that don't support ping.
7063             */
7064             if (appData.debugMode) {
7065                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7066                         cps->which, gameMode);
7067             }
7068             SendToProgram("undo\n", cps);
7069             return;
7070
7071           case MachinePlaysWhite:
7072           case IcsPlayingWhite:
7073             machineWhite = TRUE;
7074             break;
7075
7076           case MachinePlaysBlack:
7077           case IcsPlayingBlack:
7078             machineWhite = FALSE;
7079             break;
7080
7081           case TwoMachinesPlay:
7082             machineWhite = (cps->twoMachinesColor[0] == 'w');
7083             break;
7084         }
7085         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7086             if (appData.debugMode) {
7087                 fprintf(debugFP,
7088                         "Ignoring move out of turn by %s, gameMode %d"
7089                         ", forwardMost %d\n",
7090                         cps->which, gameMode, forwardMostMove);
7091             }
7092             return;
7093         }
7094
7095     if (appData.debugMode) { int f = forwardMostMove;
7096         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7097                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7098                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7099     }
7100         if(cps->alphaRank) AlphaRank(machineMove, 4);
7101         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7102                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7103             /* Machine move could not be parsed; ignore it. */
7104             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7105                     machineMove, cps->which);
7106             DisplayError(buf1, 0);
7107             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7108                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7109             if (gameMode == TwoMachinesPlay) {
7110               GameEnds(machineWhite ? BlackWins : WhiteWins,
7111                        buf1, GE_XBOARD);
7112             }
7113             return;
7114         }
7115
7116         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7117         /* So we have to redo legality test with true e.p. status here,  */
7118         /* to make sure an illegal e.p. capture does not slip through,   */
7119         /* to cause a forfeit on a justified illegal-move complaint      */
7120         /* of the opponent.                                              */
7121         if( gameMode==TwoMachinesPlay && appData.testLegality
7122             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7123                                                               ) {
7124            ChessMove moveType;
7125            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7126                              fromY, fromX, toY, toX, promoChar);
7127             if (appData.debugMode) {
7128                 int i;
7129                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7130                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7131                 fprintf(debugFP, "castling rights\n");
7132             }
7133             if(moveType == IllegalMove) {
7134                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7135                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7136                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7137                            buf1, GE_XBOARD);
7138                 return;
7139            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7140            /* [HGM] Kludge to handle engines that send FRC-style castling
7141               when they shouldn't (like TSCP-Gothic) */
7142            switch(moveType) {
7143              case WhiteASideCastleFR:
7144              case BlackASideCastleFR:
7145                toX+=2;
7146                currentMoveString[2]++;
7147                break;
7148              case WhiteHSideCastleFR:
7149              case BlackHSideCastleFR:
7150                toX--;
7151                currentMoveString[2]--;
7152                break;
7153              default: ; // nothing to do, but suppresses warning of pedantic compilers
7154            }
7155         }
7156         hintRequested = FALSE;
7157         lastHint[0] = NULLCHAR;
7158         bookRequested = FALSE;
7159         /* Program may be pondering now */
7160         cps->maybeThinking = TRUE;
7161         if (cps->sendTime == 2) cps->sendTime = 1;
7162         if (cps->offeredDraw) cps->offeredDraw--;
7163
7164         /* currentMoveString is set as a side-effect of ParseOneMove */
7165         strcpy(machineMove, currentMoveString);
7166         strcat(machineMove, "\n");
7167         strcpy(moveList[forwardMostMove], machineMove);
7168
7169         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7170
7171         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7172         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7173             int count = 0;
7174
7175             while( count < adjudicateLossPlies ) {
7176                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7177
7178                 if( count & 1 ) {
7179                     score = -score; /* Flip score for winning side */
7180                 }
7181
7182                 if( score > adjudicateLossThreshold ) {
7183                     break;
7184                 }
7185
7186                 count++;
7187             }
7188
7189             if( count >= adjudicateLossPlies ) {
7190                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7191
7192                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7193                     "Xboard adjudication", 
7194                     GE_XBOARD );
7195
7196                 return;
7197             }
7198         }
7199
7200         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7201
7202 #if ZIPPY
7203         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7204             first.initDone) {
7205           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7206                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7207                 SendToICS("draw ");
7208                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7209           }
7210           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7211           ics_user_moved = 1;
7212           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7213                 char buf[3*MSG_SIZ];
7214
7215                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7216                         programStats.score / 100.,
7217                         programStats.depth,
7218                         programStats.time / 100.,
7219                         (unsigned int)programStats.nodes,
7220                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7221                         programStats.movelist);
7222                 SendToICS(buf);
7223 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7224           }
7225         }
7226 #endif
7227
7228         /* [AS] Save move info and clear stats for next move */
7229         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7230         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7231         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7232         ClearProgramStats();
7233         thinkOutput[0] = NULLCHAR;
7234         hiddenThinkOutputState = 0;
7235
7236         bookHit = NULL;
7237         if (gameMode == TwoMachinesPlay) {
7238             /* [HGM] relaying draw offers moved to after reception of move */
7239             /* and interpreting offer as claim if it brings draw condition */
7240             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7241                 SendToProgram("draw\n", cps->other);
7242             }
7243             if (cps->other->sendTime) {
7244                 SendTimeRemaining(cps->other,
7245                                   cps->other->twoMachinesColor[0] == 'w');
7246             }
7247             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7248             if (firstMove && !bookHit) {
7249                 firstMove = FALSE;
7250                 if (cps->other->useColors) {
7251                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7252                 }
7253                 SendToProgram("go\n", cps->other);
7254             }
7255             cps->other->maybeThinking = TRUE;
7256         }
7257
7258         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7259         
7260         if (!pausing && appData.ringBellAfterMoves) {
7261             RingBell();
7262         }
7263
7264         /* 
7265          * Reenable menu items that were disabled while
7266          * machine was thinking
7267          */
7268         if (gameMode != TwoMachinesPlay)
7269             SetUserThinkingEnables();
7270
7271         // [HGM] book: after book hit opponent has received move and is now in force mode
7272         // force the book reply into it, and then fake that it outputted this move by jumping
7273         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7274         if(bookHit) {
7275                 static char bookMove[MSG_SIZ]; // a bit generous?
7276
7277                 strcpy(bookMove, "move ");
7278                 strcat(bookMove, bookHit);
7279                 message = bookMove;
7280                 cps = cps->other;
7281                 programStats.nodes = programStats.depth = programStats.time = 
7282                 programStats.score = programStats.got_only_move = 0;
7283                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7284
7285                 if(cps->lastPing != cps->lastPong) {
7286                     savedMessage = message; // args for deferred call
7287                     savedState = cps;
7288                     ScheduleDelayedEvent(DeferredBookMove, 10);
7289                     return;
7290                 }
7291                 goto FakeBookMove;
7292         }
7293
7294         return;
7295     }
7296
7297     /* Set special modes for chess engines.  Later something general
7298      *  could be added here; for now there is just one kludge feature,
7299      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7300      *  when "xboard" is given as an interactive command.
7301      */
7302     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7303         cps->useSigint = FALSE;
7304         cps->useSigterm = FALSE;
7305     }
7306     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7307       ParseFeatures(message+8, cps);
7308       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7309     }
7310
7311     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7312      * want this, I was asked to put it in, and obliged.
7313      */
7314     if (!strncmp(message, "setboard ", 9)) {
7315         Board initial_position;
7316
7317         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7318
7319         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7320             DisplayError(_("Bad FEN received from engine"), 0);
7321             return ;
7322         } else {
7323            Reset(TRUE, FALSE);
7324            CopyBoard(boards[0], initial_position);
7325            initialRulePlies = FENrulePlies;
7326            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7327            else gameMode = MachinePlaysBlack;                 
7328            DrawPosition(FALSE, boards[currentMove]);
7329         }
7330         return;
7331     }
7332
7333     /*
7334      * Look for communication commands
7335      */
7336     if (!strncmp(message, "telluser ", 9)) {
7337         DisplayNote(message + 9);
7338         return;
7339     }
7340     if (!strncmp(message, "tellusererror ", 14)) {
7341         cps->userError = 1;
7342         DisplayError(message + 14, 0);
7343         return;
7344     }
7345     if (!strncmp(message, "tellopponent ", 13)) {
7346       if (appData.icsActive) {
7347         if (loggedOn) {
7348           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7349           SendToICS(buf1);
7350         }
7351       } else {
7352         DisplayNote(message + 13);
7353       }
7354       return;
7355     }
7356     if (!strncmp(message, "tellothers ", 11)) {
7357       if (appData.icsActive) {
7358         if (loggedOn) {
7359           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7360           SendToICS(buf1);
7361         }
7362       }
7363       return;
7364     }
7365     if (!strncmp(message, "tellall ", 8)) {
7366       if (appData.icsActive) {
7367         if (loggedOn) {
7368           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7369           SendToICS(buf1);
7370         }
7371       } else {
7372         DisplayNote(message + 8);
7373       }
7374       return;
7375     }
7376     if (strncmp(message, "warning", 7) == 0) {
7377         /* Undocumented feature, use tellusererror in new code */
7378         DisplayError(message, 0);
7379         return;
7380     }
7381     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7382         strcpy(realname, cps->tidy);
7383         strcat(realname, " query");
7384         AskQuestion(realname, buf2, buf1, cps->pr);
7385         return;
7386     }
7387     /* Commands from the engine directly to ICS.  We don't allow these to be 
7388      *  sent until we are logged on. Crafty kibitzes have been known to 
7389      *  interfere with the login process.
7390      */
7391     if (loggedOn) {
7392         if (!strncmp(message, "tellics ", 8)) {
7393             SendToICS(message + 8);
7394             SendToICS("\n");
7395             return;
7396         }
7397         if (!strncmp(message, "tellicsnoalias ", 15)) {
7398             SendToICS(ics_prefix);
7399             SendToICS(message + 15);
7400             SendToICS("\n");
7401             return;
7402         }
7403         /* The following are for backward compatibility only */
7404         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7405             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7406             SendToICS(ics_prefix);
7407             SendToICS(message);
7408             SendToICS("\n");
7409             return;
7410         }
7411     }
7412     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7413         return;
7414     }
7415     /*
7416      * If the move is illegal, cancel it and redraw the board.
7417      * Also deal with other error cases.  Matching is rather loose
7418      * here to accommodate engines written before the spec.
7419      */
7420     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7421         strncmp(message, "Error", 5) == 0) {
7422         if (StrStr(message, "name") || 
7423             StrStr(message, "rating") || StrStr(message, "?") ||
7424             StrStr(message, "result") || StrStr(message, "board") ||
7425             StrStr(message, "bk") || StrStr(message, "computer") ||
7426             StrStr(message, "variant") || StrStr(message, "hint") ||
7427             StrStr(message, "random") || StrStr(message, "depth") ||
7428             StrStr(message, "accepted")) {
7429             return;
7430         }
7431         if (StrStr(message, "protover")) {
7432           /* Program is responding to input, so it's apparently done
7433              initializing, and this error message indicates it is
7434              protocol version 1.  So we don't need to wait any longer
7435              for it to initialize and send feature commands. */
7436           FeatureDone(cps, 1);
7437           cps->protocolVersion = 1;
7438           return;
7439         }
7440         cps->maybeThinking = FALSE;
7441
7442         if (StrStr(message, "draw")) {
7443             /* Program doesn't have "draw" command */
7444             cps->sendDrawOffers = 0;
7445             return;
7446         }
7447         if (cps->sendTime != 1 &&
7448             (StrStr(message, "time") || StrStr(message, "otim"))) {
7449           /* Program apparently doesn't have "time" or "otim" command */
7450           cps->sendTime = 0;
7451           return;
7452         }
7453         if (StrStr(message, "analyze")) {
7454             cps->analysisSupport = FALSE;
7455             cps->analyzing = FALSE;
7456             Reset(FALSE, TRUE);
7457             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7458             DisplayError(buf2, 0);
7459             return;
7460         }
7461         if (StrStr(message, "(no matching move)st")) {
7462           /* Special kludge for GNU Chess 4 only */
7463           cps->stKludge = TRUE;
7464           SendTimeControl(cps, movesPerSession, timeControl,
7465                           timeIncrement, appData.searchDepth,
7466                           searchTime);
7467           return;
7468         }
7469         if (StrStr(message, "(no matching move)sd")) {
7470           /* Special kludge for GNU Chess 4 only */
7471           cps->sdKludge = TRUE;
7472           SendTimeControl(cps, movesPerSession, timeControl,
7473                           timeIncrement, appData.searchDepth,
7474                           searchTime);
7475           return;
7476         }
7477         if (!StrStr(message, "llegal")) {
7478             return;
7479         }
7480         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7481             gameMode == IcsIdle) return;
7482         if (forwardMostMove <= backwardMostMove) return;
7483         if (pausing) PauseEvent();
7484       if(appData.forceIllegal) {
7485             // [HGM] illegal: machine refused move; force position after move into it
7486           SendToProgram("force\n", cps);
7487           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7488                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7489                 // when black is to move, while there might be nothing on a2 or black
7490                 // might already have the move. So send the board as if white has the move.
7491                 // But first we must change the stm of the engine, as it refused the last move
7492                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7493                 if(WhiteOnMove(forwardMostMove)) {
7494                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7495                     SendBoard(cps, forwardMostMove); // kludgeless board
7496                 } else {
7497                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7498                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7499                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7500                 }
7501           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7502             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7503                  gameMode == TwoMachinesPlay)
7504               SendToProgram("go\n", cps);
7505             return;
7506       } else
7507         if (gameMode == PlayFromGameFile) {
7508             /* Stop reading this game file */
7509             gameMode = EditGame;
7510             ModeHighlight();
7511         }
7512         currentMove = forwardMostMove-1;
7513         DisplayMove(currentMove-1); /* before DisplayMoveError */
7514         SwitchClocks(forwardMostMove-1); // [HGM] race
7515         DisplayBothClocks();
7516         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7517                 parseList[currentMove], cps->which);
7518         DisplayMoveError(buf1);
7519         DrawPosition(FALSE, boards[currentMove]);
7520
7521         /* [HGM] illegal-move claim should forfeit game when Xboard */
7522         /* only passes fully legal moves                            */
7523         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7524             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7525                                 "False illegal-move claim", GE_XBOARD );
7526         }
7527         return;
7528     }
7529     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7530         /* Program has a broken "time" command that
7531            outputs a string not ending in newline.
7532            Don't use it. */
7533         cps->sendTime = 0;
7534     }
7535     
7536     /*
7537      * If chess program startup fails, exit with an error message.
7538      * Attempts to recover here are futile.
7539      */
7540     if ((StrStr(message, "unknown host") != NULL)
7541         || (StrStr(message, "No remote directory") != NULL)
7542         || (StrStr(message, "not found") != NULL)
7543         || (StrStr(message, "No such file") != NULL)
7544         || (StrStr(message, "can't alloc") != NULL)
7545         || (StrStr(message, "Permission denied") != NULL)) {
7546
7547         cps->maybeThinking = FALSE;
7548         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7549                 cps->which, cps->program, cps->host, message);
7550         RemoveInputSource(cps->isr);
7551         DisplayFatalError(buf1, 0, 1);
7552         return;
7553     }
7554     
7555     /* 
7556      * Look for hint output
7557      */
7558     if (sscanf(message, "Hint: %s", buf1) == 1) {
7559         if (cps == &first && hintRequested) {
7560             hintRequested = FALSE;
7561             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7562                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7563                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7564                                     PosFlags(forwardMostMove),
7565                                     fromY, fromX, toY, toX, promoChar, buf1);
7566                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7567                 DisplayInformation(buf2);
7568             } else {
7569                 /* Hint move could not be parsed!? */
7570               snprintf(buf2, sizeof(buf2),
7571                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7572                         buf1, cps->which);
7573                 DisplayError(buf2, 0);
7574             }
7575         } else {
7576             strcpy(lastHint, buf1);
7577         }
7578         return;
7579     }
7580
7581     /*
7582      * Ignore other messages if game is not in progress
7583      */
7584     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7585         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7586
7587     /*
7588      * look for win, lose, draw, or draw offer
7589      */
7590     if (strncmp(message, "1-0", 3) == 0) {
7591         char *p, *q, *r = "";
7592         p = strchr(message, '{');
7593         if (p) {
7594             q = strchr(p, '}');
7595             if (q) {
7596                 *q = NULLCHAR;
7597                 r = p + 1;
7598             }
7599         }
7600         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7601         return;
7602     } else if (strncmp(message, "0-1", 3) == 0) {
7603         char *p, *q, *r = "";
7604         p = strchr(message, '{');
7605         if (p) {
7606             q = strchr(p, '}');
7607             if (q) {
7608                 *q = NULLCHAR;
7609                 r = p + 1;
7610             }
7611         }
7612         /* Kludge for Arasan 4.1 bug */
7613         if (strcmp(r, "Black resigns") == 0) {
7614             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7615             return;
7616         }
7617         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7618         return;
7619     } else if (strncmp(message, "1/2", 3) == 0) {
7620         char *p, *q, *r = "";
7621         p = strchr(message, '{');
7622         if (p) {
7623             q = strchr(p, '}');
7624             if (q) {
7625                 *q = NULLCHAR;
7626                 r = p + 1;
7627             }
7628         }
7629             
7630         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7631         return;
7632
7633     } else if (strncmp(message, "White resign", 12) == 0) {
7634         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7635         return;
7636     } else if (strncmp(message, "Black resign", 12) == 0) {
7637         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7638         return;
7639     } else if (strncmp(message, "White matches", 13) == 0 ||
7640                strncmp(message, "Black matches", 13) == 0   ) {
7641         /* [HGM] ignore GNUShogi noises */
7642         return;
7643     } else if (strncmp(message, "White", 5) == 0 &&
7644                message[5] != '(' &&
7645                StrStr(message, "Black") == NULL) {
7646         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7647         return;
7648     } else if (strncmp(message, "Black", 5) == 0 &&
7649                message[5] != '(') {
7650         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7651         return;
7652     } else if (strcmp(message, "resign") == 0 ||
7653                strcmp(message, "computer resigns") == 0) {
7654         switch (gameMode) {
7655           case MachinePlaysBlack:
7656           case IcsPlayingBlack:
7657             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7658             break;
7659           case MachinePlaysWhite:
7660           case IcsPlayingWhite:
7661             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7662             break;
7663           case TwoMachinesPlay:
7664             if (cps->twoMachinesColor[0] == 'w')
7665               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7666             else
7667               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7668             break;
7669           default:
7670             /* can't happen */
7671             break;
7672         }
7673         return;
7674     } else if (strncmp(message, "opponent mates", 14) == 0) {
7675         switch (gameMode) {
7676           case MachinePlaysBlack:
7677           case IcsPlayingBlack:
7678             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7679             break;
7680           case MachinePlaysWhite:
7681           case IcsPlayingWhite:
7682             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7683             break;
7684           case TwoMachinesPlay:
7685             if (cps->twoMachinesColor[0] == 'w')
7686               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7687             else
7688               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7689             break;
7690           default:
7691             /* can't happen */
7692             break;
7693         }
7694         return;
7695     } else if (strncmp(message, "computer mates", 14) == 0) {
7696         switch (gameMode) {
7697           case MachinePlaysBlack:
7698           case IcsPlayingBlack:
7699             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7700             break;
7701           case MachinePlaysWhite:
7702           case IcsPlayingWhite:
7703             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7704             break;
7705           case TwoMachinesPlay:
7706             if (cps->twoMachinesColor[0] == 'w')
7707               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7708             else
7709               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7710             break;
7711           default:
7712             /* can't happen */
7713             break;
7714         }
7715         return;
7716     } else if (strncmp(message, "checkmate", 9) == 0) {
7717         if (WhiteOnMove(forwardMostMove)) {
7718             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7719         } else {
7720             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7721         }
7722         return;
7723     } else if (strstr(message, "Draw") != NULL ||
7724                strstr(message, "game is a draw") != NULL) {
7725         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7726         return;
7727     } else if (strstr(message, "offer") != NULL &&
7728                strstr(message, "draw") != NULL) {
7729 #if ZIPPY
7730         if (appData.zippyPlay && first.initDone) {
7731             /* Relay offer to ICS */
7732             SendToICS(ics_prefix);
7733             SendToICS("draw\n");
7734         }
7735 #endif
7736         cps->offeredDraw = 2; /* valid until this engine moves twice */
7737         if (gameMode == TwoMachinesPlay) {
7738             if (cps->other->offeredDraw) {
7739                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7740             /* [HGM] in two-machine mode we delay relaying draw offer      */
7741             /* until after we also have move, to see if it is really claim */
7742             }
7743         } else if (gameMode == MachinePlaysWhite ||
7744                    gameMode == MachinePlaysBlack) {
7745           if (userOfferedDraw) {
7746             DisplayInformation(_("Machine accepts your draw offer"));
7747             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7748           } else {
7749             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7750           }
7751         }
7752     }
7753
7754     
7755     /*
7756      * Look for thinking output
7757      */
7758     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7759           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7760                                 ) {
7761         int plylev, mvleft, mvtot, curscore, time;
7762         char mvname[MOVE_LEN];
7763         u64 nodes; // [DM]
7764         char plyext;
7765         int ignore = FALSE;
7766         int prefixHint = FALSE;
7767         mvname[0] = NULLCHAR;
7768
7769         switch (gameMode) {
7770           case MachinePlaysBlack:
7771           case IcsPlayingBlack:
7772             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7773             break;
7774           case MachinePlaysWhite:
7775           case IcsPlayingWhite:
7776             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7777             break;
7778           case AnalyzeMode:
7779           case AnalyzeFile:
7780             break;
7781           case IcsObserving: /* [DM] icsEngineAnalyze */
7782             if (!appData.icsEngineAnalyze) ignore = TRUE;
7783             break;
7784           case TwoMachinesPlay:
7785             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7786                 ignore = TRUE;
7787             }
7788             break;
7789           default:
7790             ignore = TRUE;
7791             break;
7792         }
7793
7794         if (!ignore) {
7795             buf1[0] = NULLCHAR;
7796             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7797                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7798
7799                 if (plyext != ' ' && plyext != '\t') {
7800                     time *= 100;
7801                 }
7802
7803                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7804                 if( cps->scoreIsAbsolute && 
7805                     ( gameMode == MachinePlaysBlack ||
7806                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7807                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7808                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7809                      !WhiteOnMove(currentMove)
7810                     ) )
7811                 {
7812                     curscore = -curscore;
7813                 }
7814
7815
7816                 programStats.depth = plylev;
7817                 programStats.nodes = nodes;
7818                 programStats.time = time;
7819                 programStats.score = curscore;
7820                 programStats.got_only_move = 0;
7821
7822                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7823                         int ticklen;
7824
7825                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7826                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7827                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7828                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7829                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7830                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7831                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7832                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7833                 }
7834
7835                 /* Buffer overflow protection */
7836                 if (buf1[0] != NULLCHAR) {
7837                     if (strlen(buf1) >= sizeof(programStats.movelist)
7838                         && appData.debugMode) {
7839                         fprintf(debugFP,
7840                                 "PV is too long; using the first %u bytes.\n",
7841                                 (unsigned) sizeof(programStats.movelist) - 1);
7842                     }
7843
7844                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7845                 } else {
7846                     sprintf(programStats.movelist, " no PV\n");
7847                 }
7848
7849                 if (programStats.seen_stat) {
7850                     programStats.ok_to_send = 1;
7851                 }
7852
7853                 if (strchr(programStats.movelist, '(') != NULL) {
7854                     programStats.line_is_book = 1;
7855                     programStats.nr_moves = 0;
7856                     programStats.moves_left = 0;
7857                 } else {
7858                     programStats.line_is_book = 0;
7859                 }
7860
7861                 SendProgramStatsToFrontend( cps, &programStats );
7862
7863                 /* 
7864                     [AS] Protect the thinkOutput buffer from overflow... this
7865                     is only useful if buf1 hasn't overflowed first!
7866                 */
7867                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7868                         plylev, 
7869                         (gameMode == TwoMachinesPlay ?
7870                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7871                         ((double) curscore) / 100.0,
7872                         prefixHint ? lastHint : "",
7873                         prefixHint ? " " : "" );
7874
7875                 if( buf1[0] != NULLCHAR ) {
7876                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7877
7878                     if( strlen(buf1) > max_len ) {
7879                         if( appData.debugMode) {
7880                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7881                         }
7882                         buf1[max_len+1] = '\0';
7883                     }
7884
7885                     strcat( thinkOutput, buf1 );
7886                 }
7887
7888                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7889                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7890                     DisplayMove(currentMove - 1);
7891                 }
7892                 return;
7893
7894             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7895                 /* crafty (9.25+) says "(only move) <move>"
7896                  * if there is only 1 legal move
7897                  */
7898                 sscanf(p, "(only move) %s", buf1);
7899                 sprintf(thinkOutput, "%s (only move)", buf1);
7900                 sprintf(programStats.movelist, "%s (only move)", buf1);
7901                 programStats.depth = 1;
7902                 programStats.nr_moves = 1;
7903                 programStats.moves_left = 1;
7904                 programStats.nodes = 1;
7905                 programStats.time = 1;
7906                 programStats.got_only_move = 1;
7907
7908                 /* Not really, but we also use this member to
7909                    mean "line isn't going to change" (Crafty
7910                    isn't searching, so stats won't change) */
7911                 programStats.line_is_book = 1;
7912
7913                 SendProgramStatsToFrontend( cps, &programStats );
7914                 
7915                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7916                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7917                     DisplayMove(currentMove - 1);
7918                 }
7919                 return;
7920             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7921                               &time, &nodes, &plylev, &mvleft,
7922                               &mvtot, mvname) >= 5) {
7923                 /* The stat01: line is from Crafty (9.29+) in response
7924                    to the "." command */
7925                 programStats.seen_stat = 1;
7926                 cps->maybeThinking = TRUE;
7927
7928                 if (programStats.got_only_move || !appData.periodicUpdates)
7929                   return;
7930
7931                 programStats.depth = plylev;
7932                 programStats.time = time;
7933                 programStats.nodes = nodes;
7934                 programStats.moves_left = mvleft;
7935                 programStats.nr_moves = mvtot;
7936                 strcpy(programStats.move_name, mvname);
7937                 programStats.ok_to_send = 1;
7938                 programStats.movelist[0] = '\0';
7939
7940                 SendProgramStatsToFrontend( cps, &programStats );
7941
7942                 return;
7943
7944             } else if (strncmp(message,"++",2) == 0) {
7945                 /* Crafty 9.29+ outputs this */
7946                 programStats.got_fail = 2;
7947                 return;
7948
7949             } else if (strncmp(message,"--",2) == 0) {
7950                 /* Crafty 9.29+ outputs this */
7951                 programStats.got_fail = 1;
7952                 return;
7953
7954             } else if (thinkOutput[0] != NULLCHAR &&
7955                        strncmp(message, "    ", 4) == 0) {
7956                 unsigned message_len;
7957
7958                 p = message;
7959                 while (*p && *p == ' ') p++;
7960
7961                 message_len = strlen( p );
7962
7963                 /* [AS] Avoid buffer overflow */
7964                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7965                     strcat(thinkOutput, " ");
7966                     strcat(thinkOutput, p);
7967                 }
7968
7969                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7970                     strcat(programStats.movelist, " ");
7971                     strcat(programStats.movelist, p);
7972                 }
7973
7974                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7975                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7976                     DisplayMove(currentMove - 1);
7977                 }
7978                 return;
7979             }
7980         }
7981         else {
7982             buf1[0] = NULLCHAR;
7983
7984             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7985                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7986             {
7987                 ChessProgramStats cpstats;
7988
7989                 if (plyext != ' ' && plyext != '\t') {
7990                     time *= 100;
7991                 }
7992
7993                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7994                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7995                     curscore = -curscore;
7996                 }
7997
7998                 cpstats.depth = plylev;
7999                 cpstats.nodes = nodes;
8000                 cpstats.time = time;
8001                 cpstats.score = curscore;
8002                 cpstats.got_only_move = 0;
8003                 cpstats.movelist[0] = '\0';
8004
8005                 if (buf1[0] != NULLCHAR) {
8006                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
8007                 }
8008
8009                 cpstats.ok_to_send = 0;
8010                 cpstats.line_is_book = 0;
8011                 cpstats.nr_moves = 0;
8012                 cpstats.moves_left = 0;
8013
8014                 SendProgramStatsToFrontend( cps, &cpstats );
8015             }
8016         }
8017     }
8018 }
8019
8020
8021 /* Parse a game score from the character string "game", and
8022    record it as the history of the current game.  The game
8023    score is NOT assumed to start from the standard position. 
8024    The display is not updated in any way.
8025    */
8026 void
8027 ParseGameHistory(game)
8028      char *game;
8029 {
8030     ChessMove moveType;
8031     int fromX, fromY, toX, toY, boardIndex;
8032     char promoChar;
8033     char *p, *q;
8034     char buf[MSG_SIZ];
8035
8036     if (appData.debugMode)
8037       fprintf(debugFP, "Parsing game history: %s\n", game);
8038
8039     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8040     gameInfo.site = StrSave(appData.icsHost);
8041     gameInfo.date = PGNDate();
8042     gameInfo.round = StrSave("-");
8043
8044     /* Parse out names of players */
8045     while (*game == ' ') game++;
8046     p = buf;
8047     while (*game != ' ') *p++ = *game++;
8048     *p = NULLCHAR;
8049     gameInfo.white = StrSave(buf);
8050     while (*game == ' ') game++;
8051     p = buf;
8052     while (*game != ' ' && *game != '\n') *p++ = *game++;
8053     *p = NULLCHAR;
8054     gameInfo.black = StrSave(buf);
8055
8056     /* Parse moves */
8057     boardIndex = blackPlaysFirst ? 1 : 0;
8058     yynewstr(game);
8059     for (;;) {
8060         yyboardindex = boardIndex;
8061         moveType = (ChessMove) yylex();
8062         switch (moveType) {
8063           case IllegalMove:             /* maybe suicide chess, etc. */
8064   if (appData.debugMode) {
8065     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8066     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8067     setbuf(debugFP, NULL);
8068   }
8069           case WhitePromotionChancellor:
8070           case BlackPromotionChancellor:
8071           case WhitePromotionArchbishop:
8072           case BlackPromotionArchbishop:
8073           case WhitePromotionQueen:
8074           case BlackPromotionQueen:
8075           case WhitePromotionRook:
8076           case BlackPromotionRook:
8077           case WhitePromotionBishop:
8078           case BlackPromotionBishop:
8079           case WhitePromotionKnight:
8080           case BlackPromotionKnight:
8081           case WhitePromotionKing:
8082           case BlackPromotionKing:
8083           case NormalMove:
8084           case WhiteCapturesEnPassant:
8085           case BlackCapturesEnPassant:
8086           case WhiteKingSideCastle:
8087           case WhiteQueenSideCastle:
8088           case BlackKingSideCastle:
8089           case BlackQueenSideCastle:
8090           case WhiteKingSideCastleWild:
8091           case WhiteQueenSideCastleWild:
8092           case BlackKingSideCastleWild:
8093           case BlackQueenSideCastleWild:
8094           /* PUSH Fabien */
8095           case WhiteHSideCastleFR:
8096           case WhiteASideCastleFR:
8097           case BlackHSideCastleFR:
8098           case BlackASideCastleFR:
8099           /* POP Fabien */
8100             fromX = currentMoveString[0] - AAA;
8101             fromY = currentMoveString[1] - ONE;
8102             toX = currentMoveString[2] - AAA;
8103             toY = currentMoveString[3] - ONE;
8104             promoChar = currentMoveString[4];
8105             break;
8106           case WhiteDrop:
8107           case BlackDrop:
8108             fromX = moveType == WhiteDrop ?
8109               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8110             (int) CharToPiece(ToLower(currentMoveString[0]));
8111             fromY = DROP_RANK;
8112             toX = currentMoveString[2] - AAA;
8113             toY = currentMoveString[3] - ONE;
8114             promoChar = NULLCHAR;
8115             break;
8116           case AmbiguousMove:
8117             /* bug? */
8118             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8119   if (appData.debugMode) {
8120     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8121     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8122     setbuf(debugFP, NULL);
8123   }
8124             DisplayError(buf, 0);
8125             return;
8126           case ImpossibleMove:
8127             /* bug? */
8128             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8129   if (appData.debugMode) {
8130     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8131     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8132     setbuf(debugFP, NULL);
8133   }
8134             DisplayError(buf, 0);
8135             return;
8136           case (ChessMove) 0:   /* end of file */
8137             if (boardIndex < backwardMostMove) {
8138                 /* Oops, gap.  How did that happen? */
8139                 DisplayError(_("Gap in move list"), 0);
8140                 return;
8141             }
8142             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8143             if (boardIndex > forwardMostMove) {
8144                 forwardMostMove = boardIndex;
8145             }
8146             return;
8147           case ElapsedTime:
8148             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8149                 strcat(parseList[boardIndex-1], " ");
8150                 strcat(parseList[boardIndex-1], yy_text);
8151             }
8152             continue;
8153           case Comment:
8154           case PGNTag:
8155           case NAG:
8156           default:
8157             /* ignore */
8158             continue;
8159           case WhiteWins:
8160           case BlackWins:
8161           case GameIsDrawn:
8162           case GameUnfinished:
8163             if (gameMode == IcsExamining) {
8164                 if (boardIndex < backwardMostMove) {
8165                     /* Oops, gap.  How did that happen? */
8166                     return;
8167                 }
8168                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8169                 return;
8170             }
8171             gameInfo.result = moveType;
8172             p = strchr(yy_text, '{');
8173             if (p == NULL) p = strchr(yy_text, '(');
8174             if (p == NULL) {
8175                 p = yy_text;
8176                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8177             } else {
8178                 q = strchr(p, *p == '{' ? '}' : ')');
8179                 if (q != NULL) *q = NULLCHAR;
8180                 p++;
8181             }
8182             gameInfo.resultDetails = StrSave(p);
8183             continue;
8184         }
8185         if (boardIndex >= forwardMostMove &&
8186             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8187             backwardMostMove = blackPlaysFirst ? 1 : 0;
8188             return;
8189         }
8190         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8191                                  fromY, fromX, toY, toX, promoChar,
8192                                  parseList[boardIndex]);
8193         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8194         /* currentMoveString is set as a side-effect of yylex */
8195         strcpy(moveList[boardIndex], currentMoveString);
8196         strcat(moveList[boardIndex], "\n");
8197         boardIndex++;
8198         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8199         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8200           case MT_NONE:
8201           case MT_STALEMATE:
8202           default:
8203             break;
8204           case MT_CHECK:
8205             if(gameInfo.variant != VariantShogi)
8206                 strcat(parseList[boardIndex - 1], "+");
8207             break;
8208           case MT_CHECKMATE:
8209           case MT_STAINMATE:
8210             strcat(parseList[boardIndex - 1], "#");
8211             break;
8212         }
8213     }
8214 }
8215
8216
8217 /* Apply a move to the given board  */
8218 void
8219 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8220      int fromX, fromY, toX, toY;
8221      int promoChar;
8222      Board board;
8223 {
8224   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8225   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8226
8227     /* [HGM] compute & store e.p. status and castling rights for new position */
8228     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8229     { int i;
8230
8231       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8232       oldEP = (signed char)board[EP_STATUS];
8233       board[EP_STATUS] = EP_NONE;
8234
8235       if( board[toY][toX] != EmptySquare ) 
8236            board[EP_STATUS] = EP_CAPTURE;  
8237
8238       if( board[fromY][fromX] == WhitePawn ) {
8239            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8240                board[EP_STATUS] = EP_PAWN_MOVE;
8241            if( toY-fromY==2) {
8242                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8243                         gameInfo.variant != VariantBerolina || toX < fromX)
8244                       board[EP_STATUS] = toX | berolina;
8245                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8246                         gameInfo.variant != VariantBerolina || toX > fromX) 
8247                       board[EP_STATUS] = toX;
8248            }
8249       } else 
8250       if( board[fromY][fromX] == BlackPawn ) {
8251            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8252                board[EP_STATUS] = EP_PAWN_MOVE; 
8253            if( toY-fromY== -2) {
8254                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8255                         gameInfo.variant != VariantBerolina || toX < fromX)
8256                       board[EP_STATUS] = toX | berolina;
8257                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8258                         gameInfo.variant != VariantBerolina || toX > fromX) 
8259                       board[EP_STATUS] = toX;
8260            }
8261        }
8262
8263        for(i=0; i<nrCastlingRights; i++) {
8264            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8265               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8266              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8267        }
8268
8269     }
8270
8271   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8272   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8273        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8274          
8275   if (fromX == toX && fromY == toY) return;
8276
8277   if (fromY == DROP_RANK) {
8278         /* must be first */
8279         piece = board[toY][toX] = (ChessSquare) fromX;
8280   } else {
8281      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8282      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8283      if(gameInfo.variant == VariantKnightmate)
8284          king += (int) WhiteUnicorn - (int) WhiteKing;
8285
8286     /* Code added by Tord: */
8287     /* FRC castling assumed when king captures friendly rook. */
8288     if (board[fromY][fromX] == WhiteKing &&
8289              board[toY][toX] == WhiteRook) {
8290       board[fromY][fromX] = EmptySquare;
8291       board[toY][toX] = EmptySquare;
8292       if(toX > fromX) {
8293         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8294       } else {
8295         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8296       }
8297     } else if (board[fromY][fromX] == BlackKing &&
8298                board[toY][toX] == BlackRook) {
8299       board[fromY][fromX] = EmptySquare;
8300       board[toY][toX] = EmptySquare;
8301       if(toX > fromX) {
8302         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8303       } else {
8304         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8305       }
8306     /* End of code added by Tord */
8307
8308     } else if (board[fromY][fromX] == king
8309         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8310         && toY == fromY && toX > fromX+1) {
8311         board[fromY][fromX] = EmptySquare;
8312         board[toY][toX] = king;
8313         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8314         board[fromY][BOARD_RGHT-1] = EmptySquare;
8315     } else if (board[fromY][fromX] == king
8316         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8317                && toY == fromY && toX < fromX-1) {
8318         board[fromY][fromX] = EmptySquare;
8319         board[toY][toX] = king;
8320         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8321         board[fromY][BOARD_LEFT] = EmptySquare;
8322     } else if (board[fromY][fromX] == WhitePawn
8323                && toY >= BOARD_HEIGHT-promoRank
8324                && gameInfo.variant != VariantXiangqi
8325                ) {
8326         /* white pawn promotion */
8327         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8328         if (board[toY][toX] == EmptySquare) {
8329             board[toY][toX] = WhiteQueen;
8330         }
8331         if(gameInfo.variant==VariantBughouse ||
8332            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8333             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8334         board[fromY][fromX] = EmptySquare;
8335     } else if ((fromY == BOARD_HEIGHT-4)
8336                && (toX != fromX)
8337                && gameInfo.variant != VariantXiangqi
8338                && gameInfo.variant != VariantBerolina
8339                && (board[fromY][fromX] == WhitePawn)
8340                && (board[toY][toX] == EmptySquare)) {
8341         board[fromY][fromX] = EmptySquare;
8342         board[toY][toX] = WhitePawn;
8343         captured = board[toY - 1][toX];
8344         board[toY - 1][toX] = EmptySquare;
8345     } else if ((fromY == BOARD_HEIGHT-4)
8346                && (toX == fromX)
8347                && gameInfo.variant == VariantBerolina
8348                && (board[fromY][fromX] == WhitePawn)
8349                && (board[toY][toX] == EmptySquare)) {
8350         board[fromY][fromX] = EmptySquare;
8351         board[toY][toX] = WhitePawn;
8352         if(oldEP & EP_BEROLIN_A) {
8353                 captured = board[fromY][fromX-1];
8354                 board[fromY][fromX-1] = EmptySquare;
8355         }else{  captured = board[fromY][fromX+1];
8356                 board[fromY][fromX+1] = EmptySquare;
8357         }
8358     } else if (board[fromY][fromX] == king
8359         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8360                && toY == fromY && toX > fromX+1) {
8361         board[fromY][fromX] = EmptySquare;
8362         board[toY][toX] = king;
8363         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8364         board[fromY][BOARD_RGHT-1] = EmptySquare;
8365     } else if (board[fromY][fromX] == king
8366         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8367                && toY == fromY && toX < fromX-1) {
8368         board[fromY][fromX] = EmptySquare;
8369         board[toY][toX] = king;
8370         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8371         board[fromY][BOARD_LEFT] = EmptySquare;
8372     } else if (fromY == 7 && fromX == 3
8373                && board[fromY][fromX] == BlackKing
8374                && toY == 7 && toX == 5) {
8375         board[fromY][fromX] = EmptySquare;
8376         board[toY][toX] = BlackKing;
8377         board[fromY][7] = EmptySquare;
8378         board[toY][4] = BlackRook;
8379     } else if (fromY == 7 && fromX == 3
8380                && board[fromY][fromX] == BlackKing
8381                && toY == 7 && toX == 1) {
8382         board[fromY][fromX] = EmptySquare;
8383         board[toY][toX] = BlackKing;
8384         board[fromY][0] = EmptySquare;
8385         board[toY][2] = BlackRook;
8386     } else if (board[fromY][fromX] == BlackPawn
8387                && toY < promoRank
8388                && gameInfo.variant != VariantXiangqi
8389                ) {
8390         /* black pawn promotion */
8391         board[toY][toX] = CharToPiece(ToLower(promoChar));
8392         if (board[toY][toX] == EmptySquare) {
8393             board[toY][toX] = BlackQueen;
8394         }
8395         if(gameInfo.variant==VariantBughouse ||
8396            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8397             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8398         board[fromY][fromX] = EmptySquare;
8399     } else if ((fromY == 3)
8400                && (toX != fromX)
8401                && gameInfo.variant != VariantXiangqi
8402                && gameInfo.variant != VariantBerolina
8403                && (board[fromY][fromX] == BlackPawn)
8404                && (board[toY][toX] == EmptySquare)) {
8405         board[fromY][fromX] = EmptySquare;
8406         board[toY][toX] = BlackPawn;
8407         captured = board[toY + 1][toX];
8408         board[toY + 1][toX] = EmptySquare;
8409     } else if ((fromY == 3)
8410                && (toX == fromX)
8411                && gameInfo.variant == VariantBerolina
8412                && (board[fromY][fromX] == BlackPawn)
8413                && (board[toY][toX] == EmptySquare)) {
8414         board[fromY][fromX] = EmptySquare;
8415         board[toY][toX] = BlackPawn;
8416         if(oldEP & EP_BEROLIN_A) {
8417                 captured = board[fromY][fromX-1];
8418                 board[fromY][fromX-1] = EmptySquare;
8419         }else{  captured = board[fromY][fromX+1];
8420                 board[fromY][fromX+1] = EmptySquare;
8421         }
8422     } else {
8423         board[toY][toX] = board[fromY][fromX];
8424         board[fromY][fromX] = EmptySquare;
8425     }
8426
8427     /* [HGM] now we promote for Shogi, if needed */
8428     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8429         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8430   }
8431
8432     if (gameInfo.holdingsWidth != 0) {
8433
8434       /* !!A lot more code needs to be written to support holdings  */
8435       /* [HGM] OK, so I have written it. Holdings are stored in the */
8436       /* penultimate board files, so they are automaticlly stored   */
8437       /* in the game history.                                       */
8438       if (fromY == DROP_RANK) {
8439         /* Delete from holdings, by decreasing count */
8440         /* and erasing image if necessary            */
8441         p = (int) fromX;
8442         if(p < (int) BlackPawn) { /* white drop */
8443              p -= (int)WhitePawn;
8444                  p = PieceToNumber((ChessSquare)p);
8445              if(p >= gameInfo.holdingsSize) p = 0;
8446              if(--board[p][BOARD_WIDTH-2] <= 0)
8447                   board[p][BOARD_WIDTH-1] = EmptySquare;
8448              if((int)board[p][BOARD_WIDTH-2] < 0)
8449                         board[p][BOARD_WIDTH-2] = 0;
8450         } else {                  /* black drop */
8451              p -= (int)BlackPawn;
8452                  p = PieceToNumber((ChessSquare)p);
8453              if(p >= gameInfo.holdingsSize) p = 0;
8454              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8455                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8456              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8457                         board[BOARD_HEIGHT-1-p][1] = 0;
8458         }
8459       }
8460       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8461           && gameInfo.variant != VariantBughouse        ) {
8462         /* [HGM] holdings: Add to holdings, if holdings exist */
8463         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8464                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8465                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8466         }
8467         p = (int) captured;
8468         if (p >= (int) BlackPawn) {
8469           p -= (int)BlackPawn;
8470           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8471                   /* in Shogi restore piece to its original  first */
8472                   captured = (ChessSquare) (DEMOTED captured);
8473                   p = DEMOTED p;
8474           }
8475           p = PieceToNumber((ChessSquare)p);
8476           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8477           board[p][BOARD_WIDTH-2]++;
8478           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8479         } else {
8480           p -= (int)WhitePawn;
8481           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8482                   captured = (ChessSquare) (DEMOTED captured);
8483                   p = DEMOTED p;
8484           }
8485           p = PieceToNumber((ChessSquare)p);
8486           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8487           board[BOARD_HEIGHT-1-p][1]++;
8488           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8489         }
8490       }
8491     } else if (gameInfo.variant == VariantAtomic) {
8492       if (captured != EmptySquare) {
8493         int y, x;
8494         for (y = toY-1; y <= toY+1; y++) {
8495           for (x = toX-1; x <= toX+1; x++) {
8496             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8497                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8498               board[y][x] = EmptySquare;
8499             }
8500           }
8501         }
8502         board[toY][toX] = EmptySquare;
8503       }
8504     }
8505     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8506         /* [HGM] Shogi promotions */
8507         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8508     }
8509
8510     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8511                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8512         // [HGM] superchess: take promotion piece out of holdings
8513         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8514         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8515             if(!--board[k][BOARD_WIDTH-2])
8516                 board[k][BOARD_WIDTH-1] = EmptySquare;
8517         } else {
8518             if(!--board[BOARD_HEIGHT-1-k][1])
8519                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8520         }
8521     }
8522
8523 }
8524
8525 /* Updates forwardMostMove */
8526 void
8527 MakeMove(fromX, fromY, toX, toY, promoChar)
8528      int fromX, fromY, toX, toY;
8529      int promoChar;
8530 {
8531 //    forwardMostMove++; // [HGM] bare: moved downstream
8532
8533     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8534         int timeLeft; static int lastLoadFlag=0; int king, piece;
8535         piece = boards[forwardMostMove][fromY][fromX];
8536         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8537         if(gameInfo.variant == VariantKnightmate)
8538             king += (int) WhiteUnicorn - (int) WhiteKing;
8539         if(forwardMostMove == 0) {
8540             if(blackPlaysFirst) 
8541                 fprintf(serverMoves, "%s;", second.tidy);
8542             fprintf(serverMoves, "%s;", first.tidy);
8543             if(!blackPlaysFirst) 
8544                 fprintf(serverMoves, "%s;", second.tidy);
8545         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8546         lastLoadFlag = loadFlag;
8547         // print base move
8548         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8549         // print castling suffix
8550         if( toY == fromY && piece == king ) {
8551             if(toX-fromX > 1)
8552                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8553             if(fromX-toX >1)
8554                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8555         }
8556         // e.p. suffix
8557         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8558              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8559              boards[forwardMostMove][toY][toX] == EmptySquare
8560              && fromX != toX )
8561                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8562         // promotion suffix
8563         if(promoChar != NULLCHAR)
8564                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8565         if(!loadFlag) {
8566             fprintf(serverMoves, "/%d/%d",
8567                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8568             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8569             else                      timeLeft = blackTimeRemaining/1000;
8570             fprintf(serverMoves, "/%d", timeLeft);
8571         }
8572         fflush(serverMoves);
8573     }
8574
8575     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8576       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8577                         0, 1);
8578       return;
8579     }
8580     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8581     if (commentList[forwardMostMove+1] != NULL) {
8582         free(commentList[forwardMostMove+1]);
8583         commentList[forwardMostMove+1] = NULL;
8584     }
8585     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8586     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8587     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8588     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8589     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8590     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8591     gameInfo.result = GameUnfinished;
8592     if (gameInfo.resultDetails != NULL) {
8593         free(gameInfo.resultDetails);
8594         gameInfo.resultDetails = NULL;
8595     }
8596     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8597                               moveList[forwardMostMove - 1]);
8598     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8599                              PosFlags(forwardMostMove - 1),
8600                              fromY, fromX, toY, toX, promoChar,
8601                              parseList[forwardMostMove - 1]);
8602     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8603       case MT_NONE:
8604       case MT_STALEMATE:
8605       default:
8606         break;
8607       case MT_CHECK:
8608         if(gameInfo.variant != VariantShogi)
8609             strcat(parseList[forwardMostMove - 1], "+");
8610         break;
8611       case MT_CHECKMATE:
8612       case MT_STAINMATE:
8613         strcat(parseList[forwardMostMove - 1], "#");
8614         break;
8615     }
8616     if (appData.debugMode) {
8617         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8618     }
8619
8620 }
8621
8622 /* Updates currentMove if not pausing */
8623 void
8624 ShowMove(fromX, fromY, toX, toY)
8625 {
8626     int instant = (gameMode == PlayFromGameFile) ?
8627         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8628     if(appData.noGUI) return;
8629     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8630         if (!instant) {
8631             if (forwardMostMove == currentMove + 1) {
8632                 AnimateMove(boards[forwardMostMove - 1],
8633                             fromX, fromY, toX, toY);
8634             }
8635             if (appData.highlightLastMove) {
8636                 SetHighlights(fromX, fromY, toX, toY);
8637             }
8638         }
8639         currentMove = forwardMostMove;
8640     }
8641
8642     if (instant) return;
8643
8644     DisplayMove(currentMove - 1);
8645     DrawPosition(FALSE, boards[currentMove]);
8646     DisplayBothClocks();
8647     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8648 }
8649
8650 void SendEgtPath(ChessProgramState *cps)
8651 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8652         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8653
8654         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8655
8656         while(*p) {
8657             char c, *q = name+1, *r, *s;
8658
8659             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8660             while(*p && *p != ',') *q++ = *p++;
8661             *q++ = ':'; *q = 0;
8662             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8663                 strcmp(name, ",nalimov:") == 0 ) {
8664                 // take nalimov path from the menu-changeable option first, if it is defined
8665                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8666                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8667             } else
8668             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8669                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8670                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8671                 s = r = StrStr(s, ":") + 1; // beginning of path info
8672                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8673                 c = *r; *r = 0;             // temporarily null-terminate path info
8674                     *--q = 0;               // strip of trailig ':' from name
8675                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8676                 *r = c;
8677                 SendToProgram(buf,cps);     // send egtbpath command for this format
8678             }
8679             if(*p == ',') p++; // read away comma to position for next format name
8680         }
8681 }
8682
8683 void
8684 InitChessProgram(cps, setup)
8685      ChessProgramState *cps;
8686      int setup; /* [HGM] needed to setup FRC opening position */
8687 {
8688     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8689     if (appData.noChessProgram) return;
8690     hintRequested = FALSE;
8691     bookRequested = FALSE;
8692
8693     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8694     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8695     if(cps->memSize) { /* [HGM] memory */
8696         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8697         SendToProgram(buf, cps);
8698     }
8699     SendEgtPath(cps); /* [HGM] EGT */
8700     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8701         sprintf(buf, "cores %d\n", appData.smpCores);
8702         SendToProgram(buf, cps);
8703     }
8704
8705     SendToProgram(cps->initString, cps);
8706     if (gameInfo.variant != VariantNormal &&
8707         gameInfo.variant != VariantLoadable
8708         /* [HGM] also send variant if board size non-standard */
8709         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8710                                             ) {
8711       char *v = VariantName(gameInfo.variant);
8712       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8713         /* [HGM] in protocol 1 we have to assume all variants valid */
8714         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8715         DisplayFatalError(buf, 0, 1);
8716         return;
8717       }
8718
8719       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8720       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8721       if( gameInfo.variant == VariantXiangqi )
8722            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8723       if( gameInfo.variant == VariantShogi )
8724            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8725       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8726            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8727       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8728                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8729            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8730       if( gameInfo.variant == VariantCourier )
8731            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8732       if( gameInfo.variant == VariantSuper )
8733            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8734       if( gameInfo.variant == VariantGreat )
8735            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8736
8737       if(overruled) {
8738            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8739                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8740            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8741            if(StrStr(cps->variants, b) == NULL) { 
8742                // specific sized variant not known, check if general sizing allowed
8743                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8744                    if(StrStr(cps->variants, "boardsize") == NULL) {
8745                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8746                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8747                        DisplayFatalError(buf, 0, 1);
8748                        return;
8749                    }
8750                    /* [HGM] here we really should compare with the maximum supported board size */
8751                }
8752            }
8753       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8754       sprintf(buf, "variant %s\n", b);
8755       SendToProgram(buf, cps);
8756     }
8757     currentlyInitializedVariant = gameInfo.variant;
8758
8759     /* [HGM] send opening position in FRC to first engine */
8760     if(setup) {
8761           SendToProgram("force\n", cps);
8762           SendBoard(cps, 0);
8763           /* engine is now in force mode! Set flag to wake it up after first move. */
8764           setboardSpoiledMachineBlack = 1;
8765     }
8766
8767     if (cps->sendICS) {
8768       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8769       SendToProgram(buf, cps);
8770     }
8771     cps->maybeThinking = FALSE;
8772     cps->offeredDraw = 0;
8773     if (!appData.icsActive) {
8774         SendTimeControl(cps, movesPerSession, timeControl,
8775                         timeIncrement, appData.searchDepth,
8776                         searchTime);
8777     }
8778     if (appData.showThinking 
8779         // [HGM] thinking: four options require thinking output to be sent
8780         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8781                                 ) {
8782         SendToProgram("post\n", cps);
8783     }
8784     SendToProgram("hard\n", cps);
8785     if (!appData.ponderNextMove) {
8786         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8787            it without being sure what state we are in first.  "hard"
8788            is not a toggle, so that one is OK.
8789          */
8790         SendToProgram("easy\n", cps);
8791     }
8792     if (cps->usePing) {
8793       sprintf(buf, "ping %d\n", ++cps->lastPing);
8794       SendToProgram(buf, cps);
8795     }
8796     cps->initDone = TRUE;
8797 }   
8798
8799
8800 void
8801 StartChessProgram(cps)
8802      ChessProgramState *cps;
8803 {
8804     char buf[MSG_SIZ];
8805     int err;
8806
8807     if (appData.noChessProgram) return;
8808     cps->initDone = FALSE;
8809
8810     if (strcmp(cps->host, "localhost") == 0) {
8811         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8812     } else if (*appData.remoteShell == NULLCHAR) {
8813         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8814     } else {
8815         if (*appData.remoteUser == NULLCHAR) {
8816           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8817                     cps->program);
8818         } else {
8819           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8820                     cps->host, appData.remoteUser, cps->program);
8821         }
8822         err = StartChildProcess(buf, "", &cps->pr);
8823     }
8824     
8825     if (err != 0) {
8826         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8827         DisplayFatalError(buf, err, 1);
8828         cps->pr = NoProc;
8829         cps->isr = NULL;
8830         return;
8831     }
8832     
8833     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8834     if (cps->protocolVersion > 1) {
8835       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8836       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8837       cps->comboCnt = 0;  //                and values of combo boxes
8838       SendToProgram(buf, cps);
8839     } else {
8840       SendToProgram("xboard\n", cps);
8841     }
8842 }
8843
8844
8845 void
8846 TwoMachinesEventIfReady P((void))
8847 {
8848   if (first.lastPing != first.lastPong) {
8849     DisplayMessage("", _("Waiting for first chess program"));
8850     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8851     return;
8852   }
8853   if (second.lastPing != second.lastPong) {
8854     DisplayMessage("", _("Waiting for second chess program"));
8855     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8856     return;
8857   }
8858   ThawUI();
8859   TwoMachinesEvent();
8860 }
8861
8862 void
8863 NextMatchGame P((void))
8864 {
8865     int index; /* [HGM] autoinc: step load index during match */
8866     Reset(FALSE, TRUE);
8867     if (*appData.loadGameFile != NULLCHAR) {
8868         index = appData.loadGameIndex;
8869         if(index < 0) { // [HGM] autoinc
8870             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8871             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8872         } 
8873         LoadGameFromFile(appData.loadGameFile,
8874                          index,
8875                          appData.loadGameFile, FALSE);
8876     } else if (*appData.loadPositionFile != NULLCHAR) {
8877         index = appData.loadPositionIndex;
8878         if(index < 0) { // [HGM] autoinc
8879             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8880             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8881         } 
8882         LoadPositionFromFile(appData.loadPositionFile,
8883                              index,
8884                              appData.loadPositionFile);
8885     }
8886     TwoMachinesEventIfReady();
8887 }
8888
8889 void UserAdjudicationEvent( int result )
8890 {
8891     ChessMove gameResult = GameIsDrawn;
8892
8893     if( result > 0 ) {
8894         gameResult = WhiteWins;
8895     }
8896     else if( result < 0 ) {
8897         gameResult = BlackWins;
8898     }
8899
8900     if( gameMode == TwoMachinesPlay ) {
8901         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8902     }
8903 }
8904
8905
8906 // [HGM] save: calculate checksum of game to make games easily identifiable
8907 int StringCheckSum(char *s)
8908 {
8909         int i = 0;
8910         if(s==NULL) return 0;
8911         while(*s) i = i*259 + *s++;
8912         return i;
8913 }
8914
8915 int GameCheckSum()
8916 {
8917         int i, sum=0;
8918         for(i=backwardMostMove; i<forwardMostMove; i++) {
8919                 sum += pvInfoList[i].depth;
8920                 sum += StringCheckSum(parseList[i]);
8921                 sum += StringCheckSum(commentList[i]);
8922                 sum *= 261;
8923         }
8924         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8925         return sum + StringCheckSum(commentList[i]);
8926 } // end of save patch
8927
8928 void
8929 GameEnds(result, resultDetails, whosays)
8930      ChessMove result;
8931      char *resultDetails;
8932      int whosays;
8933 {
8934     GameMode nextGameMode;
8935     int isIcsGame;
8936     char buf[MSG_SIZ];
8937
8938     if(endingGame) return; /* [HGM] crash: forbid recursion */
8939     endingGame = 1;
8940     if(twoBoards) { // [HGM] dual: switch back to one board
8941         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
8942         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
8943     }
8944     if (appData.debugMode) {
8945       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8946               result, resultDetails ? resultDetails : "(null)", whosays);
8947     }
8948
8949     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8950
8951     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8952         /* If we are playing on ICS, the server decides when the
8953            game is over, but the engine can offer to draw, claim 
8954            a draw, or resign. 
8955          */
8956 #if ZIPPY
8957         if (appData.zippyPlay && first.initDone) {
8958             if (result == GameIsDrawn) {
8959                 /* In case draw still needs to be claimed */
8960                 SendToICS(ics_prefix);
8961                 SendToICS("draw\n");
8962             } else if (StrCaseStr(resultDetails, "resign")) {
8963                 SendToICS(ics_prefix);
8964                 SendToICS("resign\n");
8965             }
8966         }
8967 #endif
8968         endingGame = 0; /* [HGM] crash */
8969         return;
8970     }
8971
8972     /* If we're loading the game from a file, stop */
8973     if (whosays == GE_FILE) {
8974       (void) StopLoadGameTimer();
8975       gameFileFP = NULL;
8976     }
8977
8978     /* Cancel draw offers */
8979     first.offeredDraw = second.offeredDraw = 0;
8980
8981     /* If this is an ICS game, only ICS can really say it's done;
8982        if not, anyone can. */
8983     isIcsGame = (gameMode == IcsPlayingWhite || 
8984                  gameMode == IcsPlayingBlack || 
8985                  gameMode == IcsObserving    || 
8986                  gameMode == IcsExamining);
8987
8988     if (!isIcsGame || whosays == GE_ICS) {
8989         /* OK -- not an ICS game, or ICS said it was done */
8990         StopClocks();
8991         if (!isIcsGame && !appData.noChessProgram) 
8992           SetUserThinkingEnables();
8993     
8994         /* [HGM] if a machine claims the game end we verify this claim */
8995         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8996             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8997                 char claimer;
8998                 ChessMove trueResult = (ChessMove) -1;
8999
9000                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9001                                             first.twoMachinesColor[0] :
9002                                             second.twoMachinesColor[0] ;
9003
9004                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9005                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9006                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9007                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9008                 } else
9009                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9010                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9011                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9012                 } else
9013                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9014                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9015                 }
9016
9017                 // now verify win claims, but not in drop games, as we don't understand those yet
9018                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9019                                                  || gameInfo.variant == VariantGreat) &&
9020                     (result == WhiteWins && claimer == 'w' ||
9021                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9022                       if (appData.debugMode) {
9023                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9024                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9025                       }
9026                       if(result != trueResult) {
9027                               sprintf(buf, "False win claim: '%s'", resultDetails);
9028                               result = claimer == 'w' ? BlackWins : WhiteWins;
9029                               resultDetails = buf;
9030                       }
9031                 } else
9032                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9033                     && (forwardMostMove <= backwardMostMove ||
9034                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9035                         (claimer=='b')==(forwardMostMove&1))
9036                                                                                   ) {
9037                       /* [HGM] verify: draws that were not flagged are false claims */
9038                       sprintf(buf, "False draw claim: '%s'", resultDetails);
9039                       result = claimer == 'w' ? BlackWins : WhiteWins;
9040                       resultDetails = buf;
9041                 }
9042                 /* (Claiming a loss is accepted no questions asked!) */
9043             }
9044             /* [HGM] bare: don't allow bare King to win */
9045             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9046                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9047                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9048                && result != GameIsDrawn)
9049             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9050                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9051                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9052                         if(p >= 0 && p <= (int)WhiteKing) k++;
9053                 }
9054                 if (appData.debugMode) {
9055                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9056                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9057                 }
9058                 if(k <= 1) {
9059                         result = GameIsDrawn;
9060                         sprintf(buf, "%s but bare king", resultDetails);
9061                         resultDetails = buf;
9062                 }
9063             }
9064         }
9065
9066
9067         if(serverMoves != NULL && !loadFlag) { char c = '=';
9068             if(result==WhiteWins) c = '+';
9069             if(result==BlackWins) c = '-';
9070             if(resultDetails != NULL)
9071                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9072         }
9073         if (resultDetails != NULL) {
9074             gameInfo.result = result;
9075             gameInfo.resultDetails = StrSave(resultDetails);
9076
9077             /* display last move only if game was not loaded from file */
9078             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9079                 DisplayMove(currentMove - 1);
9080     
9081             if (forwardMostMove != 0) {
9082                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9083                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9084                                                                 ) {
9085                     if (*appData.saveGameFile != NULLCHAR) {
9086                         SaveGameToFile(appData.saveGameFile, TRUE);
9087                     } else if (appData.autoSaveGames) {
9088                         AutoSaveGame();
9089                     }
9090                     if (*appData.savePositionFile != NULLCHAR) {
9091                         SavePositionToFile(appData.savePositionFile);
9092                     }
9093                 }
9094             }
9095
9096             /* Tell program how game ended in case it is learning */
9097             /* [HGM] Moved this to after saving the PGN, just in case */
9098             /* engine died and we got here through time loss. In that */
9099             /* case we will get a fatal error writing the pipe, which */
9100             /* would otherwise lose us the PGN.                       */
9101             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9102             /* output during GameEnds should never be fatal anymore   */
9103             if (gameMode == MachinePlaysWhite ||
9104                 gameMode == MachinePlaysBlack ||
9105                 gameMode == TwoMachinesPlay ||
9106                 gameMode == IcsPlayingWhite ||
9107                 gameMode == IcsPlayingBlack ||
9108                 gameMode == BeginningOfGame) {
9109                 char buf[MSG_SIZ];
9110                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9111                         resultDetails);
9112                 if (first.pr != NoProc) {
9113                     SendToProgram(buf, &first);
9114                 }
9115                 if (second.pr != NoProc &&
9116                     gameMode == TwoMachinesPlay) {
9117                     SendToProgram(buf, &second);
9118                 }
9119             }
9120         }
9121
9122         if (appData.icsActive) {
9123             if (appData.quietPlay &&
9124                 (gameMode == IcsPlayingWhite ||
9125                  gameMode == IcsPlayingBlack)) {
9126                 SendToICS(ics_prefix);
9127                 SendToICS("set shout 1\n");
9128             }
9129             nextGameMode = IcsIdle;
9130             ics_user_moved = FALSE;
9131             /* clean up premove.  It's ugly when the game has ended and the
9132              * premove highlights are still on the board.
9133              */
9134             if (gotPremove) {
9135               gotPremove = FALSE;
9136               ClearPremoveHighlights();
9137               DrawPosition(FALSE, boards[currentMove]);
9138             }
9139             if (whosays == GE_ICS) {
9140                 switch (result) {
9141                 case WhiteWins:
9142                     if (gameMode == IcsPlayingWhite)
9143                         PlayIcsWinSound();
9144                     else if(gameMode == IcsPlayingBlack)
9145                         PlayIcsLossSound();
9146                     break;
9147                 case BlackWins:
9148                     if (gameMode == IcsPlayingBlack)
9149                         PlayIcsWinSound();
9150                     else if(gameMode == IcsPlayingWhite)
9151                         PlayIcsLossSound();
9152                     break;
9153                 case GameIsDrawn:
9154                     PlayIcsDrawSound();
9155                     break;
9156                 default:
9157                     PlayIcsUnfinishedSound();
9158                 }
9159             }
9160         } else if (gameMode == EditGame ||
9161                    gameMode == PlayFromGameFile || 
9162                    gameMode == AnalyzeMode || 
9163                    gameMode == AnalyzeFile) {
9164             nextGameMode = gameMode;
9165         } else {
9166             nextGameMode = EndOfGame;
9167         }
9168         pausing = FALSE;
9169         ModeHighlight();
9170     } else {
9171         nextGameMode = gameMode;
9172     }
9173
9174     if (appData.noChessProgram) {
9175         gameMode = nextGameMode;
9176         ModeHighlight();
9177         endingGame = 0; /* [HGM] crash */
9178         return;
9179     }
9180
9181     if (first.reuse) {
9182         /* Put first chess program into idle state */
9183         if (first.pr != NoProc &&
9184             (gameMode == MachinePlaysWhite ||
9185              gameMode == MachinePlaysBlack ||
9186              gameMode == TwoMachinesPlay ||
9187              gameMode == IcsPlayingWhite ||
9188              gameMode == IcsPlayingBlack ||
9189              gameMode == BeginningOfGame)) {
9190             SendToProgram("force\n", &first);
9191             if (first.usePing) {
9192               char buf[MSG_SIZ];
9193               sprintf(buf, "ping %d\n", ++first.lastPing);
9194               SendToProgram(buf, &first);
9195             }
9196         }
9197     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9198         /* Kill off first chess program */
9199         if (first.isr != NULL)
9200           RemoveInputSource(first.isr);
9201         first.isr = NULL;
9202     
9203         if (first.pr != NoProc) {
9204             ExitAnalyzeMode();
9205             DoSleep( appData.delayBeforeQuit );
9206             SendToProgram("quit\n", &first);
9207             DoSleep( appData.delayAfterQuit );
9208             DestroyChildProcess(first.pr, first.useSigterm);
9209         }
9210         first.pr = NoProc;
9211     }
9212     if (second.reuse) {
9213         /* Put second chess program into idle state */
9214         if (second.pr != NoProc &&
9215             gameMode == TwoMachinesPlay) {
9216             SendToProgram("force\n", &second);
9217             if (second.usePing) {
9218               char buf[MSG_SIZ];
9219               sprintf(buf, "ping %d\n", ++second.lastPing);
9220               SendToProgram(buf, &second);
9221             }
9222         }
9223     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9224         /* Kill off second chess program */
9225         if (second.isr != NULL)
9226           RemoveInputSource(second.isr);
9227         second.isr = NULL;
9228     
9229         if (second.pr != NoProc) {
9230             DoSleep( appData.delayBeforeQuit );
9231             SendToProgram("quit\n", &second);
9232             DoSleep( appData.delayAfterQuit );
9233             DestroyChildProcess(second.pr, second.useSigterm);
9234         }
9235         second.pr = NoProc;
9236     }
9237
9238     if (matchMode && gameMode == TwoMachinesPlay) {
9239         switch (result) {
9240         case WhiteWins:
9241           if (first.twoMachinesColor[0] == 'w') {
9242             first.matchWins++;
9243           } else {
9244             second.matchWins++;
9245           }
9246           break;
9247         case BlackWins:
9248           if (first.twoMachinesColor[0] == 'b') {
9249             first.matchWins++;
9250           } else {
9251             second.matchWins++;
9252           }
9253           break;
9254         default:
9255           break;
9256         }
9257         if (matchGame < appData.matchGames) {
9258             char *tmp;
9259             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9260                 tmp = first.twoMachinesColor;
9261                 first.twoMachinesColor = second.twoMachinesColor;
9262                 second.twoMachinesColor = tmp;
9263             }
9264             gameMode = nextGameMode;
9265             matchGame++;
9266             if(appData.matchPause>10000 || appData.matchPause<10)
9267                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9268             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9269             endingGame = 0; /* [HGM] crash */
9270             return;
9271         } else {
9272             char buf[MSG_SIZ];
9273             gameMode = nextGameMode;
9274             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9275                     first.tidy, second.tidy,
9276                     first.matchWins, second.matchWins,
9277                     appData.matchGames - (first.matchWins + second.matchWins));
9278             DisplayFatalError(buf, 0, 0);
9279         }
9280     }
9281     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9282         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9283       ExitAnalyzeMode();
9284     gameMode = nextGameMode;
9285     ModeHighlight();
9286     endingGame = 0;  /* [HGM] crash */
9287 }
9288
9289 /* Assumes program was just initialized (initString sent).
9290    Leaves program in force mode. */
9291 void
9292 FeedMovesToProgram(cps, upto) 
9293      ChessProgramState *cps;
9294      int upto;
9295 {
9296     int i;
9297     
9298     if (appData.debugMode)
9299       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9300               startedFromSetupPosition ? "position and " : "",
9301               backwardMostMove, upto, cps->which);
9302     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9303         // [HGM] variantswitch: make engine aware of new variant
9304         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9305                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9306         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9307         SendToProgram(buf, cps);
9308         currentlyInitializedVariant = gameInfo.variant;
9309     }
9310     SendToProgram("force\n", cps);
9311     if (startedFromSetupPosition) {
9312         SendBoard(cps, backwardMostMove);
9313     if (appData.debugMode) {
9314         fprintf(debugFP, "feedMoves\n");
9315     }
9316     }
9317     for (i = backwardMostMove; i < upto; i++) {
9318         SendMoveToProgram(i, cps);
9319     }
9320 }
9321
9322
9323 void
9324 ResurrectChessProgram()
9325 {
9326      /* The chess program may have exited.
9327         If so, restart it and feed it all the moves made so far. */
9328
9329     if (appData.noChessProgram || first.pr != NoProc) return;
9330     
9331     StartChessProgram(&first);
9332     InitChessProgram(&first, FALSE);
9333     FeedMovesToProgram(&first, currentMove);
9334
9335     if (!first.sendTime) {
9336         /* can't tell gnuchess what its clock should read,
9337            so we bow to its notion. */
9338         ResetClocks();
9339         timeRemaining[0][currentMove] = whiteTimeRemaining;
9340         timeRemaining[1][currentMove] = blackTimeRemaining;
9341     }
9342
9343     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9344                 appData.icsEngineAnalyze) && first.analysisSupport) {
9345       SendToProgram("analyze\n", &first);
9346       first.analyzing = TRUE;
9347     }
9348 }
9349
9350 /*
9351  * Button procedures
9352  */
9353 void
9354 Reset(redraw, init)
9355      int redraw, init;
9356 {
9357     int i;
9358
9359     if (appData.debugMode) {
9360         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9361                 redraw, init, gameMode);
9362     }
9363     CleanupTail(); // [HGM] vari: delete any stored variations
9364     pausing = pauseExamInvalid = FALSE;
9365     startedFromSetupPosition = blackPlaysFirst = FALSE;
9366     firstMove = TRUE;
9367     whiteFlag = blackFlag = FALSE;
9368     userOfferedDraw = FALSE;
9369     hintRequested = bookRequested = FALSE;
9370     first.maybeThinking = FALSE;
9371     second.maybeThinking = FALSE;
9372     first.bookSuspend = FALSE; // [HGM] book
9373     second.bookSuspend = FALSE;
9374     thinkOutput[0] = NULLCHAR;
9375     lastHint[0] = NULLCHAR;
9376     ClearGameInfo(&gameInfo);
9377     gameInfo.variant = StringToVariant(appData.variant);
9378     ics_user_moved = ics_clock_paused = FALSE;
9379     ics_getting_history = H_FALSE;
9380     ics_gamenum = -1;
9381     white_holding[0] = black_holding[0] = NULLCHAR;
9382     ClearProgramStats();
9383     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9384     
9385     ResetFrontEnd();
9386     ClearHighlights();
9387     flipView = appData.flipView;
9388     ClearPremoveHighlights();
9389     gotPremove = FALSE;
9390     alarmSounded = FALSE;
9391
9392     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9393     if(appData.serverMovesName != NULL) {
9394         /* [HGM] prepare to make moves file for broadcasting */
9395         clock_t t = clock();
9396         if(serverMoves != NULL) fclose(serverMoves);
9397         serverMoves = fopen(appData.serverMovesName, "r");
9398         if(serverMoves != NULL) {
9399             fclose(serverMoves);
9400             /* delay 15 sec before overwriting, so all clients can see end */
9401             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9402         }
9403         serverMoves = fopen(appData.serverMovesName, "w");
9404     }
9405
9406     ExitAnalyzeMode();
9407     gameMode = BeginningOfGame;
9408     ModeHighlight();
9409     if(appData.icsActive) gameInfo.variant = VariantNormal;
9410     currentMove = forwardMostMove = backwardMostMove = 0;
9411     InitPosition(redraw);
9412     for (i = 0; i < MAX_MOVES; i++) {
9413         if (commentList[i] != NULL) {
9414             free(commentList[i]);
9415             commentList[i] = NULL;
9416         }
9417     }
9418     ResetClocks();
9419     timeRemaining[0][0] = whiteTimeRemaining;
9420     timeRemaining[1][0] = blackTimeRemaining;
9421     if (first.pr == NULL) {
9422         StartChessProgram(&first);
9423     }
9424     if (init) {
9425             InitChessProgram(&first, startedFromSetupPosition);
9426     }
9427     DisplayTitle("");
9428     DisplayMessage("", "");
9429     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9430     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9431 }
9432
9433 void
9434 AutoPlayGameLoop()
9435 {
9436     for (;;) {
9437         if (!AutoPlayOneMove())
9438           return;
9439         if (matchMode || appData.timeDelay == 0)
9440           continue;
9441         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9442           return;
9443         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9444         break;
9445     }
9446 }
9447
9448
9449 int
9450 AutoPlayOneMove()
9451 {
9452     int fromX, fromY, toX, toY;
9453
9454     if (appData.debugMode) {
9455       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9456     }
9457
9458     if (gameMode != PlayFromGameFile)
9459       return FALSE;
9460
9461     if (currentMove >= forwardMostMove) {
9462       gameMode = EditGame;
9463       ModeHighlight();
9464
9465       /* [AS] Clear current move marker at the end of a game */
9466       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9467
9468       return FALSE;
9469     }
9470     
9471     toX = moveList[currentMove][2] - AAA;
9472     toY = moveList[currentMove][3] - ONE;
9473
9474     if (moveList[currentMove][1] == '@') {
9475         if (appData.highlightLastMove) {
9476             SetHighlights(-1, -1, toX, toY);
9477         }
9478     } else {
9479         fromX = moveList[currentMove][0] - AAA;
9480         fromY = moveList[currentMove][1] - ONE;
9481
9482         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9483
9484         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9485
9486         if (appData.highlightLastMove) {
9487             SetHighlights(fromX, fromY, toX, toY);
9488         }
9489     }
9490     DisplayMove(currentMove);
9491     SendMoveToProgram(currentMove++, &first);
9492     DisplayBothClocks();
9493     DrawPosition(FALSE, boards[currentMove]);
9494     // [HGM] PV info: always display, routine tests if empty
9495     DisplayComment(currentMove - 1, commentList[currentMove]);
9496     return TRUE;
9497 }
9498
9499
9500 int
9501 LoadGameOneMove(readAhead)
9502      ChessMove readAhead;
9503 {
9504     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9505     char promoChar = NULLCHAR;
9506     ChessMove moveType;
9507     char move[MSG_SIZ];
9508     char *p, *q;
9509     
9510     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9511         gameMode != AnalyzeMode && gameMode != Training) {
9512         gameFileFP = NULL;
9513         return FALSE;
9514     }
9515     
9516     yyboardindex = forwardMostMove;
9517     if (readAhead != (ChessMove)0) {
9518       moveType = readAhead;
9519     } else {
9520       if (gameFileFP == NULL)
9521           return FALSE;
9522       moveType = (ChessMove) yylex();
9523     }
9524     
9525     done = FALSE;
9526     switch (moveType) {
9527       case Comment:
9528         if (appData.debugMode) 
9529           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9530         p = yy_text;
9531
9532         /* append the comment but don't display it */
9533         AppendComment(currentMove, p, FALSE);
9534         return TRUE;
9535
9536       case WhiteCapturesEnPassant:
9537       case BlackCapturesEnPassant:
9538       case WhitePromotionChancellor:
9539       case BlackPromotionChancellor:
9540       case WhitePromotionArchbishop:
9541       case BlackPromotionArchbishop:
9542       case WhitePromotionCentaur:
9543       case BlackPromotionCentaur:
9544       case WhitePromotionQueen:
9545       case BlackPromotionQueen:
9546       case WhitePromotionRook:
9547       case BlackPromotionRook:
9548       case WhitePromotionBishop:
9549       case BlackPromotionBishop:
9550       case WhitePromotionKnight:
9551       case BlackPromotionKnight:
9552       case WhitePromotionKing:
9553       case BlackPromotionKing:
9554       case NormalMove:
9555       case WhiteKingSideCastle:
9556       case WhiteQueenSideCastle:
9557       case BlackKingSideCastle:
9558       case BlackQueenSideCastle:
9559       case WhiteKingSideCastleWild:
9560       case WhiteQueenSideCastleWild:
9561       case BlackKingSideCastleWild:
9562       case BlackQueenSideCastleWild:
9563       /* PUSH Fabien */
9564       case WhiteHSideCastleFR:
9565       case WhiteASideCastleFR:
9566       case BlackHSideCastleFR:
9567       case BlackASideCastleFR:
9568       /* POP Fabien */
9569         if (appData.debugMode)
9570           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9571         fromX = currentMoveString[0] - AAA;
9572         fromY = currentMoveString[1] - ONE;
9573         toX = currentMoveString[2] - AAA;
9574         toY = currentMoveString[3] - ONE;
9575         promoChar = currentMoveString[4];
9576         break;
9577
9578       case WhiteDrop:
9579       case BlackDrop:
9580         if (appData.debugMode)
9581           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9582         fromX = moveType == WhiteDrop ?
9583           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9584         (int) CharToPiece(ToLower(currentMoveString[0]));
9585         fromY = DROP_RANK;
9586         toX = currentMoveString[2] - AAA;
9587         toY = currentMoveString[3] - ONE;
9588         break;
9589
9590       case WhiteWins:
9591       case BlackWins:
9592       case GameIsDrawn:
9593       case GameUnfinished:
9594         if (appData.debugMode)
9595           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9596         p = strchr(yy_text, '{');
9597         if (p == NULL) p = strchr(yy_text, '(');
9598         if (p == NULL) {
9599             p = yy_text;
9600             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9601         } else {
9602             q = strchr(p, *p == '{' ? '}' : ')');
9603             if (q != NULL) *q = NULLCHAR;
9604             p++;
9605         }
9606         GameEnds(moveType, p, GE_FILE);
9607         done = TRUE;
9608         if (cmailMsgLoaded) {
9609             ClearHighlights();
9610             flipView = WhiteOnMove(currentMove);
9611             if (moveType == GameUnfinished) flipView = !flipView;
9612             if (appData.debugMode)
9613               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9614         }
9615         break;
9616
9617       case (ChessMove) 0:       /* end of file */
9618         if (appData.debugMode)
9619           fprintf(debugFP, "Parser hit end of file\n");
9620         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9621           case MT_NONE:
9622           case MT_CHECK:
9623             break;
9624           case MT_CHECKMATE:
9625           case MT_STAINMATE:
9626             if (WhiteOnMove(currentMove)) {
9627                 GameEnds(BlackWins, "Black mates", GE_FILE);
9628             } else {
9629                 GameEnds(WhiteWins, "White mates", GE_FILE);
9630             }
9631             break;
9632           case MT_STALEMATE:
9633             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9634             break;
9635         }
9636         done = TRUE;
9637         break;
9638
9639       case MoveNumberOne:
9640         if (lastLoadGameStart == GNUChessGame) {
9641             /* GNUChessGames have numbers, but they aren't move numbers */
9642             if (appData.debugMode)
9643               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9644                       yy_text, (int) moveType);
9645             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9646         }
9647         /* else fall thru */
9648
9649       case XBoardGame:
9650       case GNUChessGame:
9651       case PGNTag:
9652         /* Reached start of next game in file */
9653         if (appData.debugMode)
9654           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9655         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9656           case MT_NONE:
9657           case MT_CHECK:
9658             break;
9659           case MT_CHECKMATE:
9660           case MT_STAINMATE:
9661             if (WhiteOnMove(currentMove)) {
9662                 GameEnds(BlackWins, "Black mates", GE_FILE);
9663             } else {
9664                 GameEnds(WhiteWins, "White mates", GE_FILE);
9665             }
9666             break;
9667           case MT_STALEMATE:
9668             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9669             break;
9670         }
9671         done = TRUE;
9672         break;
9673
9674       case PositionDiagram:     /* should not happen; ignore */
9675       case ElapsedTime:         /* ignore */
9676       case NAG:                 /* ignore */
9677         if (appData.debugMode)
9678           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9679                   yy_text, (int) moveType);
9680         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9681
9682       case IllegalMove:
9683         if (appData.testLegality) {
9684             if (appData.debugMode)
9685               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9686             sprintf(move, _("Illegal move: %d.%s%s"),
9687                     (forwardMostMove / 2) + 1,
9688                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9689             DisplayError(move, 0);
9690             done = TRUE;
9691         } else {
9692             if (appData.debugMode)
9693               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9694                       yy_text, currentMoveString);
9695             fromX = currentMoveString[0] - AAA;
9696             fromY = currentMoveString[1] - ONE;
9697             toX = currentMoveString[2] - AAA;
9698             toY = currentMoveString[3] - ONE;
9699             promoChar = currentMoveString[4];
9700         }
9701         break;
9702
9703       case AmbiguousMove:
9704         if (appData.debugMode)
9705           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9706         sprintf(move, _("Ambiguous move: %d.%s%s"),
9707                 (forwardMostMove / 2) + 1,
9708                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9709         DisplayError(move, 0);
9710         done = TRUE;
9711         break;
9712
9713       default:
9714       case ImpossibleMove:
9715         if (appData.debugMode)
9716           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9717         sprintf(move, _("Illegal move: %d.%s%s"),
9718                 (forwardMostMove / 2) + 1,
9719                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9720         DisplayError(move, 0);
9721         done = TRUE;
9722         break;
9723     }
9724
9725     if (done) {
9726         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9727             DrawPosition(FALSE, boards[currentMove]);
9728             DisplayBothClocks();
9729             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9730               DisplayComment(currentMove - 1, commentList[currentMove]);
9731         }
9732         (void) StopLoadGameTimer();
9733         gameFileFP = NULL;
9734         cmailOldMove = forwardMostMove;
9735         return FALSE;
9736     } else {
9737         /* currentMoveString is set as a side-effect of yylex */
9738         strcat(currentMoveString, "\n");
9739         strcpy(moveList[forwardMostMove], currentMoveString);
9740         
9741         thinkOutput[0] = NULLCHAR;
9742         MakeMove(fromX, fromY, toX, toY, promoChar);
9743         currentMove = forwardMostMove;
9744         return TRUE;
9745     }
9746 }
9747
9748 /* Load the nth game from the given file */
9749 int
9750 LoadGameFromFile(filename, n, title, useList)
9751      char *filename;
9752      int n;
9753      char *title;
9754      /*Boolean*/ int useList;
9755 {
9756     FILE *f;
9757     char buf[MSG_SIZ];
9758
9759     if (strcmp(filename, "-") == 0) {
9760         f = stdin;
9761         title = "stdin";
9762     } else {
9763         f = fopen(filename, "rb");
9764         if (f == NULL) {
9765           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9766             DisplayError(buf, errno);
9767             return FALSE;
9768         }
9769     }
9770     if (fseek(f, 0, 0) == -1) {
9771         /* f is not seekable; probably a pipe */
9772         useList = FALSE;
9773     }
9774     if (useList && n == 0) {
9775         int error = GameListBuild(f);
9776         if (error) {
9777             DisplayError(_("Cannot build game list"), error);
9778         } else if (!ListEmpty(&gameList) &&
9779                    ((ListGame *) gameList.tailPred)->number > 1) {
9780             GameListPopUp(f, title);
9781             return TRUE;
9782         }
9783         GameListDestroy();
9784         n = 1;
9785     }
9786     if (n == 0) n = 1;
9787     return LoadGame(f, n, title, FALSE);
9788 }
9789
9790
9791 void
9792 MakeRegisteredMove()
9793 {
9794     int fromX, fromY, toX, toY;
9795     char promoChar;
9796     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9797         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9798           case CMAIL_MOVE:
9799           case CMAIL_DRAW:
9800             if (appData.debugMode)
9801               fprintf(debugFP, "Restoring %s for game %d\n",
9802                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9803     
9804             thinkOutput[0] = NULLCHAR;
9805             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9806             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9807             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9808             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9809             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9810             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9811             MakeMove(fromX, fromY, toX, toY, promoChar);
9812             ShowMove(fromX, fromY, toX, toY);
9813               
9814             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9815               case MT_NONE:
9816               case MT_CHECK:
9817                 break;
9818                 
9819               case MT_CHECKMATE:
9820               case MT_STAINMATE:
9821                 if (WhiteOnMove(currentMove)) {
9822                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9823                 } else {
9824                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9825                 }
9826                 break;
9827                 
9828               case MT_STALEMATE:
9829                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9830                 break;
9831             }
9832
9833             break;
9834             
9835           case CMAIL_RESIGN:
9836             if (WhiteOnMove(currentMove)) {
9837                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9838             } else {
9839                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9840             }
9841             break;
9842             
9843           case CMAIL_ACCEPT:
9844             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9845             break;
9846               
9847           default:
9848             break;
9849         }
9850     }
9851
9852     return;
9853 }
9854
9855 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9856 int
9857 CmailLoadGame(f, gameNumber, title, useList)
9858      FILE *f;
9859      int gameNumber;
9860      char *title;
9861      int useList;
9862 {
9863     int retVal;
9864
9865     if (gameNumber > nCmailGames) {
9866         DisplayError(_("No more games in this message"), 0);
9867         return FALSE;
9868     }
9869     if (f == lastLoadGameFP) {
9870         int offset = gameNumber - lastLoadGameNumber;
9871         if (offset == 0) {
9872             cmailMsg[0] = NULLCHAR;
9873             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9874                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9875                 nCmailMovesRegistered--;
9876             }
9877             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9878             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9879                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9880             }
9881         } else {
9882             if (! RegisterMove()) return FALSE;
9883         }
9884     }
9885
9886     retVal = LoadGame(f, gameNumber, title, useList);
9887
9888     /* Make move registered during previous look at this game, if any */
9889     MakeRegisteredMove();
9890
9891     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9892         commentList[currentMove]
9893           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9894         DisplayComment(currentMove - 1, commentList[currentMove]);
9895     }
9896
9897     return retVal;
9898 }
9899
9900 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9901 int
9902 ReloadGame(offset)
9903      int offset;
9904 {
9905     int gameNumber = lastLoadGameNumber + offset;
9906     if (lastLoadGameFP == NULL) {
9907         DisplayError(_("No game has been loaded yet"), 0);
9908         return FALSE;
9909     }
9910     if (gameNumber <= 0) {
9911         DisplayError(_("Can't back up any further"), 0);
9912         return FALSE;
9913     }
9914     if (cmailMsgLoaded) {
9915         return CmailLoadGame(lastLoadGameFP, gameNumber,
9916                              lastLoadGameTitle, lastLoadGameUseList);
9917     } else {
9918         return LoadGame(lastLoadGameFP, gameNumber,
9919                         lastLoadGameTitle, lastLoadGameUseList);
9920     }
9921 }
9922
9923
9924
9925 /* Load the nth game from open file f */
9926 int
9927 LoadGame(f, gameNumber, title, useList)
9928      FILE *f;
9929      int gameNumber;
9930      char *title;
9931      int useList;
9932 {
9933     ChessMove cm;
9934     char buf[MSG_SIZ];
9935     int gn = gameNumber;
9936     ListGame *lg = NULL;
9937     int numPGNTags = 0;
9938     int err;
9939     GameMode oldGameMode;
9940     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9941
9942     if (appData.debugMode) 
9943         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9944
9945     if (gameMode == Training )
9946         SetTrainingModeOff();
9947
9948     oldGameMode = gameMode;
9949     if (gameMode != BeginningOfGame) {
9950       Reset(FALSE, TRUE);
9951     }
9952
9953     gameFileFP = f;
9954     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9955         fclose(lastLoadGameFP);
9956     }
9957
9958     if (useList) {
9959         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9960         
9961         if (lg) {
9962             fseek(f, lg->offset, 0);
9963             GameListHighlight(gameNumber);
9964             gn = 1;
9965         }
9966         else {
9967             DisplayError(_("Game number out of range"), 0);
9968             return FALSE;
9969         }
9970     } else {
9971         GameListDestroy();
9972         if (fseek(f, 0, 0) == -1) {
9973             if (f == lastLoadGameFP ?
9974                 gameNumber == lastLoadGameNumber + 1 :
9975                 gameNumber == 1) {
9976                 gn = 1;
9977             } else {
9978                 DisplayError(_("Can't seek on game file"), 0);
9979                 return FALSE;
9980             }
9981         }
9982     }
9983     lastLoadGameFP = f;
9984     lastLoadGameNumber = gameNumber;
9985     strcpy(lastLoadGameTitle, title);
9986     lastLoadGameUseList = useList;
9987
9988     yynewfile(f);
9989
9990     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9991       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9992                 lg->gameInfo.black);
9993             DisplayTitle(buf);
9994     } else if (*title != NULLCHAR) {
9995         if (gameNumber > 1) {
9996             sprintf(buf, "%s %d", title, gameNumber);
9997             DisplayTitle(buf);
9998         } else {
9999             DisplayTitle(title);
10000         }
10001     }
10002
10003     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10004         gameMode = PlayFromGameFile;
10005         ModeHighlight();
10006     }
10007
10008     currentMove = forwardMostMove = backwardMostMove = 0;
10009     CopyBoard(boards[0], initialPosition);
10010     StopClocks();
10011
10012     /*
10013      * Skip the first gn-1 games in the file.
10014      * Also skip over anything that precedes an identifiable 
10015      * start of game marker, to avoid being confused by 
10016      * garbage at the start of the file.  Currently 
10017      * recognized start of game markers are the move number "1",
10018      * the pattern "gnuchess .* game", the pattern
10019      * "^[#;%] [^ ]* game file", and a PGN tag block.  
10020      * A game that starts with one of the latter two patterns
10021      * will also have a move number 1, possibly
10022      * following a position diagram.
10023      * 5-4-02: Let's try being more lenient and allowing a game to
10024      * start with an unnumbered move.  Does that break anything?
10025      */
10026     cm = lastLoadGameStart = (ChessMove) 0;
10027     while (gn > 0) {
10028         yyboardindex = forwardMostMove;
10029         cm = (ChessMove) yylex();
10030         switch (cm) {
10031           case (ChessMove) 0:
10032             if (cmailMsgLoaded) {
10033                 nCmailGames = CMAIL_MAX_GAMES - gn;
10034             } else {
10035                 Reset(TRUE, TRUE);
10036                 DisplayError(_("Game not found in file"), 0);
10037             }
10038             return FALSE;
10039
10040           case GNUChessGame:
10041           case XBoardGame:
10042             gn--;
10043             lastLoadGameStart = cm;
10044             break;
10045             
10046           case MoveNumberOne:
10047             switch (lastLoadGameStart) {
10048               case GNUChessGame:
10049               case XBoardGame:
10050               case PGNTag:
10051                 break;
10052               case MoveNumberOne:
10053               case (ChessMove) 0:
10054                 gn--;           /* count this game */
10055                 lastLoadGameStart = cm;
10056                 break;
10057               default:
10058                 /* impossible */
10059                 break;
10060             }
10061             break;
10062
10063           case PGNTag:
10064             switch (lastLoadGameStart) {
10065               case GNUChessGame:
10066               case PGNTag:
10067               case MoveNumberOne:
10068               case (ChessMove) 0:
10069                 gn--;           /* count this game */
10070                 lastLoadGameStart = cm;
10071                 break;
10072               case XBoardGame:
10073                 lastLoadGameStart = cm; /* game counted already */
10074                 break;
10075               default:
10076                 /* impossible */
10077                 break;
10078             }
10079             if (gn > 0) {
10080                 do {
10081                     yyboardindex = forwardMostMove;
10082                     cm = (ChessMove) yylex();
10083                 } while (cm == PGNTag || cm == Comment);
10084             }
10085             break;
10086
10087           case WhiteWins:
10088           case BlackWins:
10089           case GameIsDrawn:
10090             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10091                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10092                     != CMAIL_OLD_RESULT) {
10093                     nCmailResults ++ ;
10094                     cmailResult[  CMAIL_MAX_GAMES
10095                                 - gn - 1] = CMAIL_OLD_RESULT;
10096                 }
10097             }
10098             break;
10099
10100           case NormalMove:
10101             /* Only a NormalMove can be at the start of a game
10102              * without a position diagram. */
10103             if (lastLoadGameStart == (ChessMove) 0) {
10104               gn--;
10105               lastLoadGameStart = MoveNumberOne;
10106             }
10107             break;
10108
10109           default:
10110             break;
10111         }
10112     }
10113     
10114     if (appData.debugMode)
10115       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10116
10117     if (cm == XBoardGame) {
10118         /* Skip any header junk before position diagram and/or move 1 */
10119         for (;;) {
10120             yyboardindex = forwardMostMove;
10121             cm = (ChessMove) yylex();
10122
10123             if (cm == (ChessMove) 0 ||
10124                 cm == GNUChessGame || cm == XBoardGame) {
10125                 /* Empty game; pretend end-of-file and handle later */
10126                 cm = (ChessMove) 0;
10127                 break;
10128             }
10129
10130             if (cm == MoveNumberOne || cm == PositionDiagram ||
10131                 cm == PGNTag || cm == Comment)
10132               break;
10133         }
10134     } else if (cm == GNUChessGame) {
10135         if (gameInfo.event != NULL) {
10136             free(gameInfo.event);
10137         }
10138         gameInfo.event = StrSave(yy_text);
10139     }   
10140
10141     startedFromSetupPosition = FALSE;
10142     while (cm == PGNTag) {
10143         if (appData.debugMode) 
10144           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10145         err = ParsePGNTag(yy_text, &gameInfo);
10146         if (!err) numPGNTags++;
10147
10148         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10149         if(gameInfo.variant != oldVariant) {
10150             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10151             InitPosition(TRUE);
10152             oldVariant = gameInfo.variant;
10153             if (appData.debugMode) 
10154               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10155         }
10156
10157
10158         if (gameInfo.fen != NULL) {
10159           Board initial_position;
10160           startedFromSetupPosition = TRUE;
10161           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10162             Reset(TRUE, TRUE);
10163             DisplayError(_("Bad FEN position in file"), 0);
10164             return FALSE;
10165           }
10166           CopyBoard(boards[0], initial_position);
10167           if (blackPlaysFirst) {
10168             currentMove = forwardMostMove = backwardMostMove = 1;
10169             CopyBoard(boards[1], initial_position);
10170             strcpy(moveList[0], "");
10171             strcpy(parseList[0], "");
10172             timeRemaining[0][1] = whiteTimeRemaining;
10173             timeRemaining[1][1] = blackTimeRemaining;
10174             if (commentList[0] != NULL) {
10175               commentList[1] = commentList[0];
10176               commentList[0] = NULL;
10177             }
10178           } else {
10179             currentMove = forwardMostMove = backwardMostMove = 0;
10180           }
10181           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10182           {   int i;
10183               initialRulePlies = FENrulePlies;
10184               for( i=0; i< nrCastlingRights; i++ )
10185                   initialRights[i] = initial_position[CASTLING][i];
10186           }
10187           yyboardindex = forwardMostMove;
10188           free(gameInfo.fen);
10189           gameInfo.fen = NULL;
10190         }
10191
10192         yyboardindex = forwardMostMove;
10193         cm = (ChessMove) yylex();
10194
10195         /* Handle comments interspersed among the tags */
10196         while (cm == Comment) {
10197             char *p;
10198             if (appData.debugMode) 
10199               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10200             p = yy_text;
10201             AppendComment(currentMove, p, FALSE);
10202             yyboardindex = forwardMostMove;
10203             cm = (ChessMove) yylex();
10204         }
10205     }
10206
10207     /* don't rely on existence of Event tag since if game was
10208      * pasted from clipboard the Event tag may not exist
10209      */
10210     if (numPGNTags > 0){
10211         char *tags;
10212         if (gameInfo.variant == VariantNormal) {
10213           VariantClass v = StringToVariant(gameInfo.event);
10214           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10215           if(v < VariantShogi) gameInfo.variant = v;
10216         }
10217         if (!matchMode) {
10218           if( appData.autoDisplayTags ) {
10219             tags = PGNTags(&gameInfo);
10220             TagsPopUp(tags, CmailMsg());
10221             free(tags);
10222           }
10223         }
10224     } else {
10225         /* Make something up, but don't display it now */
10226         SetGameInfo();
10227         TagsPopDown();
10228     }
10229
10230     if (cm == PositionDiagram) {
10231         int i, j;
10232         char *p;
10233         Board initial_position;
10234
10235         if (appData.debugMode)
10236           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10237
10238         if (!startedFromSetupPosition) {
10239             p = yy_text;
10240             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10241               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10242                 switch (*p) {
10243                   case '[':
10244                   case '-':
10245                   case ' ':
10246                   case '\t':
10247                   case '\n':
10248                   case '\r':
10249                     break;
10250                   default:
10251                     initial_position[i][j++] = CharToPiece(*p);
10252                     break;
10253                 }
10254             while (*p == ' ' || *p == '\t' ||
10255                    *p == '\n' || *p == '\r') p++;
10256         
10257             if (strncmp(p, "black", strlen("black"))==0)
10258               blackPlaysFirst = TRUE;
10259             else
10260               blackPlaysFirst = FALSE;
10261             startedFromSetupPosition = TRUE;
10262         
10263             CopyBoard(boards[0], initial_position);
10264             if (blackPlaysFirst) {
10265                 currentMove = forwardMostMove = backwardMostMove = 1;
10266                 CopyBoard(boards[1], initial_position);
10267                 strcpy(moveList[0], "");
10268                 strcpy(parseList[0], "");
10269                 timeRemaining[0][1] = whiteTimeRemaining;
10270                 timeRemaining[1][1] = blackTimeRemaining;
10271                 if (commentList[0] != NULL) {
10272                     commentList[1] = commentList[0];
10273                     commentList[0] = NULL;
10274                 }
10275             } else {
10276                 currentMove = forwardMostMove = backwardMostMove = 0;
10277             }
10278         }
10279         yyboardindex = forwardMostMove;
10280         cm = (ChessMove) yylex();
10281     }
10282
10283     if (first.pr == NoProc) {
10284         StartChessProgram(&first);
10285     }
10286     InitChessProgram(&first, FALSE);
10287     SendToProgram("force\n", &first);
10288     if (startedFromSetupPosition) {
10289         SendBoard(&first, forwardMostMove);
10290     if (appData.debugMode) {
10291         fprintf(debugFP, "Load Game\n");
10292     }
10293         DisplayBothClocks();
10294     }      
10295
10296     /* [HGM] server: flag to write setup moves in broadcast file as one */
10297     loadFlag = appData.suppressLoadMoves;
10298
10299     while (cm == Comment) {
10300         char *p;
10301         if (appData.debugMode) 
10302           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10303         p = yy_text;
10304         AppendComment(currentMove, p, FALSE);
10305         yyboardindex = forwardMostMove;
10306         cm = (ChessMove) yylex();
10307     }
10308
10309     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10310         cm == WhiteWins || cm == BlackWins ||
10311         cm == GameIsDrawn || cm == GameUnfinished) {
10312         DisplayMessage("", _("No moves in game"));
10313         if (cmailMsgLoaded) {
10314             if (appData.debugMode)
10315               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10316             ClearHighlights();
10317             flipView = FALSE;
10318         }
10319         DrawPosition(FALSE, boards[currentMove]);
10320         DisplayBothClocks();
10321         gameMode = EditGame;
10322         ModeHighlight();
10323         gameFileFP = NULL;
10324         cmailOldMove = 0;
10325         return TRUE;
10326     }
10327
10328     // [HGM] PV info: routine tests if comment empty
10329     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10330         DisplayComment(currentMove - 1, commentList[currentMove]);
10331     }
10332     if (!matchMode && appData.timeDelay != 0) 
10333       DrawPosition(FALSE, boards[currentMove]);
10334
10335     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10336       programStats.ok_to_send = 1;
10337     }
10338
10339     /* if the first token after the PGN tags is a move
10340      * and not move number 1, retrieve it from the parser 
10341      */
10342     if (cm != MoveNumberOne)
10343         LoadGameOneMove(cm);
10344
10345     /* load the remaining moves from the file */
10346     while (LoadGameOneMove((ChessMove)0)) {
10347       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10348       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10349     }
10350
10351     /* rewind to the start of the game */
10352     currentMove = backwardMostMove;
10353
10354     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10355
10356     if (oldGameMode == AnalyzeFile ||
10357         oldGameMode == AnalyzeMode) {
10358       AnalyzeFileEvent();
10359     }
10360
10361     if (matchMode || appData.timeDelay == 0) {
10362       ToEndEvent();
10363       gameMode = EditGame;
10364       ModeHighlight();
10365     } else if (appData.timeDelay > 0) {
10366       AutoPlayGameLoop();
10367     }
10368
10369     if (appData.debugMode) 
10370         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10371
10372     loadFlag = 0; /* [HGM] true game starts */
10373     return TRUE;
10374 }
10375
10376 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10377 int
10378 ReloadPosition(offset)
10379      int offset;
10380 {
10381     int positionNumber = lastLoadPositionNumber + offset;
10382     if (lastLoadPositionFP == NULL) {
10383         DisplayError(_("No position has been loaded yet"), 0);
10384         return FALSE;
10385     }
10386     if (positionNumber <= 0) {
10387         DisplayError(_("Can't back up any further"), 0);
10388         return FALSE;
10389     }
10390     return LoadPosition(lastLoadPositionFP, positionNumber,
10391                         lastLoadPositionTitle);
10392 }
10393
10394 /* Load the nth position from the given file */
10395 int
10396 LoadPositionFromFile(filename, n, title)
10397      char *filename;
10398      int n;
10399      char *title;
10400 {
10401     FILE *f;
10402     char buf[MSG_SIZ];
10403
10404     if (strcmp(filename, "-") == 0) {
10405         return LoadPosition(stdin, n, "stdin");
10406     } else {
10407         f = fopen(filename, "rb");
10408         if (f == NULL) {
10409             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10410             DisplayError(buf, errno);
10411             return FALSE;
10412         } else {
10413             return LoadPosition(f, n, title);
10414         }
10415     }
10416 }
10417
10418 /* Load the nth position from the given open file, and close it */
10419 int
10420 LoadPosition(f, positionNumber, title)
10421      FILE *f;
10422      int positionNumber;
10423      char *title;
10424 {
10425     char *p, line[MSG_SIZ];
10426     Board initial_position;
10427     int i, j, fenMode, pn;
10428     
10429     if (gameMode == Training )
10430         SetTrainingModeOff();
10431
10432     if (gameMode != BeginningOfGame) {
10433         Reset(FALSE, TRUE);
10434     }
10435     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10436         fclose(lastLoadPositionFP);
10437     }
10438     if (positionNumber == 0) positionNumber = 1;
10439     lastLoadPositionFP = f;
10440     lastLoadPositionNumber = positionNumber;
10441     strcpy(lastLoadPositionTitle, title);
10442     if (first.pr == NoProc) {
10443       StartChessProgram(&first);
10444       InitChessProgram(&first, FALSE);
10445     }    
10446     pn = positionNumber;
10447     if (positionNumber < 0) {
10448         /* Negative position number means to seek to that byte offset */
10449         if (fseek(f, -positionNumber, 0) == -1) {
10450             DisplayError(_("Can't seek on position file"), 0);
10451             return FALSE;
10452         };
10453         pn = 1;
10454     } else {
10455         if (fseek(f, 0, 0) == -1) {
10456             if (f == lastLoadPositionFP ?
10457                 positionNumber == lastLoadPositionNumber + 1 :
10458                 positionNumber == 1) {
10459                 pn = 1;
10460             } else {
10461                 DisplayError(_("Can't seek on position file"), 0);
10462                 return FALSE;
10463             }
10464         }
10465     }
10466     /* See if this file is FEN or old-style xboard */
10467     if (fgets(line, MSG_SIZ, f) == NULL) {
10468         DisplayError(_("Position not found in file"), 0);
10469         return FALSE;
10470     }
10471     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10472     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10473
10474     if (pn >= 2) {
10475         if (fenMode || line[0] == '#') pn--;
10476         while (pn > 0) {
10477             /* skip positions before number pn */
10478             if (fgets(line, MSG_SIZ, f) == NULL) {
10479                 Reset(TRUE, TRUE);
10480                 DisplayError(_("Position not found in file"), 0);
10481                 return FALSE;
10482             }
10483             if (fenMode || line[0] == '#') pn--;
10484         }
10485     }
10486
10487     if (fenMode) {
10488         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10489             DisplayError(_("Bad FEN position in file"), 0);
10490             return FALSE;
10491         }
10492     } else {
10493         (void) fgets(line, MSG_SIZ, f);
10494         (void) fgets(line, MSG_SIZ, f);
10495     
10496         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10497             (void) fgets(line, MSG_SIZ, f);
10498             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10499                 if (*p == ' ')
10500                   continue;
10501                 initial_position[i][j++] = CharToPiece(*p);
10502             }
10503         }
10504     
10505         blackPlaysFirst = FALSE;
10506         if (!feof(f)) {
10507             (void) fgets(line, MSG_SIZ, f);
10508             if (strncmp(line, "black", strlen("black"))==0)
10509               blackPlaysFirst = TRUE;
10510         }
10511     }
10512     startedFromSetupPosition = TRUE;
10513     
10514     SendToProgram("force\n", &first);
10515     CopyBoard(boards[0], initial_position);
10516     if (blackPlaysFirst) {
10517         currentMove = forwardMostMove = backwardMostMove = 1;
10518         strcpy(moveList[0], "");
10519         strcpy(parseList[0], "");
10520         CopyBoard(boards[1], initial_position);
10521         DisplayMessage("", _("Black to play"));
10522     } else {
10523         currentMove = forwardMostMove = backwardMostMove = 0;
10524         DisplayMessage("", _("White to play"));
10525     }
10526     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10527     SendBoard(&first, forwardMostMove);
10528     if (appData.debugMode) {
10529 int i, j;
10530   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10531   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10532         fprintf(debugFP, "Load Position\n");
10533     }
10534
10535     if (positionNumber > 1) {
10536         sprintf(line, "%s %d", title, positionNumber);
10537         DisplayTitle(line);
10538     } else {
10539         DisplayTitle(title);
10540     }
10541     gameMode = EditGame;
10542     ModeHighlight();
10543     ResetClocks();
10544     timeRemaining[0][1] = whiteTimeRemaining;
10545     timeRemaining[1][1] = blackTimeRemaining;
10546     DrawPosition(FALSE, boards[currentMove]);
10547    
10548     return TRUE;
10549 }
10550
10551
10552 void
10553 CopyPlayerNameIntoFileName(dest, src)
10554      char **dest, *src;
10555 {
10556     while (*src != NULLCHAR && *src != ',') {
10557         if (*src == ' ') {
10558             *(*dest)++ = '_';
10559             src++;
10560         } else {
10561             *(*dest)++ = *src++;
10562         }
10563     }
10564 }
10565
10566 char *DefaultFileName(ext)
10567      char *ext;
10568 {
10569     static char def[MSG_SIZ];
10570     char *p;
10571
10572     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10573         p = def;
10574         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10575         *p++ = '-';
10576         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10577         *p++ = '.';
10578         strcpy(p, ext);
10579     } else {
10580         def[0] = NULLCHAR;
10581     }
10582     return def;
10583 }
10584
10585 /* Save the current game to the given file */
10586 int
10587 SaveGameToFile(filename, append)
10588      char *filename;
10589      int append;
10590 {
10591     FILE *f;
10592     char buf[MSG_SIZ];
10593
10594     if (strcmp(filename, "-") == 0) {
10595         return SaveGame(stdout, 0, NULL);
10596     } else {
10597         f = fopen(filename, append ? "a" : "w");
10598         if (f == NULL) {
10599             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10600             DisplayError(buf, errno);
10601             return FALSE;
10602         } else {
10603             return SaveGame(f, 0, NULL);
10604         }
10605     }
10606 }
10607
10608 char *
10609 SavePart(str)
10610      char *str;
10611 {
10612     static char buf[MSG_SIZ];
10613     char *p;
10614     
10615     p = strchr(str, ' ');
10616     if (p == NULL) return str;
10617     strncpy(buf, str, p - str);
10618     buf[p - str] = NULLCHAR;
10619     return buf;
10620 }
10621
10622 #define PGN_MAX_LINE 75
10623
10624 #define PGN_SIDE_WHITE  0
10625 #define PGN_SIDE_BLACK  1
10626
10627 /* [AS] */
10628 static int FindFirstMoveOutOfBook( int side )
10629 {
10630     int result = -1;
10631
10632     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10633         int index = backwardMostMove;
10634         int has_book_hit = 0;
10635
10636         if( (index % 2) != side ) {
10637             index++;
10638         }
10639
10640         while( index < forwardMostMove ) {
10641             /* Check to see if engine is in book */
10642             int depth = pvInfoList[index].depth;
10643             int score = pvInfoList[index].score;
10644             int in_book = 0;
10645
10646             if( depth <= 2 ) {
10647                 in_book = 1;
10648             }
10649             else if( score == 0 && depth == 63 ) {
10650                 in_book = 1; /* Zappa */
10651             }
10652             else if( score == 2 && depth == 99 ) {
10653                 in_book = 1; /* Abrok */
10654             }
10655
10656             has_book_hit += in_book;
10657
10658             if( ! in_book ) {
10659                 result = index;
10660
10661                 break;
10662             }
10663
10664             index += 2;
10665         }
10666     }
10667
10668     return result;
10669 }
10670
10671 /* [AS] */
10672 void GetOutOfBookInfo( char * buf )
10673 {
10674     int oob[2];
10675     int i;
10676     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10677
10678     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10679     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10680
10681     *buf = '\0';
10682
10683     if( oob[0] >= 0 || oob[1] >= 0 ) {
10684         for( i=0; i<2; i++ ) {
10685             int idx = oob[i];
10686
10687             if( idx >= 0 ) {
10688                 if( i > 0 && oob[0] >= 0 ) {
10689                     strcat( buf, "   " );
10690                 }
10691
10692                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10693                 sprintf( buf+strlen(buf), "%s%.2f", 
10694                     pvInfoList[idx].score >= 0 ? "+" : "",
10695                     pvInfoList[idx].score / 100.0 );
10696             }
10697         }
10698     }
10699 }
10700
10701 /* Save game in PGN style and close the file */
10702 int
10703 SaveGamePGN(f)
10704      FILE *f;
10705 {
10706     int i, offset, linelen, newblock;
10707     time_t tm;
10708 //    char *movetext;
10709     char numtext[32];
10710     int movelen, numlen, blank;
10711     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10712
10713     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10714     
10715     tm = time((time_t *) NULL);
10716     
10717     PrintPGNTags(f, &gameInfo);
10718     
10719     if (backwardMostMove > 0 || startedFromSetupPosition) {
10720         char *fen = PositionToFEN(backwardMostMove, NULL);
10721         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10722         fprintf(f, "\n{--------------\n");
10723         PrintPosition(f, backwardMostMove);
10724         fprintf(f, "--------------}\n");
10725         free(fen);
10726     }
10727     else {
10728         /* [AS] Out of book annotation */
10729         if( appData.saveOutOfBookInfo ) {
10730             char buf[64];
10731
10732             GetOutOfBookInfo( buf );
10733
10734             if( buf[0] != '\0' ) {
10735                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10736             }
10737         }
10738
10739         fprintf(f, "\n");
10740     }
10741
10742     i = backwardMostMove;
10743     linelen = 0;
10744     newblock = TRUE;
10745
10746     while (i < forwardMostMove) {
10747         /* Print comments preceding this move */
10748         if (commentList[i] != NULL) {
10749             if (linelen > 0) fprintf(f, "\n");
10750             fprintf(f, "%s", commentList[i]);
10751             linelen = 0;
10752             newblock = TRUE;
10753         }
10754
10755         /* Format move number */
10756         if ((i % 2) == 0) {
10757             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10758         } else {
10759             if (newblock) {
10760                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10761             } else {
10762                 numtext[0] = NULLCHAR;
10763             }
10764         }
10765         numlen = strlen(numtext);
10766         newblock = FALSE;
10767
10768         /* Print move number */
10769         blank = linelen > 0 && numlen > 0;
10770         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10771             fprintf(f, "\n");
10772             linelen = 0;
10773             blank = 0;
10774         }
10775         if (blank) {
10776             fprintf(f, " ");
10777             linelen++;
10778         }
10779         fprintf(f, "%s", numtext);
10780         linelen += numlen;
10781
10782         /* Get move */
10783         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10784         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10785
10786         /* Print move */
10787         blank = linelen > 0 && movelen > 0;
10788         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10789             fprintf(f, "\n");
10790             linelen = 0;
10791             blank = 0;
10792         }
10793         if (blank) {
10794             fprintf(f, " ");
10795             linelen++;
10796         }
10797         fprintf(f, "%s", move_buffer);
10798         linelen += movelen;
10799
10800         /* [AS] Add PV info if present */
10801         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10802             /* [HGM] add time */
10803             char buf[MSG_SIZ]; int seconds;
10804
10805             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10806
10807             if( seconds <= 0) buf[0] = 0; else
10808             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10809                 seconds = (seconds + 4)/10; // round to full seconds
10810                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10811                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10812             }
10813
10814             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10815                 pvInfoList[i].score >= 0 ? "+" : "",
10816                 pvInfoList[i].score / 100.0,
10817                 pvInfoList[i].depth,
10818                 buf );
10819
10820             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10821
10822             /* Print score/depth */
10823             blank = linelen > 0 && movelen > 0;
10824             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10825                 fprintf(f, "\n");
10826                 linelen = 0;
10827                 blank = 0;
10828             }
10829             if (blank) {
10830                 fprintf(f, " ");
10831                 linelen++;
10832             }
10833             fprintf(f, "%s", move_buffer);
10834             linelen += movelen;
10835         }
10836
10837         i++;
10838     }
10839     
10840     /* Start a new line */
10841     if (linelen > 0) fprintf(f, "\n");
10842
10843     /* Print comments after last move */
10844     if (commentList[i] != NULL) {
10845         fprintf(f, "%s\n", commentList[i]);
10846     }
10847
10848     /* Print result */
10849     if (gameInfo.resultDetails != NULL &&
10850         gameInfo.resultDetails[0] != NULLCHAR) {
10851         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10852                 PGNResult(gameInfo.result));
10853     } else {
10854         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10855     }
10856
10857     fclose(f);
10858     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10859     return TRUE;
10860 }
10861
10862 /* Save game in old style and close the file */
10863 int
10864 SaveGameOldStyle(f)
10865      FILE *f;
10866 {
10867     int i, offset;
10868     time_t tm;
10869     
10870     tm = time((time_t *) NULL);
10871     
10872     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10873     PrintOpponents(f);
10874     
10875     if (backwardMostMove > 0 || startedFromSetupPosition) {
10876         fprintf(f, "\n[--------------\n");
10877         PrintPosition(f, backwardMostMove);
10878         fprintf(f, "--------------]\n");
10879     } else {
10880         fprintf(f, "\n");
10881     }
10882
10883     i = backwardMostMove;
10884     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10885
10886     while (i < forwardMostMove) {
10887         if (commentList[i] != NULL) {
10888             fprintf(f, "[%s]\n", commentList[i]);
10889         }
10890
10891         if ((i % 2) == 1) {
10892             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10893             i++;
10894         } else {
10895             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10896             i++;
10897             if (commentList[i] != NULL) {
10898                 fprintf(f, "\n");
10899                 continue;
10900             }
10901             if (i >= forwardMostMove) {
10902                 fprintf(f, "\n");
10903                 break;
10904             }
10905             fprintf(f, "%s\n", parseList[i]);
10906             i++;
10907         }
10908     }
10909     
10910     if (commentList[i] != NULL) {
10911         fprintf(f, "[%s]\n", commentList[i]);
10912     }
10913
10914     /* This isn't really the old style, but it's close enough */
10915     if (gameInfo.resultDetails != NULL &&
10916         gameInfo.resultDetails[0] != NULLCHAR) {
10917         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10918                 gameInfo.resultDetails);
10919     } else {
10920         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10921     }
10922
10923     fclose(f);
10924     return TRUE;
10925 }
10926
10927 /* Save the current game to open file f and close the file */
10928 int
10929 SaveGame(f, dummy, dummy2)
10930      FILE *f;
10931      int dummy;
10932      char *dummy2;
10933 {
10934     if (gameMode == EditPosition) EditPositionDone(TRUE);
10935     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10936     if (appData.oldSaveStyle)
10937       return SaveGameOldStyle(f);
10938     else
10939       return SaveGamePGN(f);
10940 }
10941
10942 /* Save the current position to the given file */
10943 int
10944 SavePositionToFile(filename)
10945      char *filename;
10946 {
10947     FILE *f;
10948     char buf[MSG_SIZ];
10949
10950     if (strcmp(filename, "-") == 0) {
10951         return SavePosition(stdout, 0, NULL);
10952     } else {
10953         f = fopen(filename, "a");
10954         if (f == NULL) {
10955             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10956             DisplayError(buf, errno);
10957             return FALSE;
10958         } else {
10959             SavePosition(f, 0, NULL);
10960             return TRUE;
10961         }
10962     }
10963 }
10964
10965 /* Save the current position to the given open file and close the file */
10966 int
10967 SavePosition(f, dummy, dummy2)
10968      FILE *f;
10969      int dummy;
10970      char *dummy2;
10971 {
10972     time_t tm;
10973     char *fen;
10974     
10975     if (gameMode == EditPosition) EditPositionDone(TRUE);
10976     if (appData.oldSaveStyle) {
10977         tm = time((time_t *) NULL);
10978     
10979         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10980         PrintOpponents(f);
10981         fprintf(f, "[--------------\n");
10982         PrintPosition(f, currentMove);
10983         fprintf(f, "--------------]\n");
10984     } else {
10985         fen = PositionToFEN(currentMove, NULL);
10986         fprintf(f, "%s\n", fen);
10987         free(fen);
10988     }
10989     fclose(f);
10990     return TRUE;
10991 }
10992
10993 void
10994 ReloadCmailMsgEvent(unregister)
10995      int unregister;
10996 {
10997 #if !WIN32
10998     static char *inFilename = NULL;
10999     static char *outFilename;
11000     int i;
11001     struct stat inbuf, outbuf;
11002     int status;
11003     
11004     /* Any registered moves are unregistered if unregister is set, */
11005     /* i.e. invoked by the signal handler */
11006     if (unregister) {
11007         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11008             cmailMoveRegistered[i] = FALSE;
11009             if (cmailCommentList[i] != NULL) {
11010                 free(cmailCommentList[i]);
11011                 cmailCommentList[i] = NULL;
11012             }
11013         }
11014         nCmailMovesRegistered = 0;
11015     }
11016
11017     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11018         cmailResult[i] = CMAIL_NOT_RESULT;
11019     }
11020     nCmailResults = 0;
11021
11022     if (inFilename == NULL) {
11023         /* Because the filenames are static they only get malloced once  */
11024         /* and they never get freed                                      */
11025         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11026         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11027
11028         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11029         sprintf(outFilename, "%s.out", appData.cmailGameName);
11030     }
11031     
11032     status = stat(outFilename, &outbuf);
11033     if (status < 0) {
11034         cmailMailedMove = FALSE;
11035     } else {
11036         status = stat(inFilename, &inbuf);
11037         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11038     }
11039     
11040     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11041        counts the games, notes how each one terminated, etc.
11042        
11043        It would be nice to remove this kludge and instead gather all
11044        the information while building the game list.  (And to keep it
11045        in the game list nodes instead of having a bunch of fixed-size
11046        parallel arrays.)  Note this will require getting each game's
11047        termination from the PGN tags, as the game list builder does
11048        not process the game moves.  --mann
11049        */
11050     cmailMsgLoaded = TRUE;
11051     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11052     
11053     /* Load first game in the file or popup game menu */
11054     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11055
11056 #endif /* !WIN32 */
11057     return;
11058 }
11059
11060 int
11061 RegisterMove()
11062 {
11063     FILE *f;
11064     char string[MSG_SIZ];
11065
11066     if (   cmailMailedMove
11067         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11068         return TRUE;            /* Allow free viewing  */
11069     }
11070
11071     /* Unregister move to ensure that we don't leave RegisterMove        */
11072     /* with the move registered when the conditions for registering no   */
11073     /* longer hold                                                       */
11074     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11075         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11076         nCmailMovesRegistered --;
11077
11078         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11079           {
11080               free(cmailCommentList[lastLoadGameNumber - 1]);
11081               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11082           }
11083     }
11084
11085     if (cmailOldMove == -1) {
11086         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11087         return FALSE;
11088     }
11089
11090     if (currentMove > cmailOldMove + 1) {
11091         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11092         return FALSE;
11093     }
11094
11095     if (currentMove < cmailOldMove) {
11096         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11097         return FALSE;
11098     }
11099
11100     if (forwardMostMove > currentMove) {
11101         /* Silently truncate extra moves */
11102         TruncateGame();
11103     }
11104
11105     if (   (currentMove == cmailOldMove + 1)
11106         || (   (currentMove == cmailOldMove)
11107             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11108                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11109         if (gameInfo.result != GameUnfinished) {
11110             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11111         }
11112
11113         if (commentList[currentMove] != NULL) {
11114             cmailCommentList[lastLoadGameNumber - 1]
11115               = StrSave(commentList[currentMove]);
11116         }
11117         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11118
11119         if (appData.debugMode)
11120           fprintf(debugFP, "Saving %s for game %d\n",
11121                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11122
11123         sprintf(string,
11124                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11125         
11126         f = fopen(string, "w");
11127         if (appData.oldSaveStyle) {
11128             SaveGameOldStyle(f); /* also closes the file */
11129             
11130             sprintf(string, "%s.pos.out", appData.cmailGameName);
11131             f = fopen(string, "w");
11132             SavePosition(f, 0, NULL); /* also closes the file */
11133         } else {
11134             fprintf(f, "{--------------\n");
11135             PrintPosition(f, currentMove);
11136             fprintf(f, "--------------}\n\n");
11137             
11138             SaveGame(f, 0, NULL); /* also closes the file*/
11139         }
11140         
11141         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11142         nCmailMovesRegistered ++;
11143     } else if (nCmailGames == 1) {
11144         DisplayError(_("You have not made a move yet"), 0);
11145         return FALSE;
11146     }
11147
11148     return TRUE;
11149 }
11150
11151 void
11152 MailMoveEvent()
11153 {
11154 #if !WIN32
11155     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11156     FILE *commandOutput;
11157     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11158     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11159     int nBuffers;
11160     int i;
11161     int archived;
11162     char *arcDir;
11163
11164     if (! cmailMsgLoaded) {
11165         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11166         return;
11167     }
11168
11169     if (nCmailGames == nCmailResults) {
11170         DisplayError(_("No unfinished games"), 0);
11171         return;
11172     }
11173
11174 #if CMAIL_PROHIBIT_REMAIL
11175     if (cmailMailedMove) {
11176         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);
11177         DisplayError(msg, 0);
11178         return;
11179     }
11180 #endif
11181
11182     if (! (cmailMailedMove || RegisterMove())) return;
11183     
11184     if (   cmailMailedMove
11185         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11186         sprintf(string, partCommandString,
11187                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11188         commandOutput = popen(string, "r");
11189
11190         if (commandOutput == NULL) {
11191             DisplayError(_("Failed to invoke cmail"), 0);
11192         } else {
11193             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11194                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11195             }
11196             if (nBuffers > 1) {
11197                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11198                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11199                 nBytes = MSG_SIZ - 1;
11200             } else {
11201                 (void) memcpy(msg, buffer, nBytes);
11202             }
11203             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11204
11205             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11206                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11207
11208                 archived = TRUE;
11209                 for (i = 0; i < nCmailGames; i ++) {
11210                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11211                         archived = FALSE;
11212                     }
11213                 }
11214                 if (   archived
11215                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11216                         != NULL)) {
11217                     sprintf(buffer, "%s/%s.%s.archive",
11218                             arcDir,
11219                             appData.cmailGameName,
11220                             gameInfo.date);
11221                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11222                     cmailMsgLoaded = FALSE;
11223                 }
11224             }
11225
11226             DisplayInformation(msg);
11227             pclose(commandOutput);
11228         }
11229     } else {
11230         if ((*cmailMsg) != '\0') {
11231             DisplayInformation(cmailMsg);
11232         }
11233     }
11234
11235     return;
11236 #endif /* !WIN32 */
11237 }
11238
11239 char *
11240 CmailMsg()
11241 {
11242 #if WIN32
11243     return NULL;
11244 #else
11245     int  prependComma = 0;
11246     char number[5];
11247     char string[MSG_SIZ];       /* Space for game-list */
11248     int  i;
11249     
11250     if (!cmailMsgLoaded) return "";
11251
11252     if (cmailMailedMove) {
11253         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11254     } else {
11255         /* Create a list of games left */
11256         sprintf(string, "[");
11257         for (i = 0; i < nCmailGames; i ++) {
11258             if (! (   cmailMoveRegistered[i]
11259                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11260                 if (prependComma) {
11261                     sprintf(number, ",%d", i + 1);
11262                 } else {
11263                     sprintf(number, "%d", i + 1);
11264                     prependComma = 1;
11265                 }
11266                 
11267                 strcat(string, number);
11268             }
11269         }
11270         strcat(string, "]");
11271
11272         if (nCmailMovesRegistered + nCmailResults == 0) {
11273             switch (nCmailGames) {
11274               case 1:
11275                 sprintf(cmailMsg,
11276                         _("Still need to make move for game\n"));
11277                 break;
11278                 
11279               case 2:
11280                 sprintf(cmailMsg,
11281                         _("Still need to make moves for both games\n"));
11282                 break;
11283                 
11284               default:
11285                 sprintf(cmailMsg,
11286                         _("Still need to make moves for all %d games\n"),
11287                         nCmailGames);
11288                 break;
11289             }
11290         } else {
11291             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11292               case 1:
11293                 sprintf(cmailMsg,
11294                         _("Still need to make a move for game %s\n"),
11295                         string);
11296                 break;
11297                 
11298               case 0:
11299                 if (nCmailResults == nCmailGames) {
11300                     sprintf(cmailMsg, _("No unfinished games\n"));
11301                 } else {
11302                     sprintf(cmailMsg, _("Ready to send mail\n"));
11303                 }
11304                 break;
11305                 
11306               default:
11307                 sprintf(cmailMsg,
11308                         _("Still need to make moves for games %s\n"),
11309                         string);
11310             }
11311         }
11312     }
11313     return cmailMsg;
11314 #endif /* WIN32 */
11315 }
11316
11317 void
11318 ResetGameEvent()
11319 {
11320     if (gameMode == Training)
11321       SetTrainingModeOff();
11322
11323     Reset(TRUE, TRUE);
11324     cmailMsgLoaded = FALSE;
11325     if (appData.icsActive) {
11326       SendToICS(ics_prefix);
11327       SendToICS("refresh\n");
11328     }
11329 }
11330
11331 void
11332 ExitEvent(status)
11333      int status;
11334 {
11335     exiting++;
11336     if (exiting > 2) {
11337       /* Give up on clean exit */
11338       exit(status);
11339     }
11340     if (exiting > 1) {
11341       /* Keep trying for clean exit */
11342       return;
11343     }
11344
11345     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11346
11347     if (telnetISR != NULL) {
11348       RemoveInputSource(telnetISR);
11349     }
11350     if (icsPR != NoProc) {
11351       DestroyChildProcess(icsPR, TRUE);
11352     }
11353
11354     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11355     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11356
11357     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11358     /* make sure this other one finishes before killing it!                  */
11359     if(endingGame) { int count = 0;
11360         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11361         while(endingGame && count++ < 10) DoSleep(1);
11362         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11363     }
11364
11365     /* Kill off chess programs */
11366     if (first.pr != NoProc) {
11367         ExitAnalyzeMode();
11368         
11369         DoSleep( appData.delayBeforeQuit );
11370         SendToProgram("quit\n", &first);
11371         DoSleep( appData.delayAfterQuit );
11372         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11373     }
11374     if (second.pr != NoProc) {
11375         DoSleep( appData.delayBeforeQuit );
11376         SendToProgram("quit\n", &second);
11377         DoSleep( appData.delayAfterQuit );
11378         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11379     }
11380     if (first.isr != NULL) {
11381         RemoveInputSource(first.isr);
11382     }
11383     if (second.isr != NULL) {
11384         RemoveInputSource(second.isr);
11385     }
11386
11387     ShutDownFrontEnd();
11388     exit(status);
11389 }
11390
11391 void
11392 PauseEvent()
11393 {
11394     if (appData.debugMode)
11395         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11396     if (pausing) {
11397         pausing = FALSE;
11398         ModeHighlight();
11399         if (gameMode == MachinePlaysWhite ||
11400             gameMode == MachinePlaysBlack) {
11401             StartClocks();
11402         } else {
11403             DisplayBothClocks();
11404         }
11405         if (gameMode == PlayFromGameFile) {
11406             if (appData.timeDelay >= 0) 
11407                 AutoPlayGameLoop();
11408         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11409             Reset(FALSE, TRUE);
11410             SendToICS(ics_prefix);
11411             SendToICS("refresh\n");
11412         } else if (currentMove < forwardMostMove) {
11413             ForwardInner(forwardMostMove);
11414         }
11415         pauseExamInvalid = FALSE;
11416     } else {
11417         switch (gameMode) {
11418           default:
11419             return;
11420           case IcsExamining:
11421             pauseExamForwardMostMove = forwardMostMove;
11422             pauseExamInvalid = FALSE;
11423             /* fall through */
11424           case IcsObserving:
11425           case IcsPlayingWhite:
11426           case IcsPlayingBlack:
11427             pausing = TRUE;
11428             ModeHighlight();
11429             return;
11430           case PlayFromGameFile:
11431             (void) StopLoadGameTimer();
11432             pausing = TRUE;
11433             ModeHighlight();
11434             break;
11435           case BeginningOfGame:
11436             if (appData.icsActive) return;
11437             /* else fall through */
11438           case MachinePlaysWhite:
11439           case MachinePlaysBlack:
11440           case TwoMachinesPlay:
11441             if (forwardMostMove == 0)
11442               return;           /* don't pause if no one has moved */
11443             if ((gameMode == MachinePlaysWhite &&
11444                  !WhiteOnMove(forwardMostMove)) ||
11445                 (gameMode == MachinePlaysBlack &&
11446                  WhiteOnMove(forwardMostMove))) {
11447                 StopClocks();
11448             }
11449             pausing = TRUE;
11450             ModeHighlight();
11451             break;
11452         }
11453     }
11454 }
11455
11456 void
11457 EditCommentEvent()
11458 {
11459     char title[MSG_SIZ];
11460
11461     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11462         strcpy(title, _("Edit comment"));
11463     } else {
11464         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11465                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11466                 parseList[currentMove - 1]);
11467     }
11468
11469     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11470 }
11471
11472
11473 void
11474 EditTagsEvent()
11475 {
11476     char *tags = PGNTags(&gameInfo);
11477     EditTagsPopUp(tags);
11478     free(tags);
11479 }
11480
11481 void
11482 AnalyzeModeEvent()
11483 {
11484     if (appData.noChessProgram || gameMode == AnalyzeMode)
11485       return;
11486
11487     if (gameMode != AnalyzeFile) {
11488         if (!appData.icsEngineAnalyze) {
11489                EditGameEvent();
11490                if (gameMode != EditGame) return;
11491         }
11492         ResurrectChessProgram();
11493         SendToProgram("analyze\n", &first);
11494         first.analyzing = TRUE;
11495         /*first.maybeThinking = TRUE;*/
11496         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11497         EngineOutputPopUp();
11498     }
11499     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11500     pausing = FALSE;
11501     ModeHighlight();
11502     SetGameInfo();
11503
11504     StartAnalysisClock();
11505     GetTimeMark(&lastNodeCountTime);
11506     lastNodeCount = 0;
11507 }
11508
11509 void
11510 AnalyzeFileEvent()
11511 {
11512     if (appData.noChessProgram || gameMode == AnalyzeFile)
11513       return;
11514
11515     if (gameMode != AnalyzeMode) {
11516         EditGameEvent();
11517         if (gameMode != EditGame) return;
11518         ResurrectChessProgram();
11519         SendToProgram("analyze\n", &first);
11520         first.analyzing = TRUE;
11521         /*first.maybeThinking = TRUE;*/
11522         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11523         EngineOutputPopUp();
11524     }
11525     gameMode = AnalyzeFile;
11526     pausing = FALSE;
11527     ModeHighlight();
11528     SetGameInfo();
11529
11530     StartAnalysisClock();
11531     GetTimeMark(&lastNodeCountTime);
11532     lastNodeCount = 0;
11533 }
11534
11535 void
11536 MachineWhiteEvent()
11537 {
11538     char buf[MSG_SIZ];
11539     char *bookHit = NULL;
11540
11541     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11542       return;
11543
11544
11545     if (gameMode == PlayFromGameFile || 
11546         gameMode == TwoMachinesPlay  || 
11547         gameMode == Training         || 
11548         gameMode == AnalyzeMode      || 
11549         gameMode == EndOfGame)
11550         EditGameEvent();
11551
11552     if (gameMode == EditPosition) 
11553         EditPositionDone(TRUE);
11554
11555     if (!WhiteOnMove(currentMove)) {
11556         DisplayError(_("It is not White's turn"), 0);
11557         return;
11558     }
11559   
11560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11561       ExitAnalyzeMode();
11562
11563     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11564         gameMode == AnalyzeFile)
11565         TruncateGame();
11566
11567     ResurrectChessProgram();    /* in case it isn't running */
11568     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11569         gameMode = MachinePlaysWhite;
11570         ResetClocks();
11571     } else
11572     gameMode = MachinePlaysWhite;
11573     pausing = FALSE;
11574     ModeHighlight();
11575     SetGameInfo();
11576     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11577     DisplayTitle(buf);
11578     if (first.sendName) {
11579       sprintf(buf, "name %s\n", gameInfo.black);
11580       SendToProgram(buf, &first);
11581     }
11582     if (first.sendTime) {
11583       if (first.useColors) {
11584         SendToProgram("black\n", &first); /*gnu kludge*/
11585       }
11586       SendTimeRemaining(&first, TRUE);
11587     }
11588     if (first.useColors) {
11589       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11590     }
11591     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11592     SetMachineThinkingEnables();
11593     first.maybeThinking = TRUE;
11594     StartClocks();
11595     firstMove = FALSE;
11596
11597     if (appData.autoFlipView && !flipView) {
11598       flipView = !flipView;
11599       DrawPosition(FALSE, NULL);
11600       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11601     }
11602
11603     if(bookHit) { // [HGM] book: simulate book reply
11604         static char bookMove[MSG_SIZ]; // a bit generous?
11605
11606         programStats.nodes = programStats.depth = programStats.time = 
11607         programStats.score = programStats.got_only_move = 0;
11608         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11609
11610         strcpy(bookMove, "move ");
11611         strcat(bookMove, bookHit);
11612         HandleMachineMove(bookMove, &first);
11613     }
11614 }
11615
11616 void
11617 MachineBlackEvent()
11618 {
11619     char buf[MSG_SIZ];
11620    char *bookHit = NULL;
11621
11622     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11623         return;
11624
11625
11626     if (gameMode == PlayFromGameFile || 
11627         gameMode == TwoMachinesPlay  || 
11628         gameMode == Training         || 
11629         gameMode == AnalyzeMode      || 
11630         gameMode == EndOfGame)
11631         EditGameEvent();
11632
11633     if (gameMode == EditPosition) 
11634         EditPositionDone(TRUE);
11635
11636     if (WhiteOnMove(currentMove)) {
11637         DisplayError(_("It is not Black's turn"), 0);
11638         return;
11639     }
11640     
11641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11642       ExitAnalyzeMode();
11643
11644     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11645         gameMode == AnalyzeFile)
11646         TruncateGame();
11647
11648     ResurrectChessProgram();    /* in case it isn't running */
11649     gameMode = MachinePlaysBlack;
11650     pausing = FALSE;
11651     ModeHighlight();
11652     SetGameInfo();
11653     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11654     DisplayTitle(buf);
11655     if (first.sendName) {
11656       sprintf(buf, "name %s\n", gameInfo.white);
11657       SendToProgram(buf, &first);
11658     }
11659     if (first.sendTime) {
11660       if (first.useColors) {
11661         SendToProgram("white\n", &first); /*gnu kludge*/
11662       }
11663       SendTimeRemaining(&first, FALSE);
11664     }
11665     if (first.useColors) {
11666       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11667     }
11668     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11669     SetMachineThinkingEnables();
11670     first.maybeThinking = TRUE;
11671     StartClocks();
11672
11673     if (appData.autoFlipView && flipView) {
11674       flipView = !flipView;
11675       DrawPosition(FALSE, NULL);
11676       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11677     }
11678     if(bookHit) { // [HGM] book: simulate book reply
11679         static char bookMove[MSG_SIZ]; // a bit generous?
11680
11681         programStats.nodes = programStats.depth = programStats.time = 
11682         programStats.score = programStats.got_only_move = 0;
11683         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11684
11685         strcpy(bookMove, "move ");
11686         strcat(bookMove, bookHit);
11687         HandleMachineMove(bookMove, &first);
11688     }
11689 }
11690
11691
11692 void
11693 DisplayTwoMachinesTitle()
11694 {
11695     char buf[MSG_SIZ];
11696     if (appData.matchGames > 0) {
11697         if (first.twoMachinesColor[0] == 'w') {
11698             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11699                     gameInfo.white, gameInfo.black,
11700                     first.matchWins, second.matchWins,
11701                     matchGame - 1 - (first.matchWins + second.matchWins));
11702         } else {
11703             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11704                     gameInfo.white, gameInfo.black,
11705                     second.matchWins, first.matchWins,
11706                     matchGame - 1 - (first.matchWins + second.matchWins));
11707         }
11708     } else {
11709         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11710     }
11711     DisplayTitle(buf);
11712 }
11713
11714 void
11715 TwoMachinesEvent P((void))
11716 {
11717     int i;
11718     char buf[MSG_SIZ];
11719     ChessProgramState *onmove;
11720     char *bookHit = NULL;
11721     
11722     if (appData.noChessProgram) return;
11723
11724     switch (gameMode) {
11725       case TwoMachinesPlay:
11726         return;
11727       case MachinePlaysWhite:
11728       case MachinePlaysBlack:
11729         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11730             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11731             return;
11732         }
11733         /* fall through */
11734       case BeginningOfGame:
11735       case PlayFromGameFile:
11736       case EndOfGame:
11737         EditGameEvent();
11738         if (gameMode != EditGame) return;
11739         break;
11740       case EditPosition:
11741         EditPositionDone(TRUE);
11742         break;
11743       case AnalyzeMode:
11744       case AnalyzeFile:
11745         ExitAnalyzeMode();
11746         break;
11747       case EditGame:
11748       default:
11749         break;
11750     }
11751
11752 //    forwardMostMove = currentMove;
11753     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11754     ResurrectChessProgram();    /* in case first program isn't running */
11755
11756     if (second.pr == NULL) {
11757         StartChessProgram(&second);
11758         if (second.protocolVersion == 1) {
11759           TwoMachinesEventIfReady();
11760         } else {
11761           /* kludge: allow timeout for initial "feature" command */
11762           FreezeUI();
11763           DisplayMessage("", _("Starting second chess program"));
11764           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11765         }
11766         return;
11767     }
11768     DisplayMessage("", "");
11769     InitChessProgram(&second, FALSE);
11770     SendToProgram("force\n", &second);
11771     if (startedFromSetupPosition) {
11772         SendBoard(&second, backwardMostMove);
11773     if (appData.debugMode) {
11774         fprintf(debugFP, "Two Machines\n");
11775     }
11776     }
11777     for (i = backwardMostMove; i < forwardMostMove; i++) {
11778         SendMoveToProgram(i, &second);
11779     }
11780
11781     gameMode = TwoMachinesPlay;
11782     pausing = FALSE;
11783     ModeHighlight();
11784     SetGameInfo();
11785     DisplayTwoMachinesTitle();
11786     firstMove = TRUE;
11787     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11788         onmove = &first;
11789     } else {
11790         onmove = &second;
11791     }
11792
11793     SendToProgram(first.computerString, &first);
11794     if (first.sendName) {
11795       sprintf(buf, "name %s\n", second.tidy);
11796       SendToProgram(buf, &first);
11797     }
11798     SendToProgram(second.computerString, &second);
11799     if (second.sendName) {
11800       sprintf(buf, "name %s\n", first.tidy);
11801       SendToProgram(buf, &second);
11802     }
11803
11804     ResetClocks();
11805     if (!first.sendTime || !second.sendTime) {
11806         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11807         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11808     }
11809     if (onmove->sendTime) {
11810       if (onmove->useColors) {
11811         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11812       }
11813       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11814     }
11815     if (onmove->useColors) {
11816       SendToProgram(onmove->twoMachinesColor, onmove);
11817     }
11818     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11819 //    SendToProgram("go\n", onmove);
11820     onmove->maybeThinking = TRUE;
11821     SetMachineThinkingEnables();
11822
11823     StartClocks();
11824
11825     if(bookHit) { // [HGM] book: simulate book reply
11826         static char bookMove[MSG_SIZ]; // a bit generous?
11827
11828         programStats.nodes = programStats.depth = programStats.time = 
11829         programStats.score = programStats.got_only_move = 0;
11830         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11831
11832         strcpy(bookMove, "move ");
11833         strcat(bookMove, bookHit);
11834         savedMessage = bookMove; // args for deferred call
11835         savedState = onmove;
11836         ScheduleDelayedEvent(DeferredBookMove, 1);
11837     }
11838 }
11839
11840 void
11841 TrainingEvent()
11842 {
11843     if (gameMode == Training) {
11844       SetTrainingModeOff();
11845       gameMode = PlayFromGameFile;
11846       DisplayMessage("", _("Training mode off"));
11847     } else {
11848       gameMode = Training;
11849       animateTraining = appData.animate;
11850
11851       /* make sure we are not already at the end of the game */
11852       if (currentMove < forwardMostMove) {
11853         SetTrainingModeOn();
11854         DisplayMessage("", _("Training mode on"));
11855       } else {
11856         gameMode = PlayFromGameFile;
11857         DisplayError(_("Already at end of game"), 0);
11858       }
11859     }
11860     ModeHighlight();
11861 }
11862
11863 void
11864 IcsClientEvent()
11865 {
11866     if (!appData.icsActive) return;
11867     switch (gameMode) {
11868       case IcsPlayingWhite:
11869       case IcsPlayingBlack:
11870       case IcsObserving:
11871       case IcsIdle:
11872       case BeginningOfGame:
11873       case IcsExamining:
11874         return;
11875
11876       case EditGame:
11877         break;
11878
11879       case EditPosition:
11880         EditPositionDone(TRUE);
11881         break;
11882
11883       case AnalyzeMode:
11884       case AnalyzeFile:
11885         ExitAnalyzeMode();
11886         break;
11887         
11888       default:
11889         EditGameEvent();
11890         break;
11891     }
11892
11893     gameMode = IcsIdle;
11894     ModeHighlight();
11895     return;
11896 }
11897
11898
11899 void
11900 EditGameEvent()
11901 {
11902     int i;
11903
11904     switch (gameMode) {
11905       case Training:
11906         SetTrainingModeOff();
11907         break;
11908       case MachinePlaysWhite:
11909       case MachinePlaysBlack:
11910       case BeginningOfGame:
11911         SendToProgram("force\n", &first);
11912         SetUserThinkingEnables();
11913         break;
11914       case PlayFromGameFile:
11915         (void) StopLoadGameTimer();
11916         if (gameFileFP != NULL) {
11917             gameFileFP = NULL;
11918         }
11919         break;
11920       case EditPosition:
11921         EditPositionDone(TRUE);
11922         break;
11923       case AnalyzeMode:
11924       case AnalyzeFile:
11925         ExitAnalyzeMode();
11926         SendToProgram("force\n", &first);
11927         break;
11928       case TwoMachinesPlay:
11929         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11930         ResurrectChessProgram();
11931         SetUserThinkingEnables();
11932         break;
11933       case EndOfGame:
11934         ResurrectChessProgram();
11935         break;
11936       case IcsPlayingBlack:
11937       case IcsPlayingWhite:
11938         DisplayError(_("Warning: You are still playing a game"), 0);
11939         break;
11940       case IcsObserving:
11941         DisplayError(_("Warning: You are still observing a game"), 0);
11942         break;
11943       case IcsExamining:
11944         DisplayError(_("Warning: You are still examining a game"), 0);
11945         break;
11946       case IcsIdle:
11947         break;
11948       case EditGame:
11949       default:
11950         return;
11951     }
11952     
11953     pausing = FALSE;
11954     StopClocks();
11955     first.offeredDraw = second.offeredDraw = 0;
11956
11957     if (gameMode == PlayFromGameFile) {
11958         whiteTimeRemaining = timeRemaining[0][currentMove];
11959         blackTimeRemaining = timeRemaining[1][currentMove];
11960         DisplayTitle("");
11961     }
11962
11963     if (gameMode == MachinePlaysWhite ||
11964         gameMode == MachinePlaysBlack ||
11965         gameMode == TwoMachinesPlay ||
11966         gameMode == EndOfGame) {
11967         i = forwardMostMove;
11968         while (i > currentMove) {
11969             SendToProgram("undo\n", &first);
11970             i--;
11971         }
11972         whiteTimeRemaining = timeRemaining[0][currentMove];
11973         blackTimeRemaining = timeRemaining[1][currentMove];
11974         DisplayBothClocks();
11975         if (whiteFlag || blackFlag) {
11976             whiteFlag = blackFlag = 0;
11977         }
11978         DisplayTitle("");
11979     }           
11980     
11981     gameMode = EditGame;
11982     ModeHighlight();
11983     SetGameInfo();
11984 }
11985
11986
11987 void
11988 EditPositionEvent()
11989 {
11990     if (gameMode == EditPosition) {
11991         EditGameEvent();
11992         return;
11993     }
11994     
11995     EditGameEvent();
11996     if (gameMode != EditGame) return;
11997     
11998     gameMode = EditPosition;
11999     ModeHighlight();
12000     SetGameInfo();
12001     if (currentMove > 0)
12002       CopyBoard(boards[0], boards[currentMove]);
12003     
12004     blackPlaysFirst = !WhiteOnMove(currentMove);
12005     ResetClocks();
12006     currentMove = forwardMostMove = backwardMostMove = 0;
12007     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12008     DisplayMove(-1);
12009 }
12010
12011 void
12012 ExitAnalyzeMode()
12013 {
12014     /* [DM] icsEngineAnalyze - possible call from other functions */
12015     if (appData.icsEngineAnalyze) {
12016         appData.icsEngineAnalyze = FALSE;
12017
12018         DisplayMessage("",_("Close ICS engine analyze..."));
12019     }
12020     if (first.analysisSupport && first.analyzing) {
12021       SendToProgram("exit\n", &first);
12022       first.analyzing = FALSE;
12023     }
12024     thinkOutput[0] = NULLCHAR;
12025 }
12026
12027 void
12028 EditPositionDone(Boolean fakeRights)
12029 {
12030     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12031
12032     startedFromSetupPosition = TRUE;
12033     InitChessProgram(&first, FALSE);
12034     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12035       boards[0][EP_STATUS] = EP_NONE;
12036       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12037     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12038         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12039         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12040       } else boards[0][CASTLING][2] = NoRights;
12041     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12042         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12043         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12044       } else boards[0][CASTLING][5] = NoRights;
12045     }
12046     SendToProgram("force\n", &first);
12047     if (blackPlaysFirst) {
12048         strcpy(moveList[0], "");
12049         strcpy(parseList[0], "");
12050         currentMove = forwardMostMove = backwardMostMove = 1;
12051         CopyBoard(boards[1], boards[0]);
12052     } else {
12053         currentMove = forwardMostMove = backwardMostMove = 0;
12054     }
12055     SendBoard(&first, forwardMostMove);
12056     if (appData.debugMode) {
12057         fprintf(debugFP, "EditPosDone\n");
12058     }
12059     DisplayTitle("");
12060     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12061     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12062     gameMode = EditGame;
12063     ModeHighlight();
12064     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12065     ClearHighlights(); /* [AS] */
12066 }
12067
12068 /* Pause for `ms' milliseconds */
12069 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12070 void
12071 TimeDelay(ms)
12072      long ms;
12073 {
12074     TimeMark m1, m2;
12075
12076     GetTimeMark(&m1);
12077     do {
12078         GetTimeMark(&m2);
12079     } while (SubtractTimeMarks(&m2, &m1) < ms);
12080 }
12081
12082 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12083 void
12084 SendMultiLineToICS(buf)
12085      char *buf;
12086 {
12087     char temp[MSG_SIZ+1], *p;
12088     int len;
12089
12090     len = strlen(buf);
12091     if (len > MSG_SIZ)
12092       len = MSG_SIZ;
12093   
12094     strncpy(temp, buf, len);
12095     temp[len] = 0;
12096
12097     p = temp;
12098     while (*p) {
12099         if (*p == '\n' || *p == '\r')
12100           *p = ' ';
12101         ++p;
12102     }
12103
12104     strcat(temp, "\n");
12105     SendToICS(temp);
12106     SendToPlayer(temp, strlen(temp));
12107 }
12108
12109 void
12110 SetWhiteToPlayEvent()
12111 {
12112     if (gameMode == EditPosition) {
12113         blackPlaysFirst = FALSE;
12114         DisplayBothClocks();    /* works because currentMove is 0 */
12115     } else if (gameMode == IcsExamining) {
12116         SendToICS(ics_prefix);
12117         SendToICS("tomove white\n");
12118     }
12119 }
12120
12121 void
12122 SetBlackToPlayEvent()
12123 {
12124     if (gameMode == EditPosition) {
12125         blackPlaysFirst = TRUE;
12126         currentMove = 1;        /* kludge */
12127         DisplayBothClocks();
12128         currentMove = 0;
12129     } else if (gameMode == IcsExamining) {
12130         SendToICS(ics_prefix);
12131         SendToICS("tomove black\n");
12132     }
12133 }
12134
12135 void
12136 EditPositionMenuEvent(selection, x, y)
12137      ChessSquare selection;
12138      int x, y;
12139 {
12140     char buf[MSG_SIZ];
12141     ChessSquare piece = boards[0][y][x];
12142
12143     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12144
12145     switch (selection) {
12146       case ClearBoard:
12147         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12148             SendToICS(ics_prefix);
12149             SendToICS("bsetup clear\n");
12150         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12151             SendToICS(ics_prefix);
12152             SendToICS("clearboard\n");
12153         } else {
12154             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12155                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12156                 for (y = 0; y < BOARD_HEIGHT; y++) {
12157                     if (gameMode == IcsExamining) {
12158                         if (boards[currentMove][y][x] != EmptySquare) {
12159                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12160                                     AAA + x, ONE + y);
12161                             SendToICS(buf);
12162                         }
12163                     } else {
12164                         boards[0][y][x] = p;
12165                     }
12166                 }
12167             }
12168         }
12169         if (gameMode == EditPosition) {
12170             DrawPosition(FALSE, boards[0]);
12171         }
12172         break;
12173
12174       case WhitePlay:
12175         SetWhiteToPlayEvent();
12176         break;
12177
12178       case BlackPlay:
12179         SetBlackToPlayEvent();
12180         break;
12181
12182       case EmptySquare:
12183         if (gameMode == IcsExamining) {
12184             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12185             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12186             SendToICS(buf);
12187         } else {
12188             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12189                 if(x == BOARD_LEFT-2) {
12190                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12191                     boards[0][y][1] = 0;
12192                 } else
12193                 if(x == BOARD_RGHT+1) {
12194                     if(y >= gameInfo.holdingsSize) break;
12195                     boards[0][y][BOARD_WIDTH-2] = 0;
12196                 } else break;
12197             }
12198             boards[0][y][x] = EmptySquare;
12199             DrawPosition(FALSE, boards[0]);
12200         }
12201         break;
12202
12203       case PromotePiece:
12204         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12205            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12206             selection = (ChessSquare) (PROMOTED piece);
12207         } else if(piece == EmptySquare) selection = WhiteSilver;
12208         else selection = (ChessSquare)((int)piece - 1);
12209         goto defaultlabel;
12210
12211       case DemotePiece:
12212         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12213            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12214             selection = (ChessSquare) (DEMOTED piece);
12215         } else if(piece == EmptySquare) selection = BlackSilver;
12216         else selection = (ChessSquare)((int)piece + 1);       
12217         goto defaultlabel;
12218
12219       case WhiteQueen:
12220       case BlackQueen:
12221         if(gameInfo.variant == VariantShatranj ||
12222            gameInfo.variant == VariantXiangqi  ||
12223            gameInfo.variant == VariantCourier  ||
12224            gameInfo.variant == VariantMakruk     )
12225             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12226         goto defaultlabel;
12227
12228       case WhiteKing:
12229       case BlackKing:
12230         if(gameInfo.variant == VariantXiangqi)
12231             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12232         if(gameInfo.variant == VariantKnightmate)
12233             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12234       default:
12235         defaultlabel:
12236         if (gameMode == IcsExamining) {
12237             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12238             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12239                     PieceToChar(selection), AAA + x, ONE + y);
12240             SendToICS(buf);
12241         } else {
12242             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12243                 int n;
12244                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12245                     n = PieceToNumber(selection - BlackPawn);
12246                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12247                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12248                     boards[0][BOARD_HEIGHT-1-n][1]++;
12249                 } else
12250                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12251                     n = PieceToNumber(selection);
12252                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12253                     boards[0][n][BOARD_WIDTH-1] = selection;
12254                     boards[0][n][BOARD_WIDTH-2]++;
12255                 }
12256             } else
12257             boards[0][y][x] = selection;
12258             DrawPosition(TRUE, boards[0]);
12259         }
12260         break;
12261     }
12262 }
12263
12264
12265 void
12266 DropMenuEvent(selection, x, y)
12267      ChessSquare selection;
12268      int x, y;
12269 {
12270     ChessMove moveType;
12271
12272     switch (gameMode) {
12273       case IcsPlayingWhite:
12274       case MachinePlaysBlack:
12275         if (!WhiteOnMove(currentMove)) {
12276             DisplayMoveError(_("It is Black's turn"));
12277             return;
12278         }
12279         moveType = WhiteDrop;
12280         break;
12281       case IcsPlayingBlack:
12282       case MachinePlaysWhite:
12283         if (WhiteOnMove(currentMove)) {
12284             DisplayMoveError(_("It is White's turn"));
12285             return;
12286         }
12287         moveType = BlackDrop;
12288         break;
12289       case EditGame:
12290         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12291         break;
12292       default:
12293         return;
12294     }
12295
12296     if (moveType == BlackDrop && selection < BlackPawn) {
12297       selection = (ChessSquare) ((int) selection
12298                                  + (int) BlackPawn - (int) WhitePawn);
12299     }
12300     if (boards[currentMove][y][x] != EmptySquare) {
12301         DisplayMoveError(_("That square is occupied"));
12302         return;
12303     }
12304
12305     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12306 }
12307
12308 void
12309 AcceptEvent()
12310 {
12311     /* Accept a pending offer of any kind from opponent */
12312     
12313     if (appData.icsActive) {
12314         SendToICS(ics_prefix);
12315         SendToICS("accept\n");
12316     } else if (cmailMsgLoaded) {
12317         if (currentMove == cmailOldMove &&
12318             commentList[cmailOldMove] != NULL &&
12319             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12320                    "Black offers a draw" : "White offers a draw")) {
12321             TruncateGame();
12322             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12323             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12324         } else {
12325             DisplayError(_("There is no pending offer on this move"), 0);
12326             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12327         }
12328     } else {
12329         /* Not used for offers from chess program */
12330     }
12331 }
12332
12333 void
12334 DeclineEvent()
12335 {
12336     /* Decline a pending offer of any kind from opponent */
12337     
12338     if (appData.icsActive) {
12339         SendToICS(ics_prefix);
12340         SendToICS("decline\n");
12341     } else if (cmailMsgLoaded) {
12342         if (currentMove == cmailOldMove &&
12343             commentList[cmailOldMove] != NULL &&
12344             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12345                    "Black offers a draw" : "White offers a draw")) {
12346 #ifdef NOTDEF
12347             AppendComment(cmailOldMove, "Draw declined", TRUE);
12348             DisplayComment(cmailOldMove - 1, "Draw declined");
12349 #endif /*NOTDEF*/
12350         } else {
12351             DisplayError(_("There is no pending offer on this move"), 0);
12352         }
12353     } else {
12354         /* Not used for offers from chess program */
12355     }
12356 }
12357
12358 void
12359 RematchEvent()
12360 {
12361     /* Issue ICS rematch command */
12362     if (appData.icsActive) {
12363         SendToICS(ics_prefix);
12364         SendToICS("rematch\n");
12365     }
12366 }
12367
12368 void
12369 CallFlagEvent()
12370 {
12371     /* Call your opponent's flag (claim a win on time) */
12372     if (appData.icsActive) {
12373         SendToICS(ics_prefix);
12374         SendToICS("flag\n");
12375     } else {
12376         switch (gameMode) {
12377           default:
12378             return;
12379           case MachinePlaysWhite:
12380             if (whiteFlag) {
12381                 if (blackFlag)
12382                   GameEnds(GameIsDrawn, "Both players ran out of time",
12383                            GE_PLAYER);
12384                 else
12385                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12386             } else {
12387                 DisplayError(_("Your opponent is not out of time"), 0);
12388             }
12389             break;
12390           case MachinePlaysBlack:
12391             if (blackFlag) {
12392                 if (whiteFlag)
12393                   GameEnds(GameIsDrawn, "Both players ran out of time",
12394                            GE_PLAYER);
12395                 else
12396                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12397             } else {
12398                 DisplayError(_("Your opponent is not out of time"), 0);
12399             }
12400             break;
12401         }
12402     }
12403 }
12404
12405 void
12406 DrawEvent()
12407 {
12408     /* Offer draw or accept pending draw offer from opponent */
12409     
12410     if (appData.icsActive) {
12411         /* Note: tournament rules require draw offers to be
12412            made after you make your move but before you punch
12413            your clock.  Currently ICS doesn't let you do that;
12414            instead, you immediately punch your clock after making
12415            a move, but you can offer a draw at any time. */
12416         
12417         SendToICS(ics_prefix);
12418         SendToICS("draw\n");
12419         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12420     } else if (cmailMsgLoaded) {
12421         if (currentMove == cmailOldMove &&
12422             commentList[cmailOldMove] != NULL &&
12423             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12424                    "Black offers a draw" : "White offers a draw")) {
12425             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12426             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12427         } else if (currentMove == cmailOldMove + 1) {
12428             char *offer = WhiteOnMove(cmailOldMove) ?
12429               "White offers a draw" : "Black offers a draw";
12430             AppendComment(currentMove, offer, TRUE);
12431             DisplayComment(currentMove - 1, offer);
12432             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12433         } else {
12434             DisplayError(_("You must make your move before offering a draw"), 0);
12435             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12436         }
12437     } else if (first.offeredDraw) {
12438         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12439     } else {
12440         if (first.sendDrawOffers) {
12441             SendToProgram("draw\n", &first);
12442             userOfferedDraw = TRUE;
12443         }
12444     }
12445 }
12446
12447 void
12448 AdjournEvent()
12449 {
12450     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12451     
12452     if (appData.icsActive) {
12453         SendToICS(ics_prefix);
12454         SendToICS("adjourn\n");
12455     } else {
12456         /* Currently GNU Chess doesn't offer or accept Adjourns */
12457     }
12458 }
12459
12460
12461 void
12462 AbortEvent()
12463 {
12464     /* Offer Abort or accept pending Abort offer from opponent */
12465     
12466     if (appData.icsActive) {
12467         SendToICS(ics_prefix);
12468         SendToICS("abort\n");
12469     } else {
12470         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12471     }
12472 }
12473
12474 void
12475 ResignEvent()
12476 {
12477     /* Resign.  You can do this even if it's not your turn. */
12478     
12479     if (appData.icsActive) {
12480         SendToICS(ics_prefix);
12481         SendToICS("resign\n");
12482     } else {
12483         switch (gameMode) {
12484           case MachinePlaysWhite:
12485             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12486             break;
12487           case MachinePlaysBlack:
12488             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12489             break;
12490           case EditGame:
12491             if (cmailMsgLoaded) {
12492                 TruncateGame();
12493                 if (WhiteOnMove(cmailOldMove)) {
12494                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12495                 } else {
12496                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12497                 }
12498                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12499             }
12500             break;
12501           default:
12502             break;
12503         }
12504     }
12505 }
12506
12507
12508 void
12509 StopObservingEvent()
12510 {
12511     /* Stop observing current games */
12512     SendToICS(ics_prefix);
12513     SendToICS("unobserve\n");
12514 }
12515
12516 void
12517 StopExaminingEvent()
12518 {
12519     /* Stop observing current game */
12520     SendToICS(ics_prefix);
12521     SendToICS("unexamine\n");
12522 }
12523
12524 void
12525 ForwardInner(target)
12526      int target;
12527 {
12528     int limit;
12529
12530     if (appData.debugMode)
12531         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12532                 target, currentMove, forwardMostMove);
12533
12534     if (gameMode == EditPosition)
12535       return;
12536
12537     if (gameMode == PlayFromGameFile && !pausing)
12538       PauseEvent();
12539     
12540     if (gameMode == IcsExamining && pausing)
12541       limit = pauseExamForwardMostMove;
12542     else
12543       limit = forwardMostMove;
12544     
12545     if (target > limit) target = limit;
12546
12547     if (target > 0 && moveList[target - 1][0]) {
12548         int fromX, fromY, toX, toY;
12549         toX = moveList[target - 1][2] - AAA;
12550         toY = moveList[target - 1][3] - ONE;
12551         if (moveList[target - 1][1] == '@') {
12552             if (appData.highlightLastMove) {
12553                 SetHighlights(-1, -1, toX, toY);
12554             }
12555         } else {
12556             fromX = moveList[target - 1][0] - AAA;
12557             fromY = moveList[target - 1][1] - ONE;
12558             if (target == currentMove + 1) {
12559                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12560             }
12561             if (appData.highlightLastMove) {
12562                 SetHighlights(fromX, fromY, toX, toY);
12563             }
12564         }
12565     }
12566     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12567         gameMode == Training || gameMode == PlayFromGameFile || 
12568         gameMode == AnalyzeFile) {
12569         while (currentMove < target) {
12570             SendMoveToProgram(currentMove++, &first);
12571         }
12572     } else {
12573         currentMove = target;
12574     }
12575     
12576     if (gameMode == EditGame || gameMode == EndOfGame) {
12577         whiteTimeRemaining = timeRemaining[0][currentMove];
12578         blackTimeRemaining = timeRemaining[1][currentMove];
12579     }
12580     DisplayBothClocks();
12581     DisplayMove(currentMove - 1);
12582     DrawPosition(FALSE, boards[currentMove]);
12583     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12584     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12585         DisplayComment(currentMove - 1, commentList[currentMove]);
12586     }
12587 }
12588
12589
12590 void
12591 ForwardEvent()
12592 {
12593     if (gameMode == IcsExamining && !pausing) {
12594         SendToICS(ics_prefix);
12595         SendToICS("forward\n");
12596     } else {
12597         ForwardInner(currentMove + 1);
12598     }
12599 }
12600
12601 void
12602 ToEndEvent()
12603 {
12604     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12605         /* to optimze, we temporarily turn off analysis mode while we feed
12606          * the remaining moves to the engine. Otherwise we get analysis output
12607          * after each move.
12608          */ 
12609         if (first.analysisSupport) {
12610           SendToProgram("exit\nforce\n", &first);
12611           first.analyzing = FALSE;
12612         }
12613     }
12614         
12615     if (gameMode == IcsExamining && !pausing) {
12616         SendToICS(ics_prefix);
12617         SendToICS("forward 999999\n");
12618     } else {
12619         ForwardInner(forwardMostMove);
12620     }
12621
12622     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12623         /* we have fed all the moves, so reactivate analysis mode */
12624         SendToProgram("analyze\n", &first);
12625         first.analyzing = TRUE;
12626         /*first.maybeThinking = TRUE;*/
12627         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12628     }
12629 }
12630
12631 void
12632 BackwardInner(target)
12633      int target;
12634 {
12635     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12636
12637     if (appData.debugMode)
12638         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12639                 target, currentMove, forwardMostMove);
12640
12641     if (gameMode == EditPosition) return;
12642     if (currentMove <= backwardMostMove) {
12643         ClearHighlights();
12644         DrawPosition(full_redraw, boards[currentMove]);
12645         return;
12646     }
12647     if (gameMode == PlayFromGameFile && !pausing)
12648       PauseEvent();
12649     
12650     if (moveList[target][0]) {
12651         int fromX, fromY, toX, toY;
12652         toX = moveList[target][2] - AAA;
12653         toY = moveList[target][3] - ONE;
12654         if (moveList[target][1] == '@') {
12655             if (appData.highlightLastMove) {
12656                 SetHighlights(-1, -1, toX, toY);
12657             }
12658         } else {
12659             fromX = moveList[target][0] - AAA;
12660             fromY = moveList[target][1] - ONE;
12661             if (target == currentMove - 1) {
12662                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12663             }
12664             if (appData.highlightLastMove) {
12665                 SetHighlights(fromX, fromY, toX, toY);
12666             }
12667         }
12668     }
12669     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12670         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12671         while (currentMove > target) {
12672             SendToProgram("undo\n", &first);
12673             currentMove--;
12674         }
12675     } else {
12676         currentMove = target;
12677     }
12678     
12679     if (gameMode == EditGame || gameMode == EndOfGame) {
12680         whiteTimeRemaining = timeRemaining[0][currentMove];
12681         blackTimeRemaining = timeRemaining[1][currentMove];
12682     }
12683     DisplayBothClocks();
12684     DisplayMove(currentMove - 1);
12685     DrawPosition(full_redraw, boards[currentMove]);
12686     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12687     // [HGM] PV info: routine tests if comment empty
12688     DisplayComment(currentMove - 1, commentList[currentMove]);
12689 }
12690
12691 void
12692 BackwardEvent()
12693 {
12694     if (gameMode == IcsExamining && !pausing) {
12695         SendToICS(ics_prefix);
12696         SendToICS("backward\n");
12697     } else {
12698         BackwardInner(currentMove - 1);
12699     }
12700 }
12701
12702 void
12703 ToStartEvent()
12704 {
12705     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12706         /* to optimize, we temporarily turn off analysis mode while we undo
12707          * all the moves. Otherwise we get analysis output after each undo.
12708          */ 
12709         if (first.analysisSupport) {
12710           SendToProgram("exit\nforce\n", &first);
12711           first.analyzing = FALSE;
12712         }
12713     }
12714
12715     if (gameMode == IcsExamining && !pausing) {
12716         SendToICS(ics_prefix);
12717         SendToICS("backward 999999\n");
12718     } else {
12719         BackwardInner(backwardMostMove);
12720     }
12721
12722     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12723         /* we have fed all the moves, so reactivate analysis mode */
12724         SendToProgram("analyze\n", &first);
12725         first.analyzing = TRUE;
12726         /*first.maybeThinking = TRUE;*/
12727         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12728     }
12729 }
12730
12731 void
12732 ToNrEvent(int to)
12733 {
12734   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12735   if (to >= forwardMostMove) to = forwardMostMove;
12736   if (to <= backwardMostMove) to = backwardMostMove;
12737   if (to < currentMove) {
12738     BackwardInner(to);
12739   } else {
12740     ForwardInner(to);
12741   }
12742 }
12743
12744 void
12745 RevertEvent(Boolean annotate)
12746 {
12747     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12748         return;
12749     }
12750     if (gameMode != IcsExamining) {
12751         DisplayError(_("You are not examining a game"), 0);
12752         return;
12753     }
12754     if (pausing) {
12755         DisplayError(_("You can't revert while pausing"), 0);
12756         return;
12757     }
12758     SendToICS(ics_prefix);
12759     SendToICS("revert\n");
12760 }
12761
12762 void
12763 RetractMoveEvent()
12764 {
12765     switch (gameMode) {
12766       case MachinePlaysWhite:
12767       case MachinePlaysBlack:
12768         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12769             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12770             return;
12771         }
12772         if (forwardMostMove < 2) return;
12773         currentMove = forwardMostMove = forwardMostMove - 2;
12774         whiteTimeRemaining = timeRemaining[0][currentMove];
12775         blackTimeRemaining = timeRemaining[1][currentMove];
12776         DisplayBothClocks();
12777         DisplayMove(currentMove - 1);
12778         ClearHighlights();/*!! could figure this out*/
12779         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12780         SendToProgram("remove\n", &first);
12781         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12782         break;
12783
12784       case BeginningOfGame:
12785       default:
12786         break;
12787
12788       case IcsPlayingWhite:
12789       case IcsPlayingBlack:
12790         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12791             SendToICS(ics_prefix);
12792             SendToICS("takeback 2\n");
12793         } else {
12794             SendToICS(ics_prefix);
12795             SendToICS("takeback 1\n");
12796         }
12797         break;
12798     }
12799 }
12800
12801 void
12802 MoveNowEvent()
12803 {
12804     ChessProgramState *cps;
12805
12806     switch (gameMode) {
12807       case MachinePlaysWhite:
12808         if (!WhiteOnMove(forwardMostMove)) {
12809             DisplayError(_("It is your turn"), 0);
12810             return;
12811         }
12812         cps = &first;
12813         break;
12814       case MachinePlaysBlack:
12815         if (WhiteOnMove(forwardMostMove)) {
12816             DisplayError(_("It is your turn"), 0);
12817             return;
12818         }
12819         cps = &first;
12820         break;
12821       case TwoMachinesPlay:
12822         if (WhiteOnMove(forwardMostMove) ==
12823             (first.twoMachinesColor[0] == 'w')) {
12824             cps = &first;
12825         } else {
12826             cps = &second;
12827         }
12828         break;
12829       case BeginningOfGame:
12830       default:
12831         return;
12832     }
12833     SendToProgram("?\n", cps);
12834 }
12835
12836 void
12837 TruncateGameEvent()
12838 {
12839     EditGameEvent();
12840     if (gameMode != EditGame) return;
12841     TruncateGame();
12842 }
12843
12844 void
12845 TruncateGame()
12846 {
12847     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12848     if (forwardMostMove > currentMove) {
12849         if (gameInfo.resultDetails != NULL) {
12850             free(gameInfo.resultDetails);
12851             gameInfo.resultDetails = NULL;
12852             gameInfo.result = GameUnfinished;
12853         }
12854         forwardMostMove = currentMove;
12855         HistorySet(parseList, backwardMostMove, forwardMostMove,
12856                    currentMove-1);
12857     }
12858 }
12859
12860 void
12861 HintEvent()
12862 {
12863     if (appData.noChessProgram) return;
12864     switch (gameMode) {
12865       case MachinePlaysWhite:
12866         if (WhiteOnMove(forwardMostMove)) {
12867             DisplayError(_("Wait until your turn"), 0);
12868             return;
12869         }
12870         break;
12871       case BeginningOfGame:
12872       case MachinePlaysBlack:
12873         if (!WhiteOnMove(forwardMostMove)) {
12874             DisplayError(_("Wait until your turn"), 0);
12875             return;
12876         }
12877         break;
12878       default:
12879         DisplayError(_("No hint available"), 0);
12880         return;
12881     }
12882     SendToProgram("hint\n", &first);
12883     hintRequested = TRUE;
12884 }
12885
12886 void
12887 BookEvent()
12888 {
12889     if (appData.noChessProgram) return;
12890     switch (gameMode) {
12891       case MachinePlaysWhite:
12892         if (WhiteOnMove(forwardMostMove)) {
12893             DisplayError(_("Wait until your turn"), 0);
12894             return;
12895         }
12896         break;
12897       case BeginningOfGame:
12898       case MachinePlaysBlack:
12899         if (!WhiteOnMove(forwardMostMove)) {
12900             DisplayError(_("Wait until your turn"), 0);
12901             return;
12902         }
12903         break;
12904       case EditPosition:
12905         EditPositionDone(TRUE);
12906         break;
12907       case TwoMachinesPlay:
12908         return;
12909       default:
12910         break;
12911     }
12912     SendToProgram("bk\n", &first);
12913     bookOutput[0] = NULLCHAR;
12914     bookRequested = TRUE;
12915 }
12916
12917 void
12918 AboutGameEvent()
12919 {
12920     char *tags = PGNTags(&gameInfo);
12921     TagsPopUp(tags, CmailMsg());
12922     free(tags);
12923 }
12924
12925 /* end button procedures */
12926
12927 void
12928 PrintPosition(fp, move)
12929      FILE *fp;
12930      int move;
12931 {
12932     int i, j;
12933     
12934     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12935         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12936             char c = PieceToChar(boards[move][i][j]);
12937             fputc(c == 'x' ? '.' : c, fp);
12938             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12939         }
12940     }
12941     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12942       fprintf(fp, "white to play\n");
12943     else
12944       fprintf(fp, "black to play\n");
12945 }
12946
12947 void
12948 PrintOpponents(fp)
12949      FILE *fp;
12950 {
12951     if (gameInfo.white != NULL) {
12952         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12953     } else {
12954         fprintf(fp, "\n");
12955     }
12956 }
12957
12958 /* Find last component of program's own name, using some heuristics */
12959 void
12960 TidyProgramName(prog, host, buf)
12961      char *prog, *host, buf[MSG_SIZ];
12962 {
12963     char *p, *q;
12964     int local = (strcmp(host, "localhost") == 0);
12965     while (!local && (p = strchr(prog, ';')) != NULL) {
12966         p++;
12967         while (*p == ' ') p++;
12968         prog = p;
12969     }
12970     if (*prog == '"' || *prog == '\'') {
12971         q = strchr(prog + 1, *prog);
12972     } else {
12973         q = strchr(prog, ' ');
12974     }
12975     if (q == NULL) q = prog + strlen(prog);
12976     p = q;
12977     while (p >= prog && *p != '/' && *p != '\\') p--;
12978     p++;
12979     if(p == prog && *p == '"') p++;
12980     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12981     memcpy(buf, p, q - p);
12982     buf[q - p] = NULLCHAR;
12983     if (!local) {
12984         strcat(buf, "@");
12985         strcat(buf, host);
12986     }
12987 }
12988
12989 char *
12990 TimeControlTagValue()
12991 {
12992     char buf[MSG_SIZ];
12993     if (!appData.clockMode) {
12994         strcpy(buf, "-");
12995     } else if (movesPerSession > 0) {
12996         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12997     } else if (timeIncrement == 0) {
12998         sprintf(buf, "%ld", timeControl/1000);
12999     } else {
13000         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13001     }
13002     return StrSave(buf);
13003 }
13004
13005 void
13006 SetGameInfo()
13007 {
13008     /* This routine is used only for certain modes */
13009     VariantClass v = gameInfo.variant;
13010     ChessMove r = GameUnfinished;
13011     char *p = NULL;
13012
13013     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13014         r = gameInfo.result; 
13015         p = gameInfo.resultDetails; 
13016         gameInfo.resultDetails = NULL;
13017     }
13018     ClearGameInfo(&gameInfo);
13019     gameInfo.variant = v;
13020
13021     switch (gameMode) {
13022       case MachinePlaysWhite:
13023         gameInfo.event = StrSave( appData.pgnEventHeader );
13024         gameInfo.site = StrSave(HostName());
13025         gameInfo.date = PGNDate();
13026         gameInfo.round = StrSave("-");
13027         gameInfo.white = StrSave(first.tidy);
13028         gameInfo.black = StrSave(UserName());
13029         gameInfo.timeControl = TimeControlTagValue();
13030         break;
13031
13032       case MachinePlaysBlack:
13033         gameInfo.event = StrSave( appData.pgnEventHeader );
13034         gameInfo.site = StrSave(HostName());
13035         gameInfo.date = PGNDate();
13036         gameInfo.round = StrSave("-");
13037         gameInfo.white = StrSave(UserName());
13038         gameInfo.black = StrSave(first.tidy);
13039         gameInfo.timeControl = TimeControlTagValue();
13040         break;
13041
13042       case TwoMachinesPlay:
13043         gameInfo.event = StrSave( appData.pgnEventHeader );
13044         gameInfo.site = StrSave(HostName());
13045         gameInfo.date = PGNDate();
13046         if (matchGame > 0) {
13047             char buf[MSG_SIZ];
13048             sprintf(buf, "%d", matchGame);
13049             gameInfo.round = StrSave(buf);
13050         } else {
13051             gameInfo.round = StrSave("-");
13052         }
13053         if (first.twoMachinesColor[0] == 'w') {
13054             gameInfo.white = StrSave(first.tidy);
13055             gameInfo.black = StrSave(second.tidy);
13056         } else {
13057             gameInfo.white = StrSave(second.tidy);
13058             gameInfo.black = StrSave(first.tidy);
13059         }
13060         gameInfo.timeControl = TimeControlTagValue();
13061         break;
13062
13063       case EditGame:
13064         gameInfo.event = StrSave("Edited game");
13065         gameInfo.site = StrSave(HostName());
13066         gameInfo.date = PGNDate();
13067         gameInfo.round = StrSave("-");
13068         gameInfo.white = StrSave("-");
13069         gameInfo.black = StrSave("-");
13070         gameInfo.result = r;
13071         gameInfo.resultDetails = p;
13072         break;
13073
13074       case EditPosition:
13075         gameInfo.event = StrSave("Edited position");
13076         gameInfo.site = StrSave(HostName());
13077         gameInfo.date = PGNDate();
13078         gameInfo.round = StrSave("-");
13079         gameInfo.white = StrSave("-");
13080         gameInfo.black = StrSave("-");
13081         break;
13082
13083       case IcsPlayingWhite:
13084       case IcsPlayingBlack:
13085       case IcsObserving:
13086       case IcsExamining:
13087         break;
13088
13089       case PlayFromGameFile:
13090         gameInfo.event = StrSave("Game from non-PGN file");
13091         gameInfo.site = StrSave(HostName());
13092         gameInfo.date = PGNDate();
13093         gameInfo.round = StrSave("-");
13094         gameInfo.white = StrSave("?");
13095         gameInfo.black = StrSave("?");
13096         break;
13097
13098       default:
13099         break;
13100     }
13101 }
13102
13103 void
13104 ReplaceComment(index, text)
13105      int index;
13106      char *text;
13107 {
13108     int len;
13109
13110     while (*text == '\n') text++;
13111     len = strlen(text);
13112     while (len > 0 && text[len - 1] == '\n') len--;
13113
13114     if (commentList[index] != NULL)
13115       free(commentList[index]);
13116
13117     if (len == 0) {
13118         commentList[index] = NULL;
13119         return;
13120     }
13121   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13122       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13123       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13124     commentList[index] = (char *) malloc(len + 2);
13125     strncpy(commentList[index], text, len);
13126     commentList[index][len] = '\n';
13127     commentList[index][len + 1] = NULLCHAR;
13128   } else { 
13129     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13130     char *p;
13131     commentList[index] = (char *) malloc(len + 6);
13132     strcpy(commentList[index], "{\n");
13133     strncpy(commentList[index]+2, text, len);
13134     commentList[index][len+2] = NULLCHAR;
13135     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13136     strcat(commentList[index], "\n}\n");
13137   }
13138 }
13139
13140 void
13141 CrushCRs(text)
13142      char *text;
13143 {
13144   char *p = text;
13145   char *q = text;
13146   char ch;
13147
13148   do {
13149     ch = *p++;
13150     if (ch == '\r') continue;
13151     *q++ = ch;
13152   } while (ch != '\0');
13153 }
13154
13155 void
13156 AppendComment(index, text, addBraces)
13157      int index;
13158      char *text;
13159      Boolean addBraces; // [HGM] braces: tells if we should add {}
13160 {
13161     int oldlen, len;
13162     char *old;
13163
13164 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13165     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13166
13167     CrushCRs(text);
13168     while (*text == '\n') text++;
13169     len = strlen(text);
13170     while (len > 0 && text[len - 1] == '\n') len--;
13171
13172     if (len == 0) return;
13173
13174     if (commentList[index] != NULL) {
13175         old = commentList[index];
13176         oldlen = strlen(old);
13177         while(commentList[index][oldlen-1] ==  '\n')
13178           commentList[index][--oldlen] = NULLCHAR;
13179         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13180         strcpy(commentList[index], old);
13181         free(old);
13182         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13183         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13184           if(addBraces) addBraces = FALSE; else { text++; len--; }
13185           while (*text == '\n') { text++; len--; }
13186           commentList[index][--oldlen] = NULLCHAR;
13187       }
13188         if(addBraces) strcat(commentList[index], "\n{\n");
13189         else          strcat(commentList[index], "\n");
13190         strcat(commentList[index], text);
13191         if(addBraces) strcat(commentList[index], "\n}\n");
13192         else          strcat(commentList[index], "\n");
13193     } else {
13194         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13195         if(addBraces)
13196              strcpy(commentList[index], "{\n");
13197         else commentList[index][0] = NULLCHAR;
13198         strcat(commentList[index], text);
13199         strcat(commentList[index], "\n");
13200         if(addBraces) strcat(commentList[index], "}\n");
13201     }
13202 }
13203
13204 static char * FindStr( char * text, char * sub_text )
13205 {
13206     char * result = strstr( text, sub_text );
13207
13208     if( result != NULL ) {
13209         result += strlen( sub_text );
13210     }
13211
13212     return result;
13213 }
13214
13215 /* [AS] Try to extract PV info from PGN comment */
13216 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13217 char *GetInfoFromComment( int index, char * text )
13218 {
13219     char * sep = text;
13220
13221     if( text != NULL && index > 0 ) {
13222         int score = 0;
13223         int depth = 0;
13224         int time = -1, sec = 0, deci;
13225         char * s_eval = FindStr( text, "[%eval " );
13226         char * s_emt = FindStr( text, "[%emt " );
13227
13228         if( s_eval != NULL || s_emt != NULL ) {
13229             /* New style */
13230             char delim;
13231
13232             if( s_eval != NULL ) {
13233                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13234                     return text;
13235                 }
13236
13237                 if( delim != ']' ) {
13238                     return text;
13239                 }
13240             }
13241
13242             if( s_emt != NULL ) {
13243             }
13244                 return text;
13245         }
13246         else {
13247             /* We expect something like: [+|-]nnn.nn/dd */
13248             int score_lo = 0;
13249
13250             if(*text != '{') return text; // [HGM] braces: must be normal comment
13251
13252             sep = strchr( text, '/' );
13253             if( sep == NULL || sep < (text+4) ) {
13254                 return text;
13255             }
13256
13257             time = -1; sec = -1; deci = -1;
13258             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13259                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13260                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13261                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13262                 return text;
13263             }
13264
13265             if( score_lo < 0 || score_lo >= 100 ) {
13266                 return text;
13267             }
13268
13269             if(sec >= 0) time = 600*time + 10*sec; else
13270             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13271
13272             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13273
13274             /* [HGM] PV time: now locate end of PV info */
13275             while( *++sep >= '0' && *sep <= '9'); // strip depth
13276             if(time >= 0)
13277             while( *++sep >= '0' && *sep <= '9'); // strip time
13278             if(sec >= 0)
13279             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13280             if(deci >= 0)
13281             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13282             while(*sep == ' ') sep++;
13283         }
13284
13285         if( depth <= 0 ) {
13286             return text;
13287         }
13288
13289         if( time < 0 ) {
13290             time = -1;
13291         }
13292
13293         pvInfoList[index-1].depth = depth;
13294         pvInfoList[index-1].score = score;
13295         pvInfoList[index-1].time  = 10*time; // centi-sec
13296         if(*sep == '}') *sep = 0; else *--sep = '{';
13297     }
13298     return sep;
13299 }
13300
13301 void
13302 SendToProgram(message, cps)
13303      char *message;
13304      ChessProgramState *cps;
13305 {
13306     int count, outCount, error;
13307     char buf[MSG_SIZ];
13308
13309     if (cps->pr == NULL) return;
13310     Attention(cps);
13311     
13312     if (appData.debugMode) {
13313         TimeMark now;
13314         GetTimeMark(&now);
13315         fprintf(debugFP, "%ld >%-6s: %s", 
13316                 SubtractTimeMarks(&now, &programStartTime),
13317                 cps->which, message);
13318     }
13319     
13320     count = strlen(message);
13321     outCount = OutputToProcess(cps->pr, message, count, &error);
13322     if (outCount < count && !exiting 
13323                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13324         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13325         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13326             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13327                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13328                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13329             } else {
13330                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13331             }
13332             gameInfo.resultDetails = StrSave(buf);
13333         }
13334         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13335     }
13336 }
13337
13338 void
13339 ReceiveFromProgram(isr, closure, message, count, error)
13340      InputSourceRef isr;
13341      VOIDSTAR closure;
13342      char *message;
13343      int count;
13344      int error;
13345 {
13346     char *end_str;
13347     char buf[MSG_SIZ];
13348     ChessProgramState *cps = (ChessProgramState *)closure;
13349
13350     if (isr != cps->isr) return; /* Killed intentionally */
13351     if (count <= 0) {
13352         if (count == 0) {
13353             sprintf(buf,
13354                     _("Error: %s chess program (%s) exited unexpectedly"),
13355                     cps->which, cps->program);
13356         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13357                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13358                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13359                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13360                 } else {
13361                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13362                 }
13363                 gameInfo.resultDetails = StrSave(buf);
13364             }
13365             RemoveInputSource(cps->isr);
13366             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13367         } else {
13368             sprintf(buf,
13369                     _("Error reading from %s chess program (%s)"),
13370                     cps->which, cps->program);
13371             RemoveInputSource(cps->isr);
13372
13373             /* [AS] Program is misbehaving badly... kill it */
13374             if( count == -2 ) {
13375                 DestroyChildProcess( cps->pr, 9 );
13376                 cps->pr = NoProc;
13377             }
13378
13379             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13380         }
13381         return;
13382     }
13383     
13384     if ((end_str = strchr(message, '\r')) != NULL)
13385       *end_str = NULLCHAR;
13386     if ((end_str = strchr(message, '\n')) != NULL)
13387       *end_str = NULLCHAR;
13388     
13389     if (appData.debugMode) {
13390         TimeMark now; int print = 1;
13391         char *quote = ""; char c; int i;
13392
13393         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13394                 char start = message[0];
13395                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13396                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13397                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13398                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13399                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13400                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13401                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13402                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13403                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13404                     print = (appData.engineComments >= 2);
13405                 }
13406                 message[0] = start; // restore original message
13407         }
13408         if(print) {
13409                 GetTimeMark(&now);
13410                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13411                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13412                         quote,
13413                         message);
13414         }
13415     }
13416
13417     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13418     if (appData.icsEngineAnalyze) {
13419         if (strstr(message, "whisper") != NULL ||
13420              strstr(message, "kibitz") != NULL || 
13421             strstr(message, "tellics") != NULL) return;
13422     }
13423
13424     HandleMachineMove(message, cps);
13425 }
13426
13427
13428 void
13429 SendTimeControl(cps, mps, tc, inc, sd, st)
13430      ChessProgramState *cps;
13431      int mps, inc, sd, st;
13432      long tc;
13433 {
13434     char buf[MSG_SIZ];
13435     int seconds;
13436
13437     if( timeControl_2 > 0 ) {
13438         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13439             tc = timeControl_2;
13440         }
13441     }
13442     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13443     inc /= cps->timeOdds;
13444     st  /= cps->timeOdds;
13445
13446     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13447
13448     if (st > 0) {
13449       /* Set exact time per move, normally using st command */
13450       if (cps->stKludge) {
13451         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13452         seconds = st % 60;
13453         if (seconds == 0) {
13454           sprintf(buf, "level 1 %d\n", st/60);
13455         } else {
13456           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13457         }
13458       } else {
13459         sprintf(buf, "st %d\n", st);
13460       }
13461     } else {
13462       /* Set conventional or incremental time control, using level command */
13463       if (seconds == 0) {
13464         /* Note old gnuchess bug -- minutes:seconds used to not work.
13465            Fixed in later versions, but still avoid :seconds
13466            when seconds is 0. */
13467         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13468       } else {
13469         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13470                 seconds, inc/1000);
13471       }
13472     }
13473     SendToProgram(buf, cps);
13474
13475     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13476     /* Orthogonally, limit search to given depth */
13477     if (sd > 0) {
13478       if (cps->sdKludge) {
13479         sprintf(buf, "depth\n%d\n", sd);
13480       } else {
13481         sprintf(buf, "sd %d\n", sd);
13482       }
13483       SendToProgram(buf, cps);
13484     }
13485
13486     if(cps->nps > 0) { /* [HGM] nps */
13487         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13488         else {
13489                 sprintf(buf, "nps %d\n", cps->nps);
13490               SendToProgram(buf, cps);
13491         }
13492     }
13493 }
13494
13495 ChessProgramState *WhitePlayer()
13496 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13497 {
13498     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13499        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13500         return &second;
13501     return &first;
13502 }
13503
13504 void
13505 SendTimeRemaining(cps, machineWhite)
13506      ChessProgramState *cps;
13507      int /*boolean*/ machineWhite;
13508 {
13509     char message[MSG_SIZ];
13510     long time, otime;
13511
13512     /* Note: this routine must be called when the clocks are stopped
13513        or when they have *just* been set or switched; otherwise
13514        it will be off by the time since the current tick started.
13515     */
13516     if (machineWhite) {
13517         time = whiteTimeRemaining / 10;
13518         otime = blackTimeRemaining / 10;
13519     } else {
13520         time = blackTimeRemaining / 10;
13521         otime = whiteTimeRemaining / 10;
13522     }
13523     /* [HGM] translate opponent's time by time-odds factor */
13524     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13525     if (appData.debugMode) {
13526         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13527     }
13528
13529     if (time <= 0) time = 1;
13530     if (otime <= 0) otime = 1;
13531     
13532     sprintf(message, "time %ld\n", time);
13533     SendToProgram(message, cps);
13534
13535     sprintf(message, "otim %ld\n", otime);
13536     SendToProgram(message, cps);
13537 }
13538
13539 int
13540 BoolFeature(p, name, loc, cps)
13541      char **p;
13542      char *name;
13543      int *loc;
13544      ChessProgramState *cps;
13545 {
13546   char buf[MSG_SIZ];
13547   int len = strlen(name);
13548   int val;
13549   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13550     (*p) += len + 1;
13551     sscanf(*p, "%d", &val);
13552     *loc = (val != 0);
13553     while (**p && **p != ' ') (*p)++;
13554     sprintf(buf, "accepted %s\n", name);
13555     SendToProgram(buf, cps);
13556     return TRUE;
13557   }
13558   return FALSE;
13559 }
13560
13561 int
13562 IntFeature(p, name, loc, cps)
13563      char **p;
13564      char *name;
13565      int *loc;
13566      ChessProgramState *cps;
13567 {
13568   char buf[MSG_SIZ];
13569   int len = strlen(name);
13570   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13571     (*p) += len + 1;
13572     sscanf(*p, "%d", loc);
13573     while (**p && **p != ' ') (*p)++;
13574     sprintf(buf, "accepted %s\n", name);
13575     SendToProgram(buf, cps);
13576     return TRUE;
13577   }
13578   return FALSE;
13579 }
13580
13581 int
13582 StringFeature(p, name, loc, cps)
13583      char **p;
13584      char *name;
13585      char loc[];
13586      ChessProgramState *cps;
13587 {
13588   char buf[MSG_SIZ];
13589   int len = strlen(name);
13590   if (strncmp((*p), name, len) == 0
13591       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13592     (*p) += len + 2;
13593     sscanf(*p, "%[^\"]", loc);
13594     while (**p && **p != '\"') (*p)++;
13595     if (**p == '\"') (*p)++;
13596     sprintf(buf, "accepted %s\n", name);
13597     SendToProgram(buf, cps);
13598     return TRUE;
13599   }
13600   return FALSE;
13601 }
13602
13603 int 
13604 ParseOption(Option *opt, ChessProgramState *cps)
13605 // [HGM] options: process the string that defines an engine option, and determine
13606 // name, type, default value, and allowed value range
13607 {
13608         char *p, *q, buf[MSG_SIZ];
13609         int n, min = (-1)<<31, max = 1<<31, def;
13610
13611         if(p = strstr(opt->name, " -spin ")) {
13612             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13613             if(max < min) max = min; // enforce consistency
13614             if(def < min) def = min;
13615             if(def > max) def = max;
13616             opt->value = def;
13617             opt->min = min;
13618             opt->max = max;
13619             opt->type = Spin;
13620         } else if((p = strstr(opt->name, " -slider "))) {
13621             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13622             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13623             if(max < min) max = min; // enforce consistency
13624             if(def < min) def = min;
13625             if(def > max) def = max;
13626             opt->value = def;
13627             opt->min = min;
13628             opt->max = max;
13629             opt->type = Spin; // Slider;
13630         } else if((p = strstr(opt->name, " -string "))) {
13631             opt->textValue = p+9;
13632             opt->type = TextBox;
13633         } else if((p = strstr(opt->name, " -file "))) {
13634             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13635             opt->textValue = p+7;
13636             opt->type = TextBox; // FileName;
13637         } else if((p = strstr(opt->name, " -path "))) {
13638             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13639             opt->textValue = p+7;
13640             opt->type = TextBox; // PathName;
13641         } else if(p = strstr(opt->name, " -check ")) {
13642             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13643             opt->value = (def != 0);
13644             opt->type = CheckBox;
13645         } else if(p = strstr(opt->name, " -combo ")) {
13646             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13647             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13648             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13649             opt->value = n = 0;
13650             while(q = StrStr(q, " /// ")) {
13651                 n++; *q = 0;    // count choices, and null-terminate each of them
13652                 q += 5;
13653                 if(*q == '*') { // remember default, which is marked with * prefix
13654                     q++;
13655                     opt->value = n;
13656                 }
13657                 cps->comboList[cps->comboCnt++] = q;
13658             }
13659             cps->comboList[cps->comboCnt++] = NULL;
13660             opt->max = n + 1;
13661             opt->type = ComboBox;
13662         } else if(p = strstr(opt->name, " -button")) {
13663             opt->type = Button;
13664         } else if(p = strstr(opt->name, " -save")) {
13665             opt->type = SaveButton;
13666         } else return FALSE;
13667         *p = 0; // terminate option name
13668         // now look if the command-line options define a setting for this engine option.
13669         if(cps->optionSettings && cps->optionSettings[0])
13670             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13671         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13672                 sprintf(buf, "option %s", p);
13673                 if(p = strstr(buf, ",")) *p = 0;
13674                 strcat(buf, "\n");
13675                 SendToProgram(buf, cps);
13676         }
13677         return TRUE;
13678 }
13679
13680 void
13681 FeatureDone(cps, val)
13682      ChessProgramState* cps;
13683      int val;
13684 {
13685   DelayedEventCallback cb = GetDelayedEvent();
13686   if ((cb == InitBackEnd3 && cps == &first) ||
13687       (cb == TwoMachinesEventIfReady && cps == &second)) {
13688     CancelDelayedEvent();
13689     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13690   }
13691   cps->initDone = val;
13692 }
13693
13694 /* Parse feature command from engine */
13695 void
13696 ParseFeatures(args, cps)
13697      char* args;
13698      ChessProgramState *cps;  
13699 {
13700   char *p = args;
13701   char *q;
13702   int val;
13703   char buf[MSG_SIZ];
13704
13705   for (;;) {
13706     while (*p == ' ') p++;
13707     if (*p == NULLCHAR) return;
13708
13709     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13710     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13711     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13712     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13713     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13714     if (BoolFeature(&p, "reuse", &val, cps)) {
13715       /* Engine can disable reuse, but can't enable it if user said no */
13716       if (!val) cps->reuse = FALSE;
13717       continue;
13718     }
13719     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13720     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13721       if (gameMode == TwoMachinesPlay) {
13722         DisplayTwoMachinesTitle();
13723       } else {
13724         DisplayTitle("");
13725       }
13726       continue;
13727     }
13728     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13729     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13730     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13731     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13732     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13733     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13734     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13735     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13736     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13737     if (IntFeature(&p, "done", &val, cps)) {
13738       FeatureDone(cps, val);
13739       continue;
13740     }
13741     /* Added by Tord: */
13742     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13743     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13744     /* End of additions by Tord */
13745
13746     /* [HGM] added features: */
13747     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13748     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13749     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13750     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13751     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13752     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13753     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13754         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13755             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13756             SendToProgram(buf, cps);
13757             continue;
13758         }
13759         if(cps->nrOptions >= MAX_OPTIONS) {
13760             cps->nrOptions--;
13761             sprintf(buf, "%s engine has too many options\n", cps->which);
13762             DisplayError(buf, 0);
13763         }
13764         continue;
13765     }
13766     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13767     /* End of additions by HGM */
13768
13769     /* unknown feature: complain and skip */
13770     q = p;
13771     while (*q && *q != '=') q++;
13772     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13773     SendToProgram(buf, cps);
13774     p = q;
13775     if (*p == '=') {
13776       p++;
13777       if (*p == '\"') {
13778         p++;
13779         while (*p && *p != '\"') p++;
13780         if (*p == '\"') p++;
13781       } else {
13782         while (*p && *p != ' ') p++;
13783       }
13784     }
13785   }
13786
13787 }
13788
13789 void
13790 PeriodicUpdatesEvent(newState)
13791      int newState;
13792 {
13793     if (newState == appData.periodicUpdates)
13794       return;
13795
13796     appData.periodicUpdates=newState;
13797
13798     /* Display type changes, so update it now */
13799 //    DisplayAnalysis();
13800
13801     /* Get the ball rolling again... */
13802     if (newState) {
13803         AnalysisPeriodicEvent(1);
13804         StartAnalysisClock();
13805     }
13806 }
13807
13808 void
13809 PonderNextMoveEvent(newState)
13810      int newState;
13811 {
13812     if (newState == appData.ponderNextMove) return;
13813     if (gameMode == EditPosition) EditPositionDone(TRUE);
13814     if (newState) {
13815         SendToProgram("hard\n", &first);
13816         if (gameMode == TwoMachinesPlay) {
13817             SendToProgram("hard\n", &second);
13818         }
13819     } else {
13820         SendToProgram("easy\n", &first);
13821         thinkOutput[0] = NULLCHAR;
13822         if (gameMode == TwoMachinesPlay) {
13823             SendToProgram("easy\n", &second);
13824         }
13825     }
13826     appData.ponderNextMove = newState;
13827 }
13828
13829 void
13830 NewSettingEvent(option, command, value)
13831      char *command;
13832      int option, value;
13833 {
13834     char buf[MSG_SIZ];
13835
13836     if (gameMode == EditPosition) EditPositionDone(TRUE);
13837     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13838     SendToProgram(buf, &first);
13839     if (gameMode == TwoMachinesPlay) {
13840         SendToProgram(buf, &second);
13841     }
13842 }
13843
13844 void
13845 ShowThinkingEvent()
13846 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13847 {
13848     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13849     int newState = appData.showThinking
13850         // [HGM] thinking: other features now need thinking output as well
13851         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13852     
13853     if (oldState == newState) return;
13854     oldState = newState;
13855     if (gameMode == EditPosition) EditPositionDone(TRUE);
13856     if (oldState) {
13857         SendToProgram("post\n", &first);
13858         if (gameMode == TwoMachinesPlay) {
13859             SendToProgram("post\n", &second);
13860         }
13861     } else {
13862         SendToProgram("nopost\n", &first);
13863         thinkOutput[0] = NULLCHAR;
13864         if (gameMode == TwoMachinesPlay) {
13865             SendToProgram("nopost\n", &second);
13866         }
13867     }
13868 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13869 }
13870
13871 void
13872 AskQuestionEvent(title, question, replyPrefix, which)
13873      char *title; char *question; char *replyPrefix; char *which;
13874 {
13875   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13876   if (pr == NoProc) return;
13877   AskQuestion(title, question, replyPrefix, pr);
13878 }
13879
13880 void
13881 DisplayMove(moveNumber)
13882      int moveNumber;
13883 {
13884     char message[MSG_SIZ];
13885     char res[MSG_SIZ];
13886     char cpThinkOutput[MSG_SIZ];
13887
13888     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13889     
13890     if (moveNumber == forwardMostMove - 1 || 
13891         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13892
13893         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13894
13895         if (strchr(cpThinkOutput, '\n')) {
13896             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13897         }
13898     } else {
13899         *cpThinkOutput = NULLCHAR;
13900     }
13901
13902     /* [AS] Hide thinking from human user */
13903     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13904         *cpThinkOutput = NULLCHAR;
13905         if( thinkOutput[0] != NULLCHAR ) {
13906             int i;
13907
13908             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13909                 cpThinkOutput[i] = '.';
13910             }
13911             cpThinkOutput[i] = NULLCHAR;
13912             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13913         }
13914     }
13915
13916     if (moveNumber == forwardMostMove - 1 &&
13917         gameInfo.resultDetails != NULL) {
13918         if (gameInfo.resultDetails[0] == NULLCHAR) {
13919             sprintf(res, " %s", PGNResult(gameInfo.result));
13920         } else {
13921             sprintf(res, " {%s} %s",
13922                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13923         }
13924     } else {
13925         res[0] = NULLCHAR;
13926     }
13927
13928     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13929         DisplayMessage(res, cpThinkOutput);
13930     } else {
13931         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13932                 WhiteOnMove(moveNumber) ? " " : ".. ",
13933                 parseList[moveNumber], res);
13934         DisplayMessage(message, cpThinkOutput);
13935     }
13936 }
13937
13938 void
13939 DisplayComment(moveNumber, text)
13940      int moveNumber;
13941      char *text;
13942 {
13943     char title[MSG_SIZ];
13944     char buf[8000]; // comment can be long!
13945     int score, depth;
13946     
13947     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13948       strcpy(title, "Comment");
13949     } else {
13950       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13951               WhiteOnMove(moveNumber) ? " " : ".. ",
13952               parseList[moveNumber]);
13953     }
13954     // [HGM] PV info: display PV info together with (or as) comment
13955     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13956       if(text == NULL) text = "";                                           
13957       score = pvInfoList[moveNumber].score;
13958       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13959               depth, (pvInfoList[moveNumber].time+50)/100, text);
13960       text = buf;
13961     }
13962     if (text != NULL && (appData.autoDisplayComment || commentUp))
13963         CommentPopUp(title, text);
13964 }
13965
13966 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13967  * might be busy thinking or pondering.  It can be omitted if your
13968  * gnuchess is configured to stop thinking immediately on any user
13969  * input.  However, that gnuchess feature depends on the FIONREAD
13970  * ioctl, which does not work properly on some flavors of Unix.
13971  */
13972 void
13973 Attention(cps)
13974      ChessProgramState *cps;
13975 {
13976 #if ATTENTION
13977     if (!cps->useSigint) return;
13978     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13979     switch (gameMode) {
13980       case MachinePlaysWhite:
13981       case MachinePlaysBlack:
13982       case TwoMachinesPlay:
13983       case IcsPlayingWhite:
13984       case IcsPlayingBlack:
13985       case AnalyzeMode:
13986       case AnalyzeFile:
13987         /* Skip if we know it isn't thinking */
13988         if (!cps->maybeThinking) return;
13989         if (appData.debugMode)
13990           fprintf(debugFP, "Interrupting %s\n", cps->which);
13991         InterruptChildProcess(cps->pr);
13992         cps->maybeThinking = FALSE;
13993         break;
13994       default:
13995         break;
13996     }
13997 #endif /*ATTENTION*/
13998 }
13999
14000 int
14001 CheckFlags()
14002 {
14003     if (whiteTimeRemaining <= 0) {
14004         if (!whiteFlag) {
14005             whiteFlag = TRUE;
14006             if (appData.icsActive) {
14007                 if (appData.autoCallFlag &&
14008                     gameMode == IcsPlayingBlack && !blackFlag) {
14009                   SendToICS(ics_prefix);
14010                   SendToICS("flag\n");
14011                 }
14012             } else {
14013                 if (blackFlag) {
14014                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14015                 } else {
14016                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14017                     if (appData.autoCallFlag) {
14018                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14019                         return TRUE;
14020                     }
14021                 }
14022             }
14023         }
14024     }
14025     if (blackTimeRemaining <= 0) {
14026         if (!blackFlag) {
14027             blackFlag = TRUE;
14028             if (appData.icsActive) {
14029                 if (appData.autoCallFlag &&
14030                     gameMode == IcsPlayingWhite && !whiteFlag) {
14031                   SendToICS(ics_prefix);
14032                   SendToICS("flag\n");
14033                 }
14034             } else {
14035                 if (whiteFlag) {
14036                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14037                 } else {
14038                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14039                     if (appData.autoCallFlag) {
14040                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14041                         return TRUE;
14042                     }
14043                 }
14044             }
14045         }
14046     }
14047     return FALSE;
14048 }
14049
14050 void
14051 CheckTimeControl()
14052 {
14053     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14054         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14055
14056     /*
14057      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14058      */
14059     if ( !WhiteOnMove(forwardMostMove) )
14060         /* White made time control */
14061         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14062         /* [HGM] time odds: correct new time quota for time odds! */
14063                                             / WhitePlayer()->timeOdds;
14064       else
14065         /* Black made time control */
14066         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14067                                             / WhitePlayer()->other->timeOdds;
14068 }
14069
14070 void
14071 DisplayBothClocks()
14072 {
14073     int wom = gameMode == EditPosition ?
14074       !blackPlaysFirst : WhiteOnMove(currentMove);
14075     DisplayWhiteClock(whiteTimeRemaining, wom);
14076     DisplayBlackClock(blackTimeRemaining, !wom);
14077 }
14078
14079
14080 /* Timekeeping seems to be a portability nightmare.  I think everyone
14081    has ftime(), but I'm really not sure, so I'm including some ifdefs
14082    to use other calls if you don't.  Clocks will be less accurate if
14083    you have neither ftime nor gettimeofday.
14084 */
14085
14086 /* VS 2008 requires the #include outside of the function */
14087 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14088 #include <sys/timeb.h>
14089 #endif
14090
14091 /* Get the current time as a TimeMark */
14092 void
14093 GetTimeMark(tm)
14094      TimeMark *tm;
14095 {
14096 #if HAVE_GETTIMEOFDAY
14097
14098     struct timeval timeVal;
14099     struct timezone timeZone;
14100
14101     gettimeofday(&timeVal, &timeZone);
14102     tm->sec = (long) timeVal.tv_sec; 
14103     tm->ms = (int) (timeVal.tv_usec / 1000L);
14104
14105 #else /*!HAVE_GETTIMEOFDAY*/
14106 #if HAVE_FTIME
14107
14108 // include <sys/timeb.h> / moved to just above start of function
14109     struct timeb timeB;
14110
14111     ftime(&timeB);
14112     tm->sec = (long) timeB.time;
14113     tm->ms = (int) timeB.millitm;
14114
14115 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14116     tm->sec = (long) time(NULL);
14117     tm->ms = 0;
14118 #endif
14119 #endif
14120 }
14121
14122 /* Return the difference in milliseconds between two
14123    time marks.  We assume the difference will fit in a long!
14124 */
14125 long
14126 SubtractTimeMarks(tm2, tm1)
14127      TimeMark *tm2, *tm1;
14128 {
14129     return 1000L*(tm2->sec - tm1->sec) +
14130            (long) (tm2->ms - tm1->ms);
14131 }
14132
14133
14134 /*
14135  * Code to manage the game clocks.
14136  *
14137  * In tournament play, black starts the clock and then white makes a move.
14138  * We give the human user a slight advantage if he is playing white---the
14139  * clocks don't run until he makes his first move, so it takes zero time.
14140  * Also, we don't account for network lag, so we could get out of sync
14141  * with GNU Chess's clock -- but then, referees are always right.  
14142  */
14143
14144 static TimeMark tickStartTM;
14145 static long intendedTickLength;
14146
14147 long
14148 NextTickLength(timeRemaining)
14149      long timeRemaining;
14150 {
14151     long nominalTickLength, nextTickLength;
14152
14153     if (timeRemaining > 0L && timeRemaining <= 10000L)
14154       nominalTickLength = 100L;
14155     else
14156       nominalTickLength = 1000L;
14157     nextTickLength = timeRemaining % nominalTickLength;
14158     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14159
14160     return nextTickLength;
14161 }
14162
14163 /* Adjust clock one minute up or down */
14164 void
14165 AdjustClock(Boolean which, int dir)
14166 {
14167     if(which) blackTimeRemaining += 60000*dir;
14168     else      whiteTimeRemaining += 60000*dir;
14169     DisplayBothClocks();
14170 }
14171
14172 /* Stop clocks and reset to a fresh time control */
14173 void
14174 ResetClocks() 
14175 {
14176     (void) StopClockTimer();
14177     if (appData.icsActive) {
14178         whiteTimeRemaining = blackTimeRemaining = 0;
14179     } else if (searchTime) {
14180         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14181         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14182     } else { /* [HGM] correct new time quote for time odds */
14183         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14184         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14185     }
14186     if (whiteFlag || blackFlag) {
14187         DisplayTitle("");
14188         whiteFlag = blackFlag = FALSE;
14189     }
14190     DisplayBothClocks();
14191 }
14192
14193 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14194
14195 /* Decrement running clock by amount of time that has passed */
14196 void
14197 DecrementClocks()
14198 {
14199     long timeRemaining;
14200     long lastTickLength, fudge;
14201     TimeMark now;
14202
14203     if (!appData.clockMode) return;
14204     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14205         
14206     GetTimeMark(&now);
14207
14208     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14209
14210     /* Fudge if we woke up a little too soon */
14211     fudge = intendedTickLength - lastTickLength;
14212     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14213
14214     if (WhiteOnMove(forwardMostMove)) {
14215         if(whiteNPS >= 0) lastTickLength = 0;
14216         timeRemaining = whiteTimeRemaining -= lastTickLength;
14217         DisplayWhiteClock(whiteTimeRemaining - fudge,
14218                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14219     } else {
14220         if(blackNPS >= 0) lastTickLength = 0;
14221         timeRemaining = blackTimeRemaining -= lastTickLength;
14222         DisplayBlackClock(blackTimeRemaining - fudge,
14223                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14224     }
14225
14226     if (CheckFlags()) return;
14227         
14228     tickStartTM = now;
14229     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14230     StartClockTimer(intendedTickLength);
14231
14232     /* if the time remaining has fallen below the alarm threshold, sound the
14233      * alarm. if the alarm has sounded and (due to a takeback or time control
14234      * with increment) the time remaining has increased to a level above the
14235      * threshold, reset the alarm so it can sound again. 
14236      */
14237     
14238     if (appData.icsActive && appData.icsAlarm) {
14239
14240         /* make sure we are dealing with the user's clock */
14241         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14242                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14243            )) return;
14244
14245         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14246             alarmSounded = FALSE;
14247         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14248             PlayAlarmSound();
14249             alarmSounded = TRUE;
14250         }
14251     }
14252 }
14253
14254
14255 /* A player has just moved, so stop the previously running
14256    clock and (if in clock mode) start the other one.
14257    We redisplay both clocks in case we're in ICS mode, because
14258    ICS gives us an update to both clocks after every move.
14259    Note that this routine is called *after* forwardMostMove
14260    is updated, so the last fractional tick must be subtracted
14261    from the color that is *not* on move now.
14262 */
14263 void
14264 SwitchClocks(int newMoveNr)
14265 {
14266     long lastTickLength;
14267     TimeMark now;
14268     int flagged = FALSE;
14269
14270     GetTimeMark(&now);
14271
14272     if (StopClockTimer() && appData.clockMode) {
14273         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14274         if (!WhiteOnMove(forwardMostMove)) {
14275             if(blackNPS >= 0) lastTickLength = 0;
14276             blackTimeRemaining -= lastTickLength;
14277            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14278 //         if(pvInfoList[forwardMostMove-1].time == -1)
14279                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14280                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14281         } else {
14282            if(whiteNPS >= 0) lastTickLength = 0;
14283            whiteTimeRemaining -= lastTickLength;
14284            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14285 //         if(pvInfoList[forwardMostMove-1].time == -1)
14286                  pvInfoList[forwardMostMove-1].time = 
14287                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14288         }
14289         flagged = CheckFlags();
14290     }
14291     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14292     CheckTimeControl();
14293
14294     if (flagged || !appData.clockMode) return;
14295
14296     switch (gameMode) {
14297       case MachinePlaysBlack:
14298       case MachinePlaysWhite:
14299       case BeginningOfGame:
14300         if (pausing) return;
14301         break;
14302
14303       case EditGame:
14304       case PlayFromGameFile:
14305       case IcsExamining:
14306         return;
14307
14308       default:
14309         break;
14310     }
14311
14312     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14313         if(WhiteOnMove(forwardMostMove))
14314              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14315         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14316     }
14317
14318     tickStartTM = now;
14319     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14320       whiteTimeRemaining : blackTimeRemaining);
14321     StartClockTimer(intendedTickLength);
14322 }
14323         
14324
14325 /* Stop both clocks */
14326 void
14327 StopClocks()
14328 {       
14329     long lastTickLength;
14330     TimeMark now;
14331
14332     if (!StopClockTimer()) return;
14333     if (!appData.clockMode) return;
14334
14335     GetTimeMark(&now);
14336
14337     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14338     if (WhiteOnMove(forwardMostMove)) {
14339         if(whiteNPS >= 0) lastTickLength = 0;
14340         whiteTimeRemaining -= lastTickLength;
14341         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14342     } else {
14343         if(blackNPS >= 0) lastTickLength = 0;
14344         blackTimeRemaining -= lastTickLength;
14345         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14346     }
14347     CheckFlags();
14348 }
14349         
14350 /* Start clock of player on move.  Time may have been reset, so
14351    if clock is already running, stop and restart it. */
14352 void
14353 StartClocks()
14354 {
14355     (void) StopClockTimer(); /* in case it was running already */
14356     DisplayBothClocks();
14357     if (CheckFlags()) return;
14358
14359     if (!appData.clockMode) return;
14360     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14361
14362     GetTimeMark(&tickStartTM);
14363     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14364       whiteTimeRemaining : blackTimeRemaining);
14365
14366    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14367     whiteNPS = blackNPS = -1; 
14368     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14369        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14370         whiteNPS = first.nps;
14371     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14372        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14373         blackNPS = first.nps;
14374     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14375         whiteNPS = second.nps;
14376     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14377         blackNPS = second.nps;
14378     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14379
14380     StartClockTimer(intendedTickLength);
14381 }
14382
14383 char *
14384 TimeString(ms)
14385      long ms;
14386 {
14387     long second, minute, hour, day;
14388     char *sign = "";
14389     static char buf[32];
14390     
14391     if (ms > 0 && ms <= 9900) {
14392       /* convert milliseconds to tenths, rounding up */
14393       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14394
14395       sprintf(buf, " %03.1f ", tenths/10.0);
14396       return buf;
14397     }
14398
14399     /* convert milliseconds to seconds, rounding up */
14400     /* use floating point to avoid strangeness of integer division
14401        with negative dividends on many machines */
14402     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14403
14404     if (second < 0) {
14405         sign = "-";
14406         second = -second;
14407     }
14408     
14409     day = second / (60 * 60 * 24);
14410     second = second % (60 * 60 * 24);
14411     hour = second / (60 * 60);
14412     second = second % (60 * 60);
14413     minute = second / 60;
14414     second = second % 60;
14415     
14416     if (day > 0)
14417       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14418               sign, day, hour, minute, second);
14419     else if (hour > 0)
14420       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14421     else
14422       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14423     
14424     return buf;
14425 }
14426
14427
14428 /*
14429  * This is necessary because some C libraries aren't ANSI C compliant yet.
14430  */
14431 char *
14432 StrStr(string, match)
14433      char *string, *match;
14434 {
14435     int i, length;
14436     
14437     length = strlen(match);
14438     
14439     for (i = strlen(string) - length; i >= 0; i--, string++)
14440       if (!strncmp(match, string, length))
14441         return string;
14442     
14443     return NULL;
14444 }
14445
14446 char *
14447 StrCaseStr(string, match)
14448      char *string, *match;
14449 {
14450     int i, j, length;
14451     
14452     length = strlen(match);
14453     
14454     for (i = strlen(string) - length; i >= 0; i--, string++) {
14455         for (j = 0; j < length; j++) {
14456             if (ToLower(match[j]) != ToLower(string[j]))
14457               break;
14458         }
14459         if (j == length) return string;
14460     }
14461
14462     return NULL;
14463 }
14464
14465 #ifndef _amigados
14466 int
14467 StrCaseCmp(s1, s2)
14468      char *s1, *s2;
14469 {
14470     char c1, c2;
14471     
14472     for (;;) {
14473         c1 = ToLower(*s1++);
14474         c2 = ToLower(*s2++);
14475         if (c1 > c2) return 1;
14476         if (c1 < c2) return -1;
14477         if (c1 == NULLCHAR) return 0;
14478     }
14479 }
14480
14481
14482 int
14483 ToLower(c)
14484      int c;
14485 {
14486     return isupper(c) ? tolower(c) : c;
14487 }
14488
14489
14490 int
14491 ToUpper(c)
14492      int c;
14493 {
14494     return islower(c) ? toupper(c) : c;
14495 }
14496 #endif /* !_amigados    */
14497
14498 char *
14499 StrSave(s)
14500      char *s;
14501 {
14502     char *ret;
14503
14504     if ((ret = (char *) malloc(strlen(s) + 1))) {
14505         strcpy(ret, s);
14506     }
14507     return ret;
14508 }
14509
14510 char *
14511 StrSavePtr(s, savePtr)
14512      char *s, **savePtr;
14513 {
14514     if (*savePtr) {
14515         free(*savePtr);
14516     }
14517     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14518         strcpy(*savePtr, s);
14519     }
14520     return(*savePtr);
14521 }
14522
14523 char *
14524 PGNDate()
14525 {
14526     time_t clock;
14527     struct tm *tm;
14528     char buf[MSG_SIZ];
14529
14530     clock = time((time_t *)NULL);
14531     tm = localtime(&clock);
14532     sprintf(buf, "%04d.%02d.%02d",
14533             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14534     return StrSave(buf);
14535 }
14536
14537
14538 char *
14539 PositionToFEN(move, overrideCastling)
14540      int move;
14541      char *overrideCastling;
14542 {
14543     int i, j, fromX, fromY, toX, toY;
14544     int whiteToPlay;
14545     char buf[128];
14546     char *p, *q;
14547     int emptycount;
14548     ChessSquare piece;
14549
14550     whiteToPlay = (gameMode == EditPosition) ?
14551       !blackPlaysFirst : (move % 2 == 0);
14552     p = buf;
14553
14554     /* Piece placement data */
14555     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14556         emptycount = 0;
14557         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14558             if (boards[move][i][j] == EmptySquare) {
14559                 emptycount++;
14560             } else { ChessSquare piece = boards[move][i][j];
14561                 if (emptycount > 0) {
14562                     if(emptycount<10) /* [HGM] can be >= 10 */
14563                         *p++ = '0' + emptycount;
14564                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14565                     emptycount = 0;
14566                 }
14567                 if(PieceToChar(piece) == '+') {
14568                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14569                     *p++ = '+';
14570                     piece = (ChessSquare)(DEMOTED piece);
14571                 } 
14572                 *p++ = PieceToChar(piece);
14573                 if(p[-1] == '~') {
14574                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14575                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14576                     *p++ = '~';
14577                 }
14578             }
14579         }
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         *p++ = '/';
14587     }
14588     *(p - 1) = ' ';
14589
14590     /* [HGM] print Crazyhouse or Shogi holdings */
14591     if( gameInfo.holdingsWidth ) {
14592         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14593         q = p;
14594         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14595             piece = boards[move][i][BOARD_WIDTH-1];
14596             if( piece != EmptySquare )
14597               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14598                   *p++ = PieceToChar(piece);
14599         }
14600         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14601             piece = boards[move][BOARD_HEIGHT-i-1][0];
14602             if( piece != EmptySquare )
14603               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14604                   *p++ = PieceToChar(piece);
14605         }
14606
14607         if( q == p ) *p++ = '-';
14608         *p++ = ']';
14609         *p++ = ' ';
14610     }
14611
14612     /* Active color */
14613     *p++ = whiteToPlay ? 'w' : 'b';
14614     *p++ = ' ';
14615
14616   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14617     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14618   } else {
14619   if(nrCastlingRights) {
14620      q = p;
14621      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14622        /* [HGM] write directly from rights */
14623            if(boards[move][CASTLING][2] != NoRights &&
14624               boards[move][CASTLING][0] != NoRights   )
14625                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14626            if(boards[move][CASTLING][2] != NoRights &&
14627               boards[move][CASTLING][1] != NoRights   )
14628                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14629            if(boards[move][CASTLING][5] != NoRights &&
14630               boards[move][CASTLING][3] != NoRights   )
14631                 *p++ = boards[move][CASTLING][3] + AAA;
14632            if(boards[move][CASTLING][5] != NoRights &&
14633               boards[move][CASTLING][4] != NoRights   )
14634                 *p++ = boards[move][CASTLING][4] + AAA;
14635      } else {
14636
14637         /* [HGM] write true castling rights */
14638         if( nrCastlingRights == 6 ) {
14639             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14640                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14641             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14642                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14643             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14644                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14645             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14646                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14647         }
14648      }
14649      if (q == p) *p++ = '-'; /* No castling rights */
14650      *p++ = ' ';
14651   }
14652
14653   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14654      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14655     /* En passant target square */
14656     if (move > backwardMostMove) {
14657         fromX = moveList[move - 1][0] - AAA;
14658         fromY = moveList[move - 1][1] - ONE;
14659         toX = moveList[move - 1][2] - AAA;
14660         toY = moveList[move - 1][3] - ONE;
14661         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14662             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14663             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14664             fromX == toX) {
14665             /* 2-square pawn move just happened */
14666             *p++ = toX + AAA;
14667             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14668         } else {
14669             *p++ = '-';
14670         }
14671     } else if(move == backwardMostMove) {
14672         // [HGM] perhaps we should always do it like this, and forget the above?
14673         if((signed char)boards[move][EP_STATUS] >= 0) {
14674             *p++ = boards[move][EP_STATUS] + AAA;
14675             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14676         } else {
14677             *p++ = '-';
14678         }
14679     } else {
14680         *p++ = '-';
14681     }
14682     *p++ = ' ';
14683   }
14684   }
14685
14686     /* [HGM] find reversible plies */
14687     {   int i = 0, j=move;
14688
14689         if (appData.debugMode) { int k;
14690             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14691             for(k=backwardMostMove; k<=forwardMostMove; k++)
14692                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14693
14694         }
14695
14696         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14697         if( j == backwardMostMove ) i += initialRulePlies;
14698         sprintf(p, "%d ", i);
14699         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14700     }
14701     /* Fullmove number */
14702     sprintf(p, "%d", (move / 2) + 1);
14703     
14704     return StrSave(buf);
14705 }
14706
14707 Boolean
14708 ParseFEN(board, blackPlaysFirst, fen)
14709     Board board;
14710      int *blackPlaysFirst;
14711      char *fen;
14712 {
14713     int i, j;
14714     char *p;
14715     int emptycount;
14716     ChessSquare piece;
14717
14718     p = fen;
14719
14720     /* [HGM] by default clear Crazyhouse holdings, if present */
14721     if(gameInfo.holdingsWidth) {
14722        for(i=0; i<BOARD_HEIGHT; i++) {
14723            board[i][0]             = EmptySquare; /* black holdings */
14724            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14725            board[i][1]             = (ChessSquare) 0; /* black counts */
14726            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14727        }
14728     }
14729
14730     /* Piece placement data */
14731     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14732         j = 0;
14733         for (;;) {
14734             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14735                 if (*p == '/') p++;
14736                 emptycount = gameInfo.boardWidth - j;
14737                 while (emptycount--)
14738                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14739                 break;
14740 #if(BOARD_FILES >= 10)
14741             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14742                 p++; emptycount=10;
14743                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14744                 while (emptycount--)
14745                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14746 #endif
14747             } else if (isdigit(*p)) {
14748                 emptycount = *p++ - '0';
14749                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14750                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14751                 while (emptycount--)
14752                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14753             } else if (*p == '+' || isalpha(*p)) {
14754                 if (j >= gameInfo.boardWidth) return FALSE;
14755                 if(*p=='+') {
14756                     piece = CharToPiece(*++p);
14757                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14758                     piece = (ChessSquare) (PROMOTED piece ); p++;
14759                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14760                 } else piece = CharToPiece(*p++);
14761
14762                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14763                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14764                     piece = (ChessSquare) (PROMOTED piece);
14765                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14766                     p++;
14767                 }
14768                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14769             } else {
14770                 return FALSE;
14771             }
14772         }
14773     }
14774     while (*p == '/' || *p == ' ') p++;
14775
14776     /* [HGM] look for Crazyhouse holdings here */
14777     while(*p==' ') p++;
14778     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14779         if(*p == '[') p++;
14780         if(*p == '-' ) *p++; /* empty holdings */ else {
14781             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14782             /* if we would allow FEN reading to set board size, we would   */
14783             /* have to add holdings and shift the board read so far here   */
14784             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14785                 *p++;
14786                 if((int) piece >= (int) BlackPawn ) {
14787                     i = (int)piece - (int)BlackPawn;
14788                     i = PieceToNumber((ChessSquare)i);
14789                     if( i >= gameInfo.holdingsSize ) return FALSE;
14790                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14791                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14792                 } else {
14793                     i = (int)piece - (int)WhitePawn;
14794                     i = PieceToNumber((ChessSquare)i);
14795                     if( i >= gameInfo.holdingsSize ) return FALSE;
14796                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14797                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14798                 }
14799             }
14800         }
14801         if(*p == ']') *p++;
14802     }
14803
14804     while(*p == ' ') p++;
14805
14806     /* Active color */
14807     switch (*p++) {
14808       case 'w':
14809         *blackPlaysFirst = FALSE;
14810         break;
14811       case 'b': 
14812         *blackPlaysFirst = TRUE;
14813         break;
14814       default:
14815         return FALSE;
14816     }
14817
14818     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14819     /* return the extra info in global variiables             */
14820
14821     /* set defaults in case FEN is incomplete */
14822     board[EP_STATUS] = EP_UNKNOWN;
14823     for(i=0; i<nrCastlingRights; i++ ) {
14824         board[CASTLING][i] =
14825             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14826     }   /* assume possible unless obviously impossible */
14827     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14828     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14829     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14830                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14831     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14832     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14833     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14834                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14835     FENrulePlies = 0;
14836
14837     while(*p==' ') p++;
14838     if(nrCastlingRights) {
14839       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14840           /* castling indicator present, so default becomes no castlings */
14841           for(i=0; i<nrCastlingRights; i++ ) {
14842                  board[CASTLING][i] = NoRights;
14843           }
14844       }
14845       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14846              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14847              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14848              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14849         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14850
14851         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14852             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14853             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14854         }
14855         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14856             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14857         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14858                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14859         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14860                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14861         switch(c) {
14862           case'K':
14863               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14864               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14865               board[CASTLING][2] = whiteKingFile;
14866               break;
14867           case'Q':
14868               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14869               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14870               board[CASTLING][2] = whiteKingFile;
14871               break;
14872           case'k':
14873               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14874               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14875               board[CASTLING][5] = blackKingFile;
14876               break;
14877           case'q':
14878               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14879               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14880               board[CASTLING][5] = blackKingFile;
14881           case '-':
14882               break;
14883           default: /* FRC castlings */
14884               if(c >= 'a') { /* black rights */
14885                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14886                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14887                   if(i == BOARD_RGHT) break;
14888                   board[CASTLING][5] = i;
14889                   c -= AAA;
14890                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14891                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14892                   if(c > i)
14893                       board[CASTLING][3] = c;
14894                   else
14895                       board[CASTLING][4] = c;
14896               } else { /* white rights */
14897                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14898                     if(board[0][i] == WhiteKing) break;
14899                   if(i == BOARD_RGHT) break;
14900                   board[CASTLING][2] = i;
14901                   c -= AAA - 'a' + 'A';
14902                   if(board[0][c] >= WhiteKing) break;
14903                   if(c > i)
14904                       board[CASTLING][0] = c;
14905                   else
14906                       board[CASTLING][1] = c;
14907               }
14908         }
14909       }
14910       for(i=0; i<nrCastlingRights; i++)
14911         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14912     if (appData.debugMode) {
14913         fprintf(debugFP, "FEN castling rights:");
14914         for(i=0; i<nrCastlingRights; i++)
14915         fprintf(debugFP, " %d", board[CASTLING][i]);
14916         fprintf(debugFP, "\n");
14917     }
14918
14919       while(*p==' ') p++;
14920     }
14921
14922     /* read e.p. field in games that know e.p. capture */
14923     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14924        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14925       if(*p=='-') {
14926         p++; board[EP_STATUS] = EP_NONE;
14927       } else {
14928          char c = *p++ - AAA;
14929
14930          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14931          if(*p >= '0' && *p <='9') *p++;
14932          board[EP_STATUS] = c;
14933       }
14934     }
14935
14936
14937     if(sscanf(p, "%d", &i) == 1) {
14938         FENrulePlies = i; /* 50-move ply counter */
14939         /* (The move number is still ignored)    */
14940     }
14941
14942     return TRUE;
14943 }
14944       
14945 void
14946 EditPositionPasteFEN(char *fen)
14947 {
14948   if (fen != NULL) {
14949     Board initial_position;
14950
14951     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14952       DisplayError(_("Bad FEN position in clipboard"), 0);
14953       return ;
14954     } else {
14955       int savedBlackPlaysFirst = blackPlaysFirst;
14956       EditPositionEvent();
14957       blackPlaysFirst = savedBlackPlaysFirst;
14958       CopyBoard(boards[0], initial_position);
14959       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14960       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14961       DisplayBothClocks();
14962       DrawPosition(FALSE, boards[currentMove]);
14963     }
14964   }
14965 }
14966
14967 static char cseq[12] = "\\   ";
14968
14969 Boolean set_cont_sequence(char *new_seq)
14970 {
14971     int len;
14972     Boolean ret;
14973
14974     // handle bad attempts to set the sequence
14975         if (!new_seq)
14976                 return 0; // acceptable error - no debug
14977
14978     len = strlen(new_seq);
14979     ret = (len > 0) && (len < sizeof(cseq));
14980     if (ret)
14981         strcpy(cseq, new_seq);
14982     else if (appData.debugMode)
14983         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14984     return ret;
14985 }
14986
14987 /*
14988     reformat a source message so words don't cross the width boundary.  internal
14989     newlines are not removed.  returns the wrapped size (no null character unless
14990     included in source message).  If dest is NULL, only calculate the size required
14991     for the dest buffer.  lp argument indicats line position upon entry, and it's
14992     passed back upon exit.
14993 */
14994 int wrap(char *dest, char *src, int count, int width, int *lp)
14995 {
14996     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14997
14998     cseq_len = strlen(cseq);
14999     old_line = line = *lp;
15000     ansi = len = clen = 0;
15001
15002     for (i=0; i < count; i++)
15003     {
15004         if (src[i] == '\033')
15005             ansi = 1;
15006
15007         // if we hit the width, back up
15008         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15009         {
15010             // store i & len in case the word is too long
15011             old_i = i, old_len = len;
15012
15013             // find the end of the last word
15014             while (i && src[i] != ' ' && src[i] != '\n')
15015             {
15016                 i--;
15017                 len--;
15018             }
15019
15020             // word too long?  restore i & len before splitting it
15021             if ((old_i-i+clen) >= width)
15022             {
15023                 i = old_i;
15024                 len = old_len;
15025             }
15026
15027             // extra space?
15028             if (i && src[i-1] == ' ')
15029                 len--;
15030
15031             if (src[i] != ' ' && src[i] != '\n')
15032             {
15033                 i--;
15034                 if (len)
15035                     len--;
15036             }
15037
15038             // now append the newline and continuation sequence
15039             if (dest)
15040                 dest[len] = '\n';
15041             len++;
15042             if (dest)
15043                 strncpy(dest+len, cseq, cseq_len);
15044             len += cseq_len;
15045             line = cseq_len;
15046             clen = cseq_len;
15047             continue;
15048         }
15049
15050         if (dest)
15051             dest[len] = src[i];
15052         len++;
15053         if (!ansi)
15054             line++;
15055         if (src[i] == '\n')
15056             line = 0;
15057         if (src[i] == 'm')
15058             ansi = 0;
15059     }
15060     if (dest && appData.debugMode)
15061     {
15062         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15063             count, width, line, len, *lp);
15064         show_bytes(debugFP, src, count);
15065         fprintf(debugFP, "\ndest: ");
15066         show_bytes(debugFP, dest, len);
15067         fprintf(debugFP, "\n");
15068     }
15069     *lp = dest ? line : old_line;
15070
15071     return len;
15072 }
15073
15074 // [HGM] vari: routines for shelving variations
15075
15076 void 
15077 PushTail(int firstMove, int lastMove)
15078 {
15079         int i, j, nrMoves = lastMove - firstMove;
15080
15081         if(appData.icsActive) { // only in local mode
15082                 forwardMostMove = currentMove; // mimic old ICS behavior
15083                 return;
15084         }
15085         if(storedGames >= MAX_VARIATIONS-1) return;
15086
15087         // push current tail of game on stack
15088         savedResult[storedGames] = gameInfo.result;
15089         savedDetails[storedGames] = gameInfo.resultDetails;
15090         gameInfo.resultDetails = NULL;
15091         savedFirst[storedGames] = firstMove;
15092         savedLast [storedGames] = lastMove;
15093         savedFramePtr[storedGames] = framePtr;
15094         framePtr -= nrMoves; // reserve space for the boards
15095         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15096             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15097             for(j=0; j<MOVE_LEN; j++)
15098                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15099             for(j=0; j<2*MOVE_LEN; j++)
15100                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15101             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15102             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15103             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15104             pvInfoList[firstMove+i-1].depth = 0;
15105             commentList[framePtr+i] = commentList[firstMove+i];
15106             commentList[firstMove+i] = NULL;
15107         }
15108
15109         storedGames++;
15110         forwardMostMove = firstMove; // truncate game so we can start variation
15111         if(storedGames == 1) GreyRevert(FALSE);
15112 }
15113
15114 Boolean
15115 PopTail(Boolean annotate)
15116 {
15117         int i, j, nrMoves;
15118         char buf[8000], moveBuf[20];
15119
15120         if(appData.icsActive) return FALSE; // only in local mode
15121         if(!storedGames) return FALSE; // sanity
15122         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15123
15124         storedGames--;
15125         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15126         nrMoves = savedLast[storedGames] - currentMove;
15127         if(annotate) {
15128                 int cnt = 10;
15129                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15130                 else strcpy(buf, "(");
15131                 for(i=currentMove; i<forwardMostMove; i++) {
15132                         if(WhiteOnMove(i))
15133                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15134                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15135                         strcat(buf, moveBuf);
15136                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15137                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15138                 }
15139                 strcat(buf, ")");
15140         }
15141         for(i=1; i<=nrMoves; i++) { // copy last variation back
15142             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15143             for(j=0; j<MOVE_LEN; j++)
15144                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15145             for(j=0; j<2*MOVE_LEN; j++)
15146                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15147             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15148             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15149             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15150             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15151             commentList[currentMove+i] = commentList[framePtr+i];
15152             commentList[framePtr+i] = NULL;
15153         }
15154         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15155         framePtr = savedFramePtr[storedGames];
15156         gameInfo.result = savedResult[storedGames];
15157         if(gameInfo.resultDetails != NULL) {
15158             free(gameInfo.resultDetails);
15159       }
15160         gameInfo.resultDetails = savedDetails[storedGames];
15161         forwardMostMove = currentMove + nrMoves;
15162         if(storedGames == 0) GreyRevert(TRUE);
15163         return TRUE;
15164 }
15165
15166 void 
15167 CleanupTail()
15168 {       // remove all shelved variations
15169         int i;
15170         for(i=0; i<storedGames; i++) {
15171             if(savedDetails[i])
15172                 free(savedDetails[i]);
15173             savedDetails[i] = NULL;
15174         }
15175         for(i=framePtr; i<MAX_MOVES; i++) {
15176                 if(commentList[i]) free(commentList[i]);
15177                 commentList[i] = NULL;
15178         }
15179         framePtr = MAX_MOVES-1;
15180         storedGames = 0;
15181 }
15182
15183 void
15184 LoadVariation(int index, char *text)
15185 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15186         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15187         int level = 0, move;
15188
15189         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15190         // first find outermost bracketing variation
15191         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15192             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15193                 if(*p == '{') wait = '}'; else
15194                 if(*p == '[') wait = ']'; else
15195                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15196                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15197             }
15198             if(*p == wait) wait = NULLCHAR; // closing ]} found
15199             p++;
15200         }
15201         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15202         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15203         end[1] = NULLCHAR; // clip off comment beyond variation
15204         ToNrEvent(currentMove-1);
15205         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15206         // kludge: use ParsePV() to append variation to game
15207         move = currentMove;
15208         ParsePV(start, TRUE);
15209         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15210         ClearPremoveHighlights();
15211         CommentPopDown();
15212         ToNrEvent(currentMove+1);
15213 }
15214