caa8e19e5fe50edd4982e2817e20aeea9e3e9644
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 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((void));
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 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 // [HGM] seekgraph
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 char *seekAdList[MAX_SEEK_ADS];
2072 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2073 float tcList[MAX_SEEK_ADS];
2074 int nrOfSeekAds = 0;
2075 int minRating = 1010, maxRating = 2800;
2076 int hMargin = 10, vMargin = 20, h, w;
2077 extern int squareSize, lineGap;
2078
2079 void
2080 PlotSeekAd(int i)
2081 {
2082         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2083         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2084         zList[i] = 0;
2085         if(r < minRating+100 && r >=0 ) r = minRating+100;
2086         if(r > maxRating) r = maxRating;
2087         if(tc < 1.) tc = 1.;
2088         if(tc > 95.) tc = 95.;
2089         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2090         y = ((double)r - minRating)/(maxRating - minRating)
2091             * (h-vMargin-squareSize/8) + vMargin;
2092         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2093         if(strstr(seekAdList[i], " u ")) color = 1;
2094         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2095            !strstr(seekAdList[i], "bullet") &&
2096            !strstr(seekAdList[i], "blitz") &&
2097            !strstr(seekAdList[i], "standard") ) color = 2;
2098         DrawSeekDot(xList[i]=x+3*color, yList[i]=h-1-y, color);
2099 }
2100
2101 void
2102 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2103 {
2104         char buf[MSG_SIZ], *ext = "";
2105         VariantClass v = StringToVariant(type);
2106         if(strstr(type, "wild")) {
2107             ext = type + 4; // append wild number
2108             if(v == VariantFischeRandom) type = "chess960"; else
2109             if(v == VariantLoadable) type = "setup"; else
2110             type = VariantName(v);
2111         }
2112         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2113         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2114             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2115             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2116             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2117             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2118             seekNrList[nrOfSeekAds] = nr;
2119             seekAdList[nrOfSeekAds++] = StrSave(buf);
2120             if(plot) PlotSeekAd(nrOfSeekAds-1);
2121         }
2122 }
2123
2124 Boolean
2125 MatchSoughtLine(char *line)
2126 {
2127     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2128     int nr, base, inc, u=0; char dummy;
2129
2130     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2131        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2132        (u=1) &&
2133        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2134         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2135         // match: compact and save the line
2136         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2137         return TRUE;
2138     }
2139     return FALSE;
2140 }
2141
2142 int
2143 DrawSeekGraph()
2144 {
2145     if(!seekGraphUp) return FALSE;
2146     int i;
2147     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2148     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2149
2150     DrawSeekBackground(0, 0, w, h);
2151     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2152     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2153     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2154         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8) + vMargin;
2155         yy = h-1-yy;
2156         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2157         if(i%500 == 0) {
2158             char buf[MSG_SIZ];
2159             sprintf(buf, "%d", i);
2160             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2161         }
2162     }
2163     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2164     for(i=1; i<100; i+=(i<10?1:5)) {
2165         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2166         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2167         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2168             char buf[MSG_SIZ];
2169             sprintf(buf, "%d", i);
2170             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2171         }
2172     }
2173     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2174     return TRUE;
2175 }
2176
2177 int SeekGraphClick(ClickType click, int x, int y, Boolean moving)
2178 {
2179     static int lastDown = 0;
2180     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2181         if(click == Release || moving) return FALSE;
2182         nrOfSeekAds = 0;
2183         soughtPending = TRUE;
2184         SendToICS(ics_prefix);
2185         SendToICS("sought\n"); // should this be "sought all"?
2186     } else { // issue challenge based on clicked ad
2187         int dist = 10000; int i, closest = 0;
2188         for(i=0; i<nrOfSeekAds; i++) {
2189             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2190             if(d < dist) { dist = d; closest = i; }
2191             if(click == Press && zList[i]>0) zList[i] *= 0.8; // age priority
2192         }
2193         if(dist < 300) {
2194             char buf[MSG_SIZ];
2195             if(lastDown != closest) DisplayMessage(seekAdList[closest], "");
2196             sprintf(buf, "play %d\n", seekNrList[closest]);
2197             if(click == Press) { lastDown = closest; return TRUE; } // on press 'hit', only show info
2198             SendToICS(ics_prefix);
2199             SendToICS(buf); // should this be "sought all"?
2200         } else if(click == Release) { // release 'miss' is ignored
2201             zList[lastDown] = 200; // make future selection of the rejected ad more difficult
2202             return TRUE;
2203         } else if(moving) { if(lastDown >= 0) DisplayMessage("", ""); lastDown = -1; return TRUE; }
2204         // press miss or release hit 'pop down' seek graph
2205         seekGraphUp = FALSE;
2206         DrawPosition(TRUE, NULL);
2207     }
2208     return TRUE;
2209 }
2210
2211 void
2212 read_from_ics(isr, closure, data, count, error)
2213      InputSourceRef isr;
2214      VOIDSTAR closure;
2215      char *data;
2216      int count;
2217      int error;
2218 {
2219 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2220 #define STARTED_NONE 0
2221 #define STARTED_MOVES 1
2222 #define STARTED_BOARD 2
2223 #define STARTED_OBSERVE 3
2224 #define STARTED_HOLDINGS 4
2225 #define STARTED_CHATTER 5
2226 #define STARTED_COMMENT 6
2227 #define STARTED_MOVES_NOHIDE 7
2228     
2229     static int started = STARTED_NONE;
2230     static char parse[20000];
2231     static int parse_pos = 0;
2232     static char buf[BUF_SIZE + 1];
2233     static int firstTime = TRUE, intfSet = FALSE;
2234     static ColorClass prevColor = ColorNormal;
2235     static int savingComment = FALSE;
2236     static int cmatch = 0; // continuation sequence match
2237     char *bp;
2238     char str[500];
2239     int i, oldi;
2240     int buf_len;
2241     int next_out;
2242     int tkind;
2243     int backup;    /* [DM] For zippy color lines */
2244     char *p;
2245     char talker[MSG_SIZ]; // [HGM] chat
2246     int channel;
2247
2248     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2249
2250     if (appData.debugMode) {
2251       if (!error) {
2252         fprintf(debugFP, "<ICS: ");
2253         show_bytes(debugFP, data, count);
2254         fprintf(debugFP, "\n");
2255       }
2256     }
2257
2258     if (appData.debugMode) { int f = forwardMostMove;
2259         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2260                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2261                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2262     }
2263     if (count > 0) {
2264         /* If last read ended with a partial line that we couldn't parse,
2265            prepend it to the new read and try again. */
2266         if (leftover_len > 0) {
2267             for (i=0; i<leftover_len; i++)
2268               buf[i] = buf[leftover_start + i];
2269         }
2270
2271     /* copy new characters into the buffer */
2272     bp = buf + leftover_len;
2273     buf_len=leftover_len;
2274     for (i=0; i<count; i++)
2275     {
2276         // ignore these
2277         if (data[i] == '\r')
2278             continue;
2279
2280         // join lines split by ICS?
2281         if (!appData.noJoin)
2282         {
2283             /*
2284                 Joining just consists of finding matches against the
2285                 continuation sequence, and discarding that sequence
2286                 if found instead of copying it.  So, until a match
2287                 fails, there's nothing to do since it might be the
2288                 complete sequence, and thus, something we don't want
2289                 copied.
2290             */
2291             if (data[i] == cont_seq[cmatch])
2292             {
2293                 cmatch++;
2294                 if (cmatch == strlen(cont_seq))
2295                 {
2296                     cmatch = 0; // complete match.  just reset the counter
2297
2298                     /*
2299                         it's possible for the ICS to not include the space
2300                         at the end of the last word, making our [correct]
2301                         join operation fuse two separate words.  the server
2302                         does this when the space occurs at the width setting.
2303                     */
2304                     if (!buf_len || buf[buf_len-1] != ' ')
2305                     {
2306                         *bp++ = ' ';
2307                         buf_len++;
2308                     }
2309                 }
2310                 continue;
2311             }
2312             else if (cmatch)
2313             {
2314                 /*
2315                     match failed, so we have to copy what matched before
2316                     falling through and copying this character.  In reality,
2317                     this will only ever be just the newline character, but
2318                     it doesn't hurt to be precise.
2319                 */
2320                 strncpy(bp, cont_seq, cmatch);
2321                 bp += cmatch;
2322                 buf_len += cmatch;
2323                 cmatch = 0;
2324             }
2325         }
2326
2327         // copy this char
2328         *bp++ = data[i];
2329         buf_len++;
2330     }
2331
2332         buf[buf_len] = NULLCHAR;
2333 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2334         next_out = 0;
2335         leftover_start = 0;
2336         
2337         i = 0;
2338         while (i < buf_len) {
2339             /* Deal with part of the TELNET option negotiation
2340                protocol.  We refuse to do anything beyond the
2341                defaults, except that we allow the WILL ECHO option,
2342                which ICS uses to turn off password echoing when we are
2343                directly connected to it.  We reject this option
2344                if localLineEditing mode is on (always on in xboard)
2345                and we are talking to port 23, which might be a real
2346                telnet server that will try to keep WILL ECHO on permanently.
2347              */
2348             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2349                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2350                 unsigned char option;
2351                 oldi = i;
2352                 switch ((unsigned char) buf[++i]) {
2353                   case TN_WILL:
2354                     if (appData.debugMode)
2355                       fprintf(debugFP, "\n<WILL ");
2356                     switch (option = (unsigned char) buf[++i]) {
2357                       case TN_ECHO:
2358                         if (appData.debugMode)
2359                           fprintf(debugFP, "ECHO ");
2360                         /* Reply only if this is a change, according
2361                            to the protocol rules. */
2362                         if (remoteEchoOption) break;
2363                         if (appData.localLineEditing &&
2364                             atoi(appData.icsPort) == TN_PORT) {
2365                             TelnetRequest(TN_DONT, TN_ECHO);
2366                         } else {
2367                             EchoOff();
2368                             TelnetRequest(TN_DO, TN_ECHO);
2369                             remoteEchoOption = TRUE;
2370                         }
2371                         break;
2372                       default:
2373                         if (appData.debugMode)
2374                           fprintf(debugFP, "%d ", option);
2375                         /* Whatever this is, we don't want it. */
2376                         TelnetRequest(TN_DONT, option);
2377                         break;
2378                     }
2379                     break;
2380                   case TN_WONT:
2381                     if (appData.debugMode)
2382                       fprintf(debugFP, "\n<WONT ");
2383                     switch (option = (unsigned char) buf[++i]) {
2384                       case TN_ECHO:
2385                         if (appData.debugMode)
2386                           fprintf(debugFP, "ECHO ");
2387                         /* Reply only if this is a change, according
2388                            to the protocol rules. */
2389                         if (!remoteEchoOption) break;
2390                         EchoOn();
2391                         TelnetRequest(TN_DONT, TN_ECHO);
2392                         remoteEchoOption = FALSE;
2393                         break;
2394                       default:
2395                         if (appData.debugMode)
2396                           fprintf(debugFP, "%d ", (unsigned char) option);
2397                         /* Whatever this is, it must already be turned
2398                            off, because we never agree to turn on
2399                            anything non-default, so according to the
2400                            protocol rules, we don't reply. */
2401                         break;
2402                     }
2403                     break;
2404                   case TN_DO:
2405                     if (appData.debugMode)
2406                       fprintf(debugFP, "\n<DO ");
2407                     switch (option = (unsigned char) buf[++i]) {
2408                       default:
2409                         /* Whatever this is, we refuse to do it. */
2410                         if (appData.debugMode)
2411                           fprintf(debugFP, "%d ", option);
2412                         TelnetRequest(TN_WONT, option);
2413                         break;
2414                     }
2415                     break;
2416                   case TN_DONT:
2417                     if (appData.debugMode)
2418                       fprintf(debugFP, "\n<DONT ");
2419                     switch (option = (unsigned char) buf[++i]) {
2420                       default:
2421                         if (appData.debugMode)
2422                           fprintf(debugFP, "%d ", option);
2423                         /* Whatever this is, we are already not doing
2424                            it, because we never agree to do anything
2425                            non-default, so according to the protocol
2426                            rules, we don't reply. */
2427                         break;
2428                     }
2429                     break;
2430                   case TN_IAC:
2431                     if (appData.debugMode)
2432                       fprintf(debugFP, "\n<IAC ");
2433                     /* Doubled IAC; pass it through */
2434                     i--;
2435                     break;
2436                   default:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2439                     /* Drop all other telnet commands on the floor */
2440                     break;
2441                 }
2442                 if (oldi > next_out)
2443                   SendToPlayer(&buf[next_out], oldi - next_out);
2444                 if (++i > next_out)
2445                   next_out = i;
2446                 continue;
2447             }
2448                 
2449             /* OK, this at least will *usually* work */
2450             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2451                 loggedOn = TRUE;
2452             }
2453             
2454             if (loggedOn && !intfSet) {
2455                 if (ics_type == ICS_ICC) {
2456                   sprintf(str,
2457                           "/set-quietly interface %s\n/set-quietly style 12\n",
2458                           programVersion);
2459                 } else if (ics_type == ICS_CHESSNET) {
2460                   sprintf(str, "/style 12\n");
2461                 } else {
2462                   strcpy(str, "alias $ @\n$set interface ");
2463                   strcat(str, programVersion);
2464                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2465 #ifdef WIN32
2466                   strcat(str, "$iset nohighlight 1\n");
2467 #endif
2468                   strcat(str, "$iset lock 1\n$style 12\n");
2469                 }
2470                 SendToICS(str);
2471                 NotifyFrontendLogin();
2472                 intfSet = TRUE;
2473             }
2474
2475             if (started == STARTED_COMMENT) {
2476                 /* Accumulate characters in comment */
2477                 parse[parse_pos++] = buf[i];
2478                 if (buf[i] == '\n') {
2479                     parse[parse_pos] = NULLCHAR;
2480                     if(chattingPartner>=0) {
2481                         char mess[MSG_SIZ];
2482                         sprintf(mess, "%s%s", talker, parse);
2483                         OutputChatMessage(chattingPartner, mess);
2484                         chattingPartner = -1;
2485                     } else
2486                     if(!suppressKibitz) // [HGM] kibitz
2487                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2488                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2489                         int nrDigit = 0, nrAlph = 0, j;
2490                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2491                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2492                         parse[parse_pos] = NULLCHAR;
2493                         // try to be smart: if it does not look like search info, it should go to
2494                         // ICS interaction window after all, not to engine-output window.
2495                         for(j=0; j<parse_pos; j++) { // count letters and digits
2496                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2497                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2498                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2499                         }
2500                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2501                             int depth=0; float score;
2502                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2503                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2504                                 pvInfoList[forwardMostMove-1].depth = depth;
2505                                 pvInfoList[forwardMostMove-1].score = 100*score;
2506                             }
2507                             OutputKibitz(suppressKibitz, parse);
2508                             next_out = i+1; // [HGM] suppress printing in ICS window
2509                         } else {
2510                             char tmp[MSG_SIZ];
2511                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2512                             SendToPlayer(tmp, strlen(tmp));
2513                         }
2514                     }
2515                     started = STARTED_NONE;
2516                 } else {
2517                     /* Don't match patterns against characters in comment */
2518                     i++;
2519                     continue;
2520                 }
2521             }
2522             if (started == STARTED_CHATTER) {
2523                 if (buf[i] != '\n') {
2524                     /* Don't match patterns against characters in chatter */
2525                     i++;
2526                     continue;
2527                 }
2528                 started = STARTED_NONE;
2529             }
2530
2531             /* Kludge to deal with rcmd protocol */
2532             if (firstTime && looking_at(buf, &i, "\001*")) {
2533                 DisplayFatalError(&buf[1], 0, 1);
2534                 continue;
2535             } else {
2536                 firstTime = FALSE;
2537             }
2538
2539             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2540                 ics_type = ICS_ICC;
2541                 ics_prefix = "/";
2542                 if (appData.debugMode)
2543                   fprintf(debugFP, "ics_type %d\n", ics_type);
2544                 continue;
2545             }
2546             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2547                 ics_type = ICS_FICS;
2548                 ics_prefix = "$";
2549                 if (appData.debugMode)
2550                   fprintf(debugFP, "ics_type %d\n", ics_type);
2551                 continue;
2552             }
2553             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2554                 ics_type = ICS_CHESSNET;
2555                 ics_prefix = "/";
2556                 if (appData.debugMode)
2557                   fprintf(debugFP, "ics_type %d\n", ics_type);
2558                 continue;
2559             }
2560
2561             if (!loggedOn &&
2562                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2563                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2564                  looking_at(buf, &i, "will be \"*\""))) {
2565               strcpy(ics_handle, star_match[0]);
2566               continue;
2567             }
2568
2569             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2570               char buf[MSG_SIZ];
2571               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2572               DisplayIcsInteractionTitle(buf);
2573               have_set_title = TRUE;
2574             }
2575
2576             /* skip finger notes */
2577             if (started == STARTED_NONE &&
2578                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2579                  (buf[i] == '1' && buf[i+1] == '0')) &&
2580                 buf[i+2] == ':' && buf[i+3] == ' ') {
2581               started = STARTED_CHATTER;
2582               i += 3;
2583               continue;
2584             }
2585
2586             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2587             if(appData.seekGraph) {
2588                 if(soughtPending && MatchSoughtLine(buf+i)) {
2589                     i = strstr(buf+i, "rated") - buf;
2590                     next_out = leftover_start = i;
2591                     started = STARTED_CHATTER;
2592                     suppressKibitz = TRUE;
2593                 }
2594                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2595                         && looking_at(buf, &i, "* ads displayed")) {
2596                     soughtPending = FALSE;
2597                     seekGraphUp = TRUE;
2598                     DrawSeekGraph();
2599                     continue;
2600                 }
2601             }
2602
2603             /* skip formula vars */
2604             if (started == STARTED_NONE &&
2605                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2606               started = STARTED_CHATTER;
2607               i += 3;
2608               continue;
2609             }
2610
2611             oldi = i;
2612             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2613             if (appData.autoKibitz && started == STARTED_NONE && 
2614                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2615                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2616                 if(looking_at(buf, &i, "* kibitzes: ") &&
2617                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2618                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2619                         suppressKibitz = TRUE;
2620                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2621                                 && (gameMode == IcsPlayingWhite)) ||
2622                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2623                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2624                             started = STARTED_CHATTER; // own kibitz we simply discard
2625                         else {
2626                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2627                             parse_pos = 0; parse[0] = NULLCHAR;
2628                             savingComment = TRUE;
2629                             suppressKibitz = gameMode != IcsObserving ? 2 :
2630                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2631                         } 
2632                         continue;
2633                 } else
2634                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2635                     // suppress the acknowledgements of our own autoKibitz
2636                     char *p;
2637                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2638                     SendToPlayer(star_match[0], strlen(star_match[0]));
2639                     looking_at(buf, &i, "*% "); // eat prompt
2640                     next_out = i;
2641                 }
2642             } // [HGM] kibitz: end of patch
2643
2644 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2645
2646             // [HGM] chat: intercept tells by users for which we have an open chat window
2647             channel = -1;
2648             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2649                                            looking_at(buf, &i, "* whispers:") ||
2650                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2651                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2652                 int p;
2653                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2654                 chattingPartner = -1;
2655
2656                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2657                 for(p=0; p<MAX_CHAT; p++) {
2658                     if(channel == atoi(chatPartner[p])) {
2659                     talker[0] = '['; strcat(talker, "] ");
2660                     chattingPartner = p; break;
2661                     }
2662                 } else
2663                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2664                 for(p=0; p<MAX_CHAT; p++) {
2665                     if(!strcmp("WHISPER", chatPartner[p])) {
2666                         talker[0] = '['; strcat(talker, "] ");
2667                         chattingPartner = p; break;
2668                     }
2669                 }
2670                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2671                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2672                     talker[0] = 0;
2673                     chattingPartner = p; break;
2674                 }
2675                 if(chattingPartner<0) i = oldi; else {
2676                     started = STARTED_COMMENT;
2677                     parse_pos = 0; parse[0] = NULLCHAR;
2678                     savingComment = 3 + chattingPartner; // counts as TRUE
2679                     suppressKibitz = TRUE;
2680                 }
2681             } // [HGM] chat: end of patch
2682
2683             if (appData.zippyTalk || appData.zippyPlay) {
2684                 /* [DM] Backup address for color zippy lines */
2685                 backup = i;
2686 #if ZIPPY
2687        #ifdef WIN32
2688                if (loggedOn == TRUE)
2689                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2690                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2691        #else
2692                 if (ZippyControl(buf, &i) ||
2693                     ZippyConverse(buf, &i) ||
2694                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2695                       loggedOn = TRUE;
2696                       if (!appData.colorize) continue;
2697                 }
2698        #endif
2699 #endif
2700             } // [DM] 'else { ' deleted
2701                 if (
2702                     /* Regular tells and says */
2703                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2704                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2705                     looking_at(buf, &i, "* says: ") ||
2706                     /* Don't color "message" or "messages" output */
2707                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2708                     looking_at(buf, &i, "*. * at *:*: ") ||
2709                     looking_at(buf, &i, "--* (*:*): ") ||
2710                     /* Message notifications (same color as tells) */
2711                     looking_at(buf, &i, "* has left a message ") ||
2712                     looking_at(buf, &i, "* just sent you a message:\n") ||
2713                     /* Whispers and kibitzes */
2714                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2715                     looking_at(buf, &i, "* kibitzes: ") ||
2716                     /* Channel tells */
2717                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2718
2719                   if (tkind == 1 && strchr(star_match[0], ':')) {
2720                       /* Avoid "tells you:" spoofs in channels */
2721                      tkind = 3;
2722                   }
2723                   if (star_match[0][0] == NULLCHAR ||
2724                       strchr(star_match[0], ' ') ||
2725                       (tkind == 3 && strchr(star_match[1], ' '))) {
2726                     /* Reject bogus matches */
2727                     i = oldi;
2728                   } else {
2729                     if (appData.colorize) {
2730                       if (oldi > next_out) {
2731                         SendToPlayer(&buf[next_out], oldi - next_out);
2732                         next_out = oldi;
2733                       }
2734                       switch (tkind) {
2735                       case 1:
2736                         Colorize(ColorTell, FALSE);
2737                         curColor = ColorTell;
2738                         break;
2739                       case 2:
2740                         Colorize(ColorKibitz, FALSE);
2741                         curColor = ColorKibitz;
2742                         break;
2743                       case 3:
2744                         p = strrchr(star_match[1], '(');
2745                         if (p == NULL) {
2746                           p = star_match[1];
2747                         } else {
2748                           p++;
2749                         }
2750                         if (atoi(p) == 1) {
2751                           Colorize(ColorChannel1, FALSE);
2752                           curColor = ColorChannel1;
2753                         } else {
2754                           Colorize(ColorChannel, FALSE);
2755                           curColor = ColorChannel;
2756                         }
2757                         break;
2758                       case 5:
2759                         curColor = ColorNormal;
2760                         break;
2761                       }
2762                     }
2763                     if (started == STARTED_NONE && appData.autoComment &&
2764                         (gameMode == IcsObserving ||
2765                          gameMode == IcsPlayingWhite ||
2766                          gameMode == IcsPlayingBlack)) {
2767                       parse_pos = i - oldi;
2768                       memcpy(parse, &buf[oldi], parse_pos);
2769                       parse[parse_pos] = NULLCHAR;
2770                       started = STARTED_COMMENT;
2771                       savingComment = TRUE;
2772                     } else {
2773                       started = STARTED_CHATTER;
2774                       savingComment = FALSE;
2775                     }
2776                     loggedOn = TRUE;
2777                     continue;
2778                   }
2779                 }
2780
2781                 if (looking_at(buf, &i, "* s-shouts: ") ||
2782                     looking_at(buf, &i, "* c-shouts: ")) {
2783                     if (appData.colorize) {
2784                         if (oldi > next_out) {
2785                             SendToPlayer(&buf[next_out], oldi - next_out);
2786                             next_out = oldi;
2787                         }
2788                         Colorize(ColorSShout, FALSE);
2789                         curColor = ColorSShout;
2790                     }
2791                     loggedOn = TRUE;
2792                     started = STARTED_CHATTER;
2793                     continue;
2794                 }
2795
2796                 if (looking_at(buf, &i, "--->")) {
2797                     loggedOn = TRUE;
2798                     continue;
2799                 }
2800
2801                 if (looking_at(buf, &i, "* shouts: ") ||
2802                     looking_at(buf, &i, "--> ")) {
2803                     if (appData.colorize) {
2804                         if (oldi > next_out) {
2805                             SendToPlayer(&buf[next_out], oldi - next_out);
2806                             next_out = oldi;
2807                         }
2808                         Colorize(ColorShout, FALSE);
2809                         curColor = ColorShout;
2810                     }
2811                     loggedOn = TRUE;
2812                     started = STARTED_CHATTER;
2813                     continue;
2814                 }
2815
2816                 if (looking_at( buf, &i, "Challenge:")) {
2817                     if (appData.colorize) {
2818                         if (oldi > next_out) {
2819                             SendToPlayer(&buf[next_out], oldi - next_out);
2820                             next_out = oldi;
2821                         }
2822                         Colorize(ColorChallenge, FALSE);
2823                         curColor = ColorChallenge;
2824                     }
2825                     loggedOn = TRUE;
2826                     continue;
2827                 }
2828
2829                 if (looking_at(buf, &i, "* offers you") ||
2830                     looking_at(buf, &i, "* offers to be") ||
2831                     looking_at(buf, &i, "* would like to") ||
2832                     looking_at(buf, &i, "* requests to") ||
2833                     looking_at(buf, &i, "Your opponent offers") ||
2834                     looking_at(buf, &i, "Your opponent requests")) {
2835
2836                     if (appData.colorize) {
2837                         if (oldi > next_out) {
2838                             SendToPlayer(&buf[next_out], oldi - next_out);
2839                             next_out = oldi;
2840                         }
2841                         Colorize(ColorRequest, FALSE);
2842                         curColor = ColorRequest;
2843                     }
2844                     continue;
2845                 }
2846
2847                 if (looking_at(buf, &i, "* (*) seeking")) {
2848                     if (appData.colorize) {
2849                         if (oldi > next_out) {
2850                             SendToPlayer(&buf[next_out], oldi - next_out);
2851                             next_out = oldi;
2852                         }
2853                         Colorize(ColorSeek, FALSE);
2854                         curColor = ColorSeek;
2855                     }
2856                     continue;
2857             }
2858
2859             if (looking_at(buf, &i, "\\   ")) {
2860                 if (prevColor != ColorNormal) {
2861                     if (oldi > next_out) {
2862                         SendToPlayer(&buf[next_out], oldi - next_out);
2863                         next_out = oldi;
2864                     }
2865                     Colorize(prevColor, TRUE);
2866                     curColor = prevColor;
2867                 }
2868                 if (savingComment) {
2869                     parse_pos = i - oldi;
2870                     memcpy(parse, &buf[oldi], parse_pos);
2871                     parse[parse_pos] = NULLCHAR;
2872                     started = STARTED_COMMENT;
2873                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2874                         chattingPartner = savingComment - 3; // kludge to remember the box
2875                 } else {
2876                     started = STARTED_CHATTER;
2877                 }
2878                 continue;
2879             }
2880
2881             if (looking_at(buf, &i, "Black Strength :") ||
2882                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2883                 looking_at(buf, &i, "<10>") ||
2884                 looking_at(buf, &i, "#@#")) {
2885                 /* Wrong board style */
2886                 loggedOn = TRUE;
2887                 SendToICS(ics_prefix);
2888                 SendToICS("set style 12\n");
2889                 SendToICS(ics_prefix);
2890                 SendToICS("refresh\n");
2891                 continue;
2892             }
2893             
2894             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2895                 ICSInitScript();
2896                 have_sent_ICS_logon = 1;
2897                 continue;
2898             }
2899               
2900             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2901                 (looking_at(buf, &i, "\n<12> ") ||
2902                  looking_at(buf, &i, "<12> "))) {
2903                 loggedOn = TRUE;
2904                 if (oldi > next_out) {
2905                     SendToPlayer(&buf[next_out], oldi - next_out);
2906                 }
2907                 next_out = i;
2908                 started = STARTED_BOARD;
2909                 parse_pos = 0;
2910                 continue;
2911             }
2912
2913             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2914                 looking_at(buf, &i, "<b1> ")) {
2915                 if (oldi > next_out) {
2916                     SendToPlayer(&buf[next_out], oldi - next_out);
2917                 }
2918                 next_out = i;
2919                 started = STARTED_HOLDINGS;
2920                 parse_pos = 0;
2921                 continue;
2922             }
2923
2924             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2925                 loggedOn = TRUE;
2926                 /* Header for a move list -- first line */
2927
2928                 switch (ics_getting_history) {
2929                   case H_FALSE:
2930                     switch (gameMode) {
2931                       case IcsIdle:
2932                       case BeginningOfGame:
2933                         /* User typed "moves" or "oldmoves" while we
2934                            were idle.  Pretend we asked for these
2935                            moves and soak them up so user can step
2936                            through them and/or save them.
2937                            */
2938                         Reset(FALSE, TRUE);
2939                         gameMode = IcsObserving;
2940                         ModeHighlight();
2941                         ics_gamenum = -1;
2942                         ics_getting_history = H_GOT_UNREQ_HEADER;
2943                         break;
2944                       case EditGame: /*?*/
2945                       case EditPosition: /*?*/
2946                         /* Should above feature work in these modes too? */
2947                         /* For now it doesn't */
2948                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2949                         break;
2950                       default:
2951                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2952                         break;
2953                     }
2954                     break;
2955                   case H_REQUESTED:
2956                     /* Is this the right one? */
2957                     if (gameInfo.white && gameInfo.black &&
2958                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2959                         strcmp(gameInfo.black, star_match[2]) == 0) {
2960                         /* All is well */
2961                         ics_getting_history = H_GOT_REQ_HEADER;
2962                     }
2963                     break;
2964                   case H_GOT_REQ_HEADER:
2965                   case H_GOT_UNREQ_HEADER:
2966                   case H_GOT_UNWANTED_HEADER:
2967                   case H_GETTING_MOVES:
2968                     /* Should not happen */
2969                     DisplayError(_("Error gathering move list: two headers"), 0);
2970                     ics_getting_history = H_FALSE;
2971                     break;
2972                 }
2973
2974                 /* Save player ratings into gameInfo if needed */
2975                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2976                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2977                     (gameInfo.whiteRating == -1 ||
2978                      gameInfo.blackRating == -1)) {
2979
2980                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2981                     gameInfo.blackRating = string_to_rating(star_match[3]);
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2984                               gameInfo.whiteRating, gameInfo.blackRating);
2985                 }
2986                 continue;
2987             }
2988
2989             if (looking_at(buf, &i,
2990               "* * match, initial time: * minute*, increment: * second")) {
2991                 /* Header for a move list -- second line */
2992                 /* Initial board will follow if this is a wild game */
2993                 if (gameInfo.event != NULL) free(gameInfo.event);
2994                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2995                 gameInfo.event = StrSave(str);
2996                 /* [HGM] we switched variant. Translate boards if needed. */
2997                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2998                 continue;
2999             }
3000
3001             if (looking_at(buf, &i, "Move  ")) {
3002                 /* Beginning of a move list */
3003                 switch (ics_getting_history) {
3004                   case H_FALSE:
3005                     /* Normally should not happen */
3006                     /* Maybe user hit reset while we were parsing */
3007                     break;
3008                   case H_REQUESTED:
3009                     /* Happens if we are ignoring a move list that is not
3010                      * the one we just requested.  Common if the user
3011                      * tries to observe two games without turning off
3012                      * getMoveList */
3013                     break;
3014                   case H_GETTING_MOVES:
3015                     /* Should not happen */
3016                     DisplayError(_("Error gathering move list: nested"), 0);
3017                     ics_getting_history = H_FALSE;
3018                     break;
3019                   case H_GOT_REQ_HEADER:
3020                     ics_getting_history = H_GETTING_MOVES;
3021                     started = STARTED_MOVES;
3022                     parse_pos = 0;
3023                     if (oldi > next_out) {
3024                         SendToPlayer(&buf[next_out], oldi - next_out);
3025                     }
3026                     break;
3027                   case H_GOT_UNREQ_HEADER:
3028                     ics_getting_history = H_GETTING_MOVES;
3029                     started = STARTED_MOVES_NOHIDE;
3030                     parse_pos = 0;
3031                     break;
3032                   case H_GOT_UNWANTED_HEADER:
3033                     ics_getting_history = H_FALSE;
3034                     break;
3035                 }
3036                 continue;
3037             }                           
3038             
3039             if (looking_at(buf, &i, "% ") ||
3040                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3041                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3042                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3043                     soughtPending = FALSE;
3044                     seekGraphUp = TRUE;
3045                     DrawSeekGraph();
3046                 }
3047                 if(suppressKibitz) next_out = i;
3048                 savingComment = FALSE;
3049                 suppressKibitz = 0;
3050                 switch (started) {
3051                   case STARTED_MOVES:
3052                   case STARTED_MOVES_NOHIDE:
3053                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3054                     parse[parse_pos + i - oldi] = NULLCHAR;
3055                     ParseGameHistory(parse);
3056 #if ZIPPY
3057                     if (appData.zippyPlay && first.initDone) {
3058                         FeedMovesToProgram(&first, forwardMostMove);
3059                         if (gameMode == IcsPlayingWhite) {
3060                             if (WhiteOnMove(forwardMostMove)) {
3061                                 if (first.sendTime) {
3062                                   if (first.useColors) {
3063                                     SendToProgram("black\n", &first); 
3064                                   }
3065                                   SendTimeRemaining(&first, TRUE);
3066                                 }
3067                                 if (first.useColors) {
3068                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3069                                 }
3070                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3071                                 first.maybeThinking = TRUE;
3072                             } else {
3073                                 if (first.usePlayother) {
3074                                   if (first.sendTime) {
3075                                     SendTimeRemaining(&first, TRUE);
3076                                   }
3077                                   SendToProgram("playother\n", &first);
3078                                   firstMove = FALSE;
3079                                 } else {
3080                                   firstMove = TRUE;
3081                                 }
3082                             }
3083                         } else if (gameMode == IcsPlayingBlack) {
3084                             if (!WhiteOnMove(forwardMostMove)) {
3085                                 if (first.sendTime) {
3086                                   if (first.useColors) {
3087                                     SendToProgram("white\n", &first);
3088                                   }
3089                                   SendTimeRemaining(&first, FALSE);
3090                                 }
3091                                 if (first.useColors) {
3092                                   SendToProgram("black\n", &first);
3093                                 }
3094                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3095                                 first.maybeThinking = TRUE;
3096                             } else {
3097                                 if (first.usePlayother) {
3098                                   if (first.sendTime) {
3099                                     SendTimeRemaining(&first, FALSE);
3100                                   }
3101                                   SendToProgram("playother\n", &first);
3102                                   firstMove = FALSE;
3103                                 } else {
3104                                   firstMove = TRUE;
3105                                 }
3106                             }
3107                         }                       
3108                     }
3109 #endif
3110                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3111                         /* Moves came from oldmoves or moves command
3112                            while we weren't doing anything else.
3113                            */
3114                         currentMove = forwardMostMove;
3115                         ClearHighlights();/*!!could figure this out*/
3116                         flipView = appData.flipView;
3117                         DrawPosition(TRUE, boards[currentMove]);
3118                         DisplayBothClocks();
3119                         sprintf(str, "%s vs. %s",
3120                                 gameInfo.white, gameInfo.black);
3121                         DisplayTitle(str);
3122                         gameMode = IcsIdle;
3123                     } else {
3124                         /* Moves were history of an active game */
3125                         if (gameInfo.resultDetails != NULL) {
3126                             free(gameInfo.resultDetails);
3127                             gameInfo.resultDetails = NULL;
3128                         }
3129                     }
3130                     HistorySet(parseList, backwardMostMove,
3131                                forwardMostMove, currentMove-1);
3132                     DisplayMove(currentMove - 1);
3133                     if (started == STARTED_MOVES) next_out = i;
3134                     started = STARTED_NONE;
3135                     ics_getting_history = H_FALSE;
3136                     break;
3137
3138                   case STARTED_OBSERVE:
3139                     started = STARTED_NONE;
3140                     SendToICS(ics_prefix);
3141                     SendToICS("refresh\n");
3142                     break;
3143
3144                   default:
3145                     break;
3146                 }
3147                 if(bookHit) { // [HGM] book: simulate book reply
3148                     static char bookMove[MSG_SIZ]; // a bit generous?
3149
3150                     programStats.nodes = programStats.depth = programStats.time = 
3151                     programStats.score = programStats.got_only_move = 0;
3152                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3153
3154                     strcpy(bookMove, "move ");
3155                     strcat(bookMove, bookHit);
3156                     HandleMachineMove(bookMove, &first);
3157                 }
3158                 continue;
3159             }
3160             
3161             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3162                  started == STARTED_HOLDINGS ||
3163                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3164                 /* Accumulate characters in move list or board */
3165                 parse[parse_pos++] = buf[i];
3166             }
3167             
3168             /* Start of game messages.  Mostly we detect start of game
3169                when the first board image arrives.  On some versions
3170                of the ICS, though, we need to do a "refresh" after starting
3171                to observe in order to get the current board right away. */
3172             if (looking_at(buf, &i, "Adding game * to observation list")) {
3173                 started = STARTED_OBSERVE;
3174                 continue;
3175             }
3176
3177             /* Handle auto-observe */
3178             if (appData.autoObserve &&
3179                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3180                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3181                 char *player;
3182                 /* Choose the player that was highlighted, if any. */
3183                 if (star_match[0][0] == '\033' ||
3184                     star_match[1][0] != '\033') {
3185                     player = star_match[0];
3186                 } else {
3187                     player = star_match[2];
3188                 }
3189                 sprintf(str, "%sobserve %s\n",
3190                         ics_prefix, StripHighlightAndTitle(player));
3191                 SendToICS(str);
3192
3193                 /* Save ratings from notify string */
3194                 strcpy(player1Name, star_match[0]);
3195                 player1Rating = string_to_rating(star_match[1]);
3196                 strcpy(player2Name, star_match[2]);
3197                 player2Rating = string_to_rating(star_match[3]);
3198
3199                 if (appData.debugMode)
3200                   fprintf(debugFP, 
3201                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3202                           player1Name, player1Rating,
3203                           player2Name, player2Rating);
3204
3205                 continue;
3206             }
3207
3208             /* Deal with automatic examine mode after a game,
3209                and with IcsObserving -> IcsExamining transition */
3210             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3211                 looking_at(buf, &i, "has made you an examiner of game *")) {
3212
3213                 int gamenum = atoi(star_match[0]);
3214                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3215                     gamenum == ics_gamenum) {
3216                     /* We were already playing or observing this game;
3217                        no need to refetch history */
3218                     gameMode = IcsExamining;
3219                     if (pausing) {
3220                         pauseExamForwardMostMove = forwardMostMove;
3221                     } else if (currentMove < forwardMostMove) {
3222                         ForwardInner(forwardMostMove);
3223                     }
3224                 } else {
3225                     /* I don't think this case really can happen */
3226                     SendToICS(ics_prefix);
3227                     SendToICS("refresh\n");
3228                 }
3229                 continue;
3230             }    
3231             
3232             /* Error messages */
3233 //          if (ics_user_moved) {
3234             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3235                 if (looking_at(buf, &i, "Illegal move") ||
3236                     looking_at(buf, &i, "Not a legal move") ||
3237                     looking_at(buf, &i, "Your king is in check") ||
3238                     looking_at(buf, &i, "It isn't your turn") ||
3239                     looking_at(buf, &i, "It is not your move")) {
3240                     /* Illegal move */
3241                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3242                         currentMove = --forwardMostMove;
3243                         DisplayMove(currentMove - 1); /* before DMError */
3244                         DrawPosition(FALSE, boards[currentMove]);
3245                         SwitchClocks();
3246                         DisplayBothClocks();
3247                     }
3248                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3249                     ics_user_moved = 0;
3250                     continue;
3251                 }
3252             }
3253
3254             if (looking_at(buf, &i, "still have time") ||
3255                 looking_at(buf, &i, "not out of time") ||
3256                 looking_at(buf, &i, "either player is out of time") ||
3257                 looking_at(buf, &i, "has timeseal; checking")) {
3258                 /* We must have called his flag a little too soon */
3259                 whiteFlag = blackFlag = FALSE;
3260                 continue;
3261             }
3262
3263             if (looking_at(buf, &i, "added * seconds to") ||
3264                 looking_at(buf, &i, "seconds were added to")) {
3265                 /* Update the clocks */
3266                 SendToICS(ics_prefix);
3267                 SendToICS("refresh\n");
3268                 continue;
3269             }
3270
3271             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3272                 ics_clock_paused = TRUE;
3273                 StopClocks();
3274                 continue;
3275             }
3276
3277             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3278                 ics_clock_paused = FALSE;
3279                 StartClocks();
3280                 continue;
3281             }
3282
3283             /* Grab player ratings from the Creating: message.
3284                Note we have to check for the special case when
3285                the ICS inserts things like [white] or [black]. */
3286             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3287                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3288                 /* star_matches:
3289                    0    player 1 name (not necessarily white)
3290                    1    player 1 rating
3291                    2    empty, white, or black (IGNORED)
3292                    3    player 2 name (not necessarily black)
3293                    4    player 2 rating
3294                    
3295                    The names/ratings are sorted out when the game
3296                    actually starts (below).
3297                 */
3298                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3299                 player1Rating = string_to_rating(star_match[1]);
3300                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3301                 player2Rating = string_to_rating(star_match[4]);
3302
3303                 if (appData.debugMode)
3304                   fprintf(debugFP, 
3305                           "Ratings from 'Creating:' %s %d, %s %d\n",
3306                           player1Name, player1Rating,
3307                           player2Name, player2Rating);
3308
3309                 continue;
3310             }
3311             
3312             /* Improved generic start/end-of-game messages */
3313             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3314                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3315                 /* If tkind == 0: */
3316                 /* star_match[0] is the game number */
3317                 /*           [1] is the white player's name */
3318                 /*           [2] is the black player's name */
3319                 /* For end-of-game: */
3320                 /*           [3] is the reason for the game end */
3321                 /*           [4] is a PGN end game-token, preceded by " " */
3322                 /* For start-of-game: */
3323                 /*           [3] begins with "Creating" or "Continuing" */
3324                 /*           [4] is " *" or empty (don't care). */
3325                 int gamenum = atoi(star_match[0]);
3326                 char *whitename, *blackname, *why, *endtoken;
3327                 ChessMove endtype = (ChessMove) 0;
3328
3329                 if (tkind == 0) {
3330                   whitename = star_match[1];
3331                   blackname = star_match[2];
3332                   why = star_match[3];
3333                   endtoken = star_match[4];
3334                 } else {
3335                   whitename = star_match[1];
3336                   blackname = star_match[3];
3337                   why = star_match[5];
3338                   endtoken = star_match[6];
3339                 }
3340
3341                 /* Game start messages */
3342                 if (strncmp(why, "Creating ", 9) == 0 ||
3343                     strncmp(why, "Continuing ", 11) == 0) {
3344                     gs_gamenum = gamenum;
3345                     strcpy(gs_kind, strchr(why, ' ') + 1);
3346                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3347 #if ZIPPY
3348                     if (appData.zippyPlay) {
3349                         ZippyGameStart(whitename, blackname);
3350                     }
3351 #endif /*ZIPPY*/
3352                     continue;
3353                 }
3354
3355                 /* Game end messages */
3356                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3357                     ics_gamenum != gamenum) {
3358                     continue;
3359                 }
3360                 while (endtoken[0] == ' ') endtoken++;
3361                 switch (endtoken[0]) {
3362                   case '*':
3363                   default:
3364                     endtype = GameUnfinished;
3365                     break;
3366                   case '0':
3367                     endtype = BlackWins;
3368                     break;
3369                   case '1':
3370                     if (endtoken[1] == '/')
3371                       endtype = GameIsDrawn;
3372                     else
3373                       endtype = WhiteWins;
3374                     break;
3375                 }
3376                 GameEnds(endtype, why, GE_ICS);
3377 #if ZIPPY
3378                 if (appData.zippyPlay && first.initDone) {
3379                     ZippyGameEnd(endtype, why);
3380                     if (first.pr == NULL) {
3381                       /* Start the next process early so that we'll
3382                          be ready for the next challenge */
3383                       StartChessProgram(&first);
3384                     }
3385                     /* Send "new" early, in case this command takes
3386                        a long time to finish, so that we'll be ready
3387                        for the next challenge. */
3388                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3389                     Reset(TRUE, TRUE);
3390                 }
3391 #endif /*ZIPPY*/
3392                 continue;
3393             }
3394
3395             if (looking_at(buf, &i, "Removing game * from observation") ||
3396                 looking_at(buf, &i, "no longer observing game *") ||
3397                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3398                 if (gameMode == IcsObserving &&
3399                     atoi(star_match[0]) == ics_gamenum)
3400                   {
3401                       /* icsEngineAnalyze */
3402                       if (appData.icsEngineAnalyze) {
3403                             ExitAnalyzeMode();
3404                             ModeHighlight();
3405                       }
3406                       StopClocks();
3407                       gameMode = IcsIdle;
3408                       ics_gamenum = -1;
3409                       ics_user_moved = FALSE;
3410                   }
3411                 continue;
3412             }
3413
3414             if (looking_at(buf, &i, "no longer examining game *")) {
3415                 if (gameMode == IcsExamining &&
3416                     atoi(star_match[0]) == ics_gamenum)
3417                   {
3418                       gameMode = IcsIdle;
3419                       ics_gamenum = -1;
3420                       ics_user_moved = FALSE;
3421                   }
3422                 continue;
3423             }
3424
3425             /* Advance leftover_start past any newlines we find,
3426                so only partial lines can get reparsed */
3427             if (looking_at(buf, &i, "\n")) {
3428                 prevColor = curColor;
3429                 if (curColor != ColorNormal) {
3430                     if (oldi > next_out) {
3431                         SendToPlayer(&buf[next_out], oldi - next_out);
3432                         next_out = oldi;
3433                     }
3434                     Colorize(ColorNormal, FALSE);
3435                     curColor = ColorNormal;
3436                 }
3437                 if (started == STARTED_BOARD) {
3438                     started = STARTED_NONE;
3439                     parse[parse_pos] = NULLCHAR;
3440                     ParseBoard12(parse);
3441                     ics_user_moved = 0;
3442
3443                     /* Send premove here */
3444                     if (appData.premove) {
3445                       char str[MSG_SIZ];
3446                       if (currentMove == 0 &&
3447                           gameMode == IcsPlayingWhite &&
3448                           appData.premoveWhite) {
3449                         sprintf(str, "%s\n", appData.premoveWhiteText);
3450                         if (appData.debugMode)
3451                           fprintf(debugFP, "Sending premove:\n");
3452                         SendToICS(str);
3453                       } else if (currentMove == 1 &&
3454                                  gameMode == IcsPlayingBlack &&
3455                                  appData.premoveBlack) {
3456                         sprintf(str, "%s\n", appData.premoveBlackText);
3457                         if (appData.debugMode)
3458                           fprintf(debugFP, "Sending premove:\n");
3459                         SendToICS(str);
3460                       } else if (gotPremove) {
3461                         gotPremove = 0;
3462                         ClearPremoveHighlights();
3463                         if (appData.debugMode)
3464                           fprintf(debugFP, "Sending premove:\n");
3465                           UserMoveEvent(premoveFromX, premoveFromY, 
3466                                         premoveToX, premoveToY, 
3467                                         premovePromoChar);
3468                       }
3469                     }
3470
3471                     /* Usually suppress following prompt */
3472                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3473                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3474                         if (looking_at(buf, &i, "*% ")) {
3475                             savingComment = FALSE;
3476                             suppressKibitz = 0;
3477                         }
3478                     }
3479                     next_out = i;
3480                 } else if (started == STARTED_HOLDINGS) {
3481                     int gamenum;
3482                     char new_piece[MSG_SIZ];
3483                     started = STARTED_NONE;
3484                     parse[parse_pos] = NULLCHAR;
3485                     if (appData.debugMode)
3486                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3487                                                         parse, currentMove);
3488                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3489                         gamenum == ics_gamenum) {
3490                         if (gameInfo.variant == VariantNormal) {
3491                           /* [HGM] We seem to switch variant during a game!
3492                            * Presumably no holdings were displayed, so we have
3493                            * to move the position two files to the right to
3494                            * create room for them!
3495                            */
3496                           VariantClass newVariant;
3497                           switch(gameInfo.boardWidth) { // base guess on board width
3498                                 case 9:  newVariant = VariantShogi; break;
3499                                 case 10: newVariant = VariantGreat; break;
3500                                 default: newVariant = VariantCrazyhouse; break;
3501                           }
3502                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3503                           /* Get a move list just to see the header, which
3504                              will tell us whether this is really bug or zh */
3505                           if (ics_getting_history == H_FALSE) {
3506                             ics_getting_history = H_REQUESTED;
3507                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3508                             SendToICS(str);
3509                           }
3510                         }
3511                         new_piece[0] = NULLCHAR;
3512                         sscanf(parse, "game %d white [%s black [%s <- %s",
3513                                &gamenum, white_holding, black_holding,
3514                                new_piece);
3515                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3516                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3517                         /* [HGM] copy holdings to board holdings area */
3518                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3519                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3520                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3521 #if ZIPPY
3522                         if (appData.zippyPlay && first.initDone) {
3523                             ZippyHoldings(white_holding, black_holding,
3524                                           new_piece);
3525                         }
3526 #endif /*ZIPPY*/
3527                         if (tinyLayout || smallLayout) {
3528                             char wh[16], bh[16];
3529                             PackHolding(wh, white_holding);
3530                             PackHolding(bh, black_holding);
3531                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3532                                     gameInfo.white, gameInfo.black);
3533                         } else {
3534                             sprintf(str, "%s [%s] vs. %s [%s]",
3535                                     gameInfo.white, white_holding,
3536                                     gameInfo.black, black_holding);
3537                         }
3538
3539                         DrawPosition(FALSE, boards[currentMove]);
3540                         DisplayTitle(str);
3541                     }
3542                     /* Suppress following prompt */
3543                     if (looking_at(buf, &i, "*% ")) {
3544                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3545                         savingComment = FALSE;
3546                         suppressKibitz = 0;
3547                     }
3548                     next_out = i;
3549                 }
3550                 continue;
3551             }
3552
3553             i++;                /* skip unparsed character and loop back */
3554         }
3555         
3556         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3557 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3558 //          SendToPlayer(&buf[next_out], i - next_out);
3559             started != STARTED_HOLDINGS && leftover_start > next_out) {
3560             SendToPlayer(&buf[next_out], leftover_start - next_out);
3561             next_out = i;
3562         }
3563         
3564         leftover_len = buf_len - leftover_start;
3565         /* if buffer ends with something we couldn't parse,
3566            reparse it after appending the next read */
3567         
3568     } else if (count == 0) {
3569         RemoveInputSource(isr);
3570         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3571     } else {
3572         DisplayFatalError(_("Error reading from ICS"), error, 1);
3573     }
3574 }
3575
3576
3577 /* Board style 12 looks like this:
3578    
3579    <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
3580    
3581  * The "<12> " is stripped before it gets to this routine.  The two
3582  * trailing 0's (flip state and clock ticking) are later addition, and
3583  * some chess servers may not have them, or may have only the first.
3584  * Additional trailing fields may be added in the future.  
3585  */
3586
3587 #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"
3588
3589 #define RELATION_OBSERVING_PLAYED    0
3590 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3591 #define RELATION_PLAYING_MYMOVE      1
3592 #define RELATION_PLAYING_NOTMYMOVE  -1
3593 #define RELATION_EXAMINING           2
3594 #define RELATION_ISOLATED_BOARD     -3
3595 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3596
3597 void
3598 ParseBoard12(string)
3599      char *string;
3600
3601     GameMode newGameMode;
3602     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3603     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3604     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3605     char to_play, board_chars[200];
3606     char move_str[500], str[500], elapsed_time[500];
3607     char black[32], white[32];
3608     Board board;
3609     int prevMove = currentMove;
3610     int ticking = 2;
3611     ChessMove moveType;
3612     int fromX, fromY, toX, toY;
3613     char promoChar;
3614     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3615     char *bookHit = NULL; // [HGM] book
3616     Boolean weird = FALSE, reqFlag = FALSE;
3617
3618     fromX = fromY = toX = toY = -1;
3619     
3620     newGame = FALSE;
3621
3622     if (appData.debugMode)
3623       fprintf(debugFP, _("Parsing board: %s\n"), string);
3624
3625     move_str[0] = NULLCHAR;
3626     elapsed_time[0] = NULLCHAR;
3627     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3628         int  i = 0, j;
3629         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3630             if(string[i] == ' ') { ranks++; files = 0; }
3631             else files++;
3632             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3633             i++;
3634         }
3635         for(j = 0; j <i; j++) board_chars[j] = string[j];
3636         board_chars[i] = '\0';
3637         string += i + 1;
3638     }
3639     n = sscanf(string, PATTERN, &to_play, &double_push,
3640                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3641                &gamenum, white, black, &relation, &basetime, &increment,
3642                &white_stren, &black_stren, &white_time, &black_time,
3643                &moveNum, str, elapsed_time, move_str, &ics_flip,
3644                &ticking);
3645
3646     if (n < 21) {
3647         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3648         DisplayError(str, 0);
3649         return;
3650     }
3651
3652     /* Convert the move number to internal form */
3653     moveNum = (moveNum - 1) * 2;
3654     if (to_play == 'B') moveNum++;
3655     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3656       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3657                         0, 1);
3658       return;
3659     }
3660     
3661     switch (relation) {
3662       case RELATION_OBSERVING_PLAYED:
3663       case RELATION_OBSERVING_STATIC:
3664         if (gamenum == -1) {
3665             /* Old ICC buglet */
3666             relation = RELATION_OBSERVING_STATIC;
3667         }
3668         newGameMode = IcsObserving;
3669         break;
3670       case RELATION_PLAYING_MYMOVE:
3671       case RELATION_PLAYING_NOTMYMOVE:
3672         newGameMode =
3673           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3674             IcsPlayingWhite : IcsPlayingBlack;
3675         break;
3676       case RELATION_EXAMINING:
3677         newGameMode = IcsExamining;
3678         break;
3679       case RELATION_ISOLATED_BOARD:
3680       default:
3681         /* Just display this board.  If user was doing something else,
3682            we will forget about it until the next board comes. */ 
3683         newGameMode = IcsIdle;
3684         break;
3685       case RELATION_STARTING_POSITION:
3686         newGameMode = gameMode;
3687         break;
3688     }
3689     
3690     /* Modify behavior for initial board display on move listing
3691        of wild games.
3692        */
3693     switch (ics_getting_history) {
3694       case H_FALSE:
3695       case H_REQUESTED:
3696         break;
3697       case H_GOT_REQ_HEADER:
3698       case H_GOT_UNREQ_HEADER:
3699         /* This is the initial position of the current game */
3700         gamenum = ics_gamenum;
3701         moveNum = 0;            /* old ICS bug workaround */
3702         if (to_play == 'B') {
3703           startedFromSetupPosition = TRUE;
3704           blackPlaysFirst = TRUE;
3705           moveNum = 1;
3706           if (forwardMostMove == 0) forwardMostMove = 1;
3707           if (backwardMostMove == 0) backwardMostMove = 1;
3708           if (currentMove == 0) currentMove = 1;
3709         }
3710         newGameMode = gameMode;
3711         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3712         break;
3713       case H_GOT_UNWANTED_HEADER:
3714         /* This is an initial board that we don't want */
3715         return;
3716       case H_GETTING_MOVES:
3717         /* Should not happen */
3718         DisplayError(_("Error gathering move list: extra board"), 0);
3719         ics_getting_history = H_FALSE;
3720         return;
3721     }
3722
3723    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3724                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3725      /* [HGM] We seem to have switched variant unexpectedly
3726       * Try to guess new variant from board size
3727       */
3728           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3729           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3730           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3731           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3732           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3733           if(!weird) newVariant = VariantNormal;
3734           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3735           /* Get a move list just to see the header, which
3736              will tell us whether this is really bug or zh */
3737           if (ics_getting_history == H_FALSE) {
3738             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3739             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3740             SendToICS(str);
3741           }
3742     }
3743     
3744     /* Take action if this is the first board of a new game, or of a
3745        different game than is currently being displayed.  */
3746     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3747         relation == RELATION_ISOLATED_BOARD) {
3748         
3749         /* Forget the old game and get the history (if any) of the new one */
3750         if (gameMode != BeginningOfGame) {
3751           Reset(TRUE, TRUE);
3752         }
3753         newGame = TRUE;
3754         if (appData.autoRaiseBoard) BoardToTop();
3755         prevMove = -3;
3756         if (gamenum == -1) {
3757             newGameMode = IcsIdle;
3758         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3759                    appData.getMoveList && !reqFlag) {
3760             /* Need to get game history */
3761             ics_getting_history = H_REQUESTED;
3762             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3763             SendToICS(str);
3764         }
3765         
3766         /* Initially flip the board to have black on the bottom if playing
3767            black or if the ICS flip flag is set, but let the user change
3768            it with the Flip View button. */
3769         flipView = appData.autoFlipView ? 
3770           (newGameMode == IcsPlayingBlack) || ics_flip :
3771           appData.flipView;
3772         
3773         /* Done with values from previous mode; copy in new ones */
3774         gameMode = newGameMode;
3775         ModeHighlight();
3776         ics_gamenum = gamenum;
3777         if (gamenum == gs_gamenum) {
3778             int klen = strlen(gs_kind);
3779             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3780             sprintf(str, "ICS %s", gs_kind);
3781             gameInfo.event = StrSave(str);
3782         } else {
3783             gameInfo.event = StrSave("ICS game");
3784         }
3785         gameInfo.site = StrSave(appData.icsHost);
3786         gameInfo.date = PGNDate();
3787         gameInfo.round = StrSave("-");
3788         gameInfo.white = StrSave(white);
3789         gameInfo.black = StrSave(black);
3790         timeControl = basetime * 60 * 1000;
3791         timeControl_2 = 0;
3792         timeIncrement = increment * 1000;
3793         movesPerSession = 0;
3794         gameInfo.timeControl = TimeControlTagValue();
3795         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3796   if (appData.debugMode) {
3797     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3798     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3799     setbuf(debugFP, NULL);
3800   }
3801
3802         gameInfo.outOfBook = NULL;
3803         
3804         /* Do we have the ratings? */
3805         if (strcmp(player1Name, white) == 0 &&
3806             strcmp(player2Name, black) == 0) {
3807             if (appData.debugMode)
3808               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3809                       player1Rating, player2Rating);
3810             gameInfo.whiteRating = player1Rating;
3811             gameInfo.blackRating = player2Rating;
3812         } else if (strcmp(player2Name, white) == 0 &&
3813                    strcmp(player1Name, black) == 0) {
3814             if (appData.debugMode)
3815               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3816                       player2Rating, player1Rating);
3817             gameInfo.whiteRating = player2Rating;
3818             gameInfo.blackRating = player1Rating;
3819         }
3820         player1Name[0] = player2Name[0] = NULLCHAR;
3821
3822         /* Silence shouts if requested */
3823         if (appData.quietPlay &&
3824             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3825             SendToICS(ics_prefix);
3826             SendToICS("set shout 0\n");
3827         }
3828     }
3829     
3830     /* Deal with midgame name changes */
3831     if (!newGame) {
3832         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3833             if (gameInfo.white) free(gameInfo.white);
3834             gameInfo.white = StrSave(white);
3835         }
3836         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3837             if (gameInfo.black) free(gameInfo.black);
3838             gameInfo.black = StrSave(black);
3839         }
3840     }
3841     
3842     /* Throw away game result if anything actually changes in examine mode */
3843     if (gameMode == IcsExamining && !newGame) {
3844         gameInfo.result = GameUnfinished;
3845         if (gameInfo.resultDetails != NULL) {
3846             free(gameInfo.resultDetails);
3847             gameInfo.resultDetails = NULL;
3848         }
3849     }
3850     
3851     /* In pausing && IcsExamining mode, we ignore boards coming
3852        in if they are in a different variation than we are. */
3853     if (pauseExamInvalid) return;
3854     if (pausing && gameMode == IcsExamining) {
3855         if (moveNum <= pauseExamForwardMostMove) {
3856             pauseExamInvalid = TRUE;
3857             forwardMostMove = pauseExamForwardMostMove;
3858             return;
3859         }
3860     }
3861     
3862   if (appData.debugMode) {
3863     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3864   }
3865     /* Parse the board */
3866     for (k = 0; k < ranks; k++) {
3867       for (j = 0; j < files; j++)
3868         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3869       if(gameInfo.holdingsWidth > 1) {
3870            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3871            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3872       }
3873     }
3874     CopyBoard(boards[moveNum], board);
3875     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3876     if (moveNum == 0) {
3877         startedFromSetupPosition =
3878           !CompareBoards(board, initialPosition);
3879         if(startedFromSetupPosition)
3880             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3881     }
3882
3883     /* [HGM] Set castling rights. Take the outermost Rooks,
3884        to make it also work for FRC opening positions. Note that board12
3885        is really defective for later FRC positions, as it has no way to
3886        indicate which Rook can castle if they are on the same side of King.
3887        For the initial position we grant rights to the outermost Rooks,
3888        and remember thos rights, and we then copy them on positions
3889        later in an FRC game. This means WB might not recognize castlings with
3890        Rooks that have moved back to their original position as illegal,
3891        but in ICS mode that is not its job anyway.
3892     */
3893     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3894     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3895
3896         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3897             if(board[0][i] == WhiteRook) j = i;
3898         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3899         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3900             if(board[0][i] == WhiteRook) j = i;
3901         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3902         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3903             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3904         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3905         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3906             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3907         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3908
3909         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3910         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3911             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3912         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3913             if(board[BOARD_HEIGHT-1][k] == bKing)
3914                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3915         if(gameInfo.variant == VariantTwoKings) {
3916             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3917             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3918             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3919         }
3920     } else { int r;
3921         r = boards[moveNum][CASTLING][0] = initialRights[0];
3922         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3923         r = boards[moveNum][CASTLING][1] = initialRights[1];
3924         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3925         r = boards[moveNum][CASTLING][3] = initialRights[3];
3926         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3927         r = boards[moveNum][CASTLING][4] = initialRights[4];
3928         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3929         /* wildcastle kludge: always assume King has rights */
3930         r = boards[moveNum][CASTLING][2] = initialRights[2];
3931         r = boards[moveNum][CASTLING][5] = initialRights[5];
3932     }
3933     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3934     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3935
3936     
3937     if (ics_getting_history == H_GOT_REQ_HEADER ||
3938         ics_getting_history == H_GOT_UNREQ_HEADER) {
3939         /* This was an initial position from a move list, not
3940            the current position */
3941         return;
3942     }
3943     
3944     /* Update currentMove and known move number limits */
3945     newMove = newGame || moveNum > forwardMostMove;
3946
3947     if (newGame) {
3948         forwardMostMove = backwardMostMove = currentMove = moveNum;
3949         if (gameMode == IcsExamining && moveNum == 0) {
3950           /* Workaround for ICS limitation: we are not told the wild
3951              type when starting to examine a game.  But if we ask for
3952              the move list, the move list header will tell us */
3953             ics_getting_history = H_REQUESTED;
3954             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3955             SendToICS(str);
3956         }
3957     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3958                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3959 #if ZIPPY
3960         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3961         /* [HGM] applied this also to an engine that is silently watching        */
3962         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3963             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3964             gameInfo.variant == currentlyInitializedVariant) {
3965           takeback = forwardMostMove - moveNum;
3966           for (i = 0; i < takeback; i++) {
3967             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3968             SendToProgram("undo\n", &first);
3969           }
3970         }
3971 #endif
3972
3973         forwardMostMove = moveNum;
3974         if (!pausing || currentMove > forwardMostMove)
3975           currentMove = forwardMostMove;
3976     } else {
3977         /* New part of history that is not contiguous with old part */ 
3978         if (pausing && gameMode == IcsExamining) {
3979             pauseExamInvalid = TRUE;
3980             forwardMostMove = pauseExamForwardMostMove;
3981             return;
3982         }
3983         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3984 #if ZIPPY
3985             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3986                 // [HGM] when we will receive the move list we now request, it will be
3987                 // fed to the engine from the first move on. So if the engine is not
3988                 // in the initial position now, bring it there.
3989                 InitChessProgram(&first, 0);
3990             }
3991 #endif
3992             ics_getting_history = H_REQUESTED;
3993             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3994             SendToICS(str);
3995         }
3996         forwardMostMove = backwardMostMove = currentMove = moveNum;
3997     }
3998     
3999     /* Update the clocks */
4000     if (strchr(elapsed_time, '.')) {
4001       /* Time is in ms */
4002       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4003       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4004     } else {
4005       /* Time is in seconds */
4006       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4007       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4008     }
4009       
4010
4011 #if ZIPPY
4012     if (appData.zippyPlay && newGame &&
4013         gameMode != IcsObserving && gameMode != IcsIdle &&
4014         gameMode != IcsExamining)
4015       ZippyFirstBoard(moveNum, basetime, increment);
4016 #endif
4017     
4018     /* Put the move on the move list, first converting
4019        to canonical algebraic form. */
4020     if (moveNum > 0) {
4021   if (appData.debugMode) {
4022     if (appData.debugMode) { int f = forwardMostMove;
4023         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4024                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4025                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4026     }
4027     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4028     fprintf(debugFP, "moveNum = %d\n", moveNum);
4029     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4030     setbuf(debugFP, NULL);
4031   }
4032         if (moveNum <= backwardMostMove) {
4033             /* We don't know what the board looked like before
4034                this move.  Punt. */
4035             strcpy(parseList[moveNum - 1], move_str);
4036             strcat(parseList[moveNum - 1], " ");
4037             strcat(parseList[moveNum - 1], elapsed_time);
4038             moveList[moveNum - 1][0] = NULLCHAR;
4039         } else if (strcmp(move_str, "none") == 0) {
4040             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4041             /* Again, we don't know what the board looked like;
4042                this is really the start of the game. */
4043             parseList[moveNum - 1][0] = NULLCHAR;
4044             moveList[moveNum - 1][0] = NULLCHAR;
4045             backwardMostMove = moveNum;
4046             startedFromSetupPosition = TRUE;
4047             fromX = fromY = toX = toY = -1;
4048         } else {
4049           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4050           //                 So we parse the long-algebraic move string in stead of the SAN move
4051           int valid; char buf[MSG_SIZ], *prom;
4052
4053           // str looks something like "Q/a1-a2"; kill the slash
4054           if(str[1] == '/') 
4055                 sprintf(buf, "%c%s", str[0], str+2);
4056           else  strcpy(buf, str); // might be castling
4057           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4058                 strcat(buf, prom); // long move lacks promo specification!
4059           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4060                 if(appData.debugMode) 
4061                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4062                 strcpy(move_str, buf);
4063           }
4064           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4065                                 &fromX, &fromY, &toX, &toY, &promoChar)
4066                || ParseOneMove(buf, moveNum - 1, &moveType,
4067                                 &fromX, &fromY, &toX, &toY, &promoChar);
4068           // end of long SAN patch
4069           if (valid) {
4070             (void) CoordsToAlgebraic(boards[moveNum - 1],
4071                                      PosFlags(moveNum - 1),
4072                                      fromY, fromX, toY, toX, promoChar,
4073                                      parseList[moveNum-1]);
4074             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4075               case MT_NONE:
4076               case MT_STALEMATE:
4077               default:
4078                 break;
4079               case MT_CHECK:
4080                 if(gameInfo.variant != VariantShogi)
4081                     strcat(parseList[moveNum - 1], "+");
4082                 break;
4083               case MT_CHECKMATE:
4084               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4085                 strcat(parseList[moveNum - 1], "#");
4086                 break;
4087             }
4088             strcat(parseList[moveNum - 1], " ");
4089             strcat(parseList[moveNum - 1], elapsed_time);
4090             /* currentMoveString is set as a side-effect of ParseOneMove */
4091             strcpy(moveList[moveNum - 1], currentMoveString);
4092             strcat(moveList[moveNum - 1], "\n");
4093           } else {
4094             /* Move from ICS was illegal!?  Punt. */
4095   if (appData.debugMode) {
4096     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4097     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4098   }
4099             strcpy(parseList[moveNum - 1], move_str);
4100             strcat(parseList[moveNum - 1], " ");
4101             strcat(parseList[moveNum - 1], elapsed_time);
4102             moveList[moveNum - 1][0] = NULLCHAR;
4103             fromX = fromY = toX = toY = -1;
4104           }
4105         }
4106   if (appData.debugMode) {
4107     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4108     setbuf(debugFP, NULL);
4109   }
4110
4111 #if ZIPPY
4112         /* Send move to chess program (BEFORE animating it). */
4113         if (appData.zippyPlay && !newGame && newMove && 
4114            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4115
4116             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4117                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4118                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4119                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4120                             move_str);
4121                     DisplayError(str, 0);
4122                 } else {
4123                     if (first.sendTime) {
4124                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4125                     }
4126                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4127                     if (firstMove && !bookHit) {
4128                         firstMove = FALSE;
4129                         if (first.useColors) {
4130                           SendToProgram(gameMode == IcsPlayingWhite ?
4131                                         "white\ngo\n" :
4132                                         "black\ngo\n", &first);
4133                         } else {
4134                           SendToProgram("go\n", &first);
4135                         }
4136                         first.maybeThinking = TRUE;
4137                     }
4138                 }
4139             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4140               if (moveList[moveNum - 1][0] == NULLCHAR) {
4141                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4142                 DisplayError(str, 0);
4143               } else {
4144                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4145                 SendMoveToProgram(moveNum - 1, &first);
4146               }
4147             }
4148         }
4149 #endif
4150     }
4151
4152     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4153         /* If move comes from a remote source, animate it.  If it
4154            isn't remote, it will have already been animated. */
4155         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4156             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4157         }
4158         if (!pausing && appData.highlightLastMove) {
4159             SetHighlights(fromX, fromY, toX, toY);
4160         }
4161     }
4162     
4163     /* Start the clocks */
4164     whiteFlag = blackFlag = FALSE;
4165     appData.clockMode = !(basetime == 0 && increment == 0);
4166     if (ticking == 0) {
4167       ics_clock_paused = TRUE;
4168       StopClocks();
4169     } else if (ticking == 1) {
4170       ics_clock_paused = FALSE;
4171     }
4172     if (gameMode == IcsIdle ||
4173         relation == RELATION_OBSERVING_STATIC ||
4174         relation == RELATION_EXAMINING ||
4175         ics_clock_paused)
4176       DisplayBothClocks();
4177     else
4178       StartClocks();
4179     
4180     /* Display opponents and material strengths */
4181     if (gameInfo.variant != VariantBughouse &&
4182         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4183         if (tinyLayout || smallLayout) {
4184             if(gameInfo.variant == VariantNormal)
4185                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4186                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4187                     basetime, increment);
4188             else
4189                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4190                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4191                     basetime, increment, (int) gameInfo.variant);
4192         } else {
4193             if(gameInfo.variant == VariantNormal)
4194                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4195                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4196                     basetime, increment);
4197             else
4198                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4199                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4200                     basetime, increment, VariantName(gameInfo.variant));
4201         }
4202         DisplayTitle(str);
4203   if (appData.debugMode) {
4204     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4205   }
4206     }
4207
4208
4209     /* Display the board */
4210     if (!pausing && !appData.noGUI) {
4211       
4212       if (appData.premove)
4213           if (!gotPremove || 
4214              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4215              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4216               ClearPremoveHighlights();
4217
4218       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4219       DrawPosition(j, boards[currentMove]);
4220
4221       DisplayMove(moveNum - 1);
4222       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4223             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4224               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4225         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4226       }
4227     }
4228
4229     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4230 #if ZIPPY
4231     if(bookHit) { // [HGM] book: simulate book reply
4232         static char bookMove[MSG_SIZ]; // a bit generous?
4233
4234         programStats.nodes = programStats.depth = programStats.time = 
4235         programStats.score = programStats.got_only_move = 0;
4236         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4237
4238         strcpy(bookMove, "move ");
4239         strcat(bookMove, bookHit);
4240         HandleMachineMove(bookMove, &first);
4241     }
4242 #endif
4243 }
4244
4245 void
4246 GetMoveListEvent()
4247 {
4248     char buf[MSG_SIZ];
4249     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4250         ics_getting_history = H_REQUESTED;
4251         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4252         SendToICS(buf);
4253     }
4254 }
4255
4256 void
4257 AnalysisPeriodicEvent(force)
4258      int force;
4259 {
4260     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4261          && !force) || !appData.periodicUpdates)
4262       return;
4263
4264     /* Send . command to Crafty to collect stats */
4265     SendToProgram(".\n", &first);
4266
4267     /* Don't send another until we get a response (this makes
4268        us stop sending to old Crafty's which don't understand
4269        the "." command (sending illegal cmds resets node count & time,
4270        which looks bad)) */
4271     programStats.ok_to_send = 0;
4272 }
4273
4274 void ics_update_width(new_width)
4275         int new_width;
4276 {
4277         ics_printf("set width %d\n", new_width);
4278 }
4279
4280 void
4281 SendMoveToProgram(moveNum, cps)
4282      int moveNum;
4283      ChessProgramState *cps;
4284 {
4285     char buf[MSG_SIZ];
4286
4287     if (cps->useUsermove) {
4288       SendToProgram("usermove ", cps);
4289     }
4290     if (cps->useSAN) {
4291       char *space;
4292       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4293         int len = space - parseList[moveNum];
4294         memcpy(buf, parseList[moveNum], len);
4295         buf[len++] = '\n';
4296         buf[len] = NULLCHAR;
4297       } else {
4298         sprintf(buf, "%s\n", parseList[moveNum]);
4299       }
4300       SendToProgram(buf, cps);
4301     } else {
4302       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4303         AlphaRank(moveList[moveNum], 4);
4304         SendToProgram(moveList[moveNum], cps);
4305         AlphaRank(moveList[moveNum], 4); // and back
4306       } else
4307       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4308        * the engine. It would be nice to have a better way to identify castle 
4309        * moves here. */
4310       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4311                                                                          && cps->useOOCastle) {
4312         int fromX = moveList[moveNum][0] - AAA; 
4313         int fromY = moveList[moveNum][1] - ONE;
4314         int toX = moveList[moveNum][2] - AAA; 
4315         int toY = moveList[moveNum][3] - ONE;
4316         if((boards[moveNum][fromY][fromX] == WhiteKing 
4317             && boards[moveNum][toY][toX] == WhiteRook)
4318            || (boards[moveNum][fromY][fromX] == BlackKing 
4319                && boards[moveNum][toY][toX] == BlackRook)) {
4320           if(toX > fromX) SendToProgram("O-O\n", cps);
4321           else SendToProgram("O-O-O\n", cps);
4322         }
4323         else SendToProgram(moveList[moveNum], cps);
4324       }
4325       else SendToProgram(moveList[moveNum], cps);
4326       /* End of additions by Tord */
4327     }
4328
4329     /* [HGM] setting up the opening has brought engine in force mode! */
4330     /*       Send 'go' if we are in a mode where machine should play. */
4331     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4332         (gameMode == TwoMachinesPlay   ||
4333 #ifdef ZIPPY
4334          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4335 #endif
4336          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4337         SendToProgram("go\n", cps);
4338   if (appData.debugMode) {
4339     fprintf(debugFP, "(extra)\n");
4340   }
4341     }
4342     setboardSpoiledMachineBlack = 0;
4343 }
4344
4345 void
4346 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4347      ChessMove moveType;
4348      int fromX, fromY, toX, toY;
4349 {
4350     char user_move[MSG_SIZ];
4351
4352     switch (moveType) {
4353       default:
4354         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4355                 (int)moveType, fromX, fromY, toX, toY);
4356         DisplayError(user_move + strlen("say "), 0);
4357         break;
4358       case WhiteKingSideCastle:
4359       case BlackKingSideCastle:
4360       case WhiteQueenSideCastleWild:
4361       case BlackQueenSideCastleWild:
4362       /* PUSH Fabien */
4363       case WhiteHSideCastleFR:
4364       case BlackHSideCastleFR:
4365       /* POP Fabien */
4366         sprintf(user_move, "o-o\n");
4367         break;
4368       case WhiteQueenSideCastle:
4369       case BlackQueenSideCastle:
4370       case WhiteKingSideCastleWild:
4371       case BlackKingSideCastleWild:
4372       /* PUSH Fabien */
4373       case WhiteASideCastleFR:
4374       case BlackASideCastleFR:
4375       /* POP Fabien */
4376         sprintf(user_move, "o-o-o\n");
4377         break;
4378       case WhitePromotionQueen:
4379       case BlackPromotionQueen:
4380       case WhitePromotionRook:
4381       case BlackPromotionRook:
4382       case WhitePromotionBishop:
4383       case BlackPromotionBishop:
4384       case WhitePromotionKnight:
4385       case BlackPromotionKnight:
4386       case WhitePromotionKing:
4387       case BlackPromotionKing:
4388       case WhitePromotionChancellor:
4389       case BlackPromotionChancellor:
4390       case WhitePromotionArchbishop:
4391       case BlackPromotionArchbishop:
4392         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4393             sprintf(user_move, "%c%c%c%c=%c\n",
4394                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4395                 PieceToChar(WhiteFerz));
4396         else if(gameInfo.variant == VariantGreat)
4397             sprintf(user_move, "%c%c%c%c=%c\n",
4398                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4399                 PieceToChar(WhiteMan));
4400         else
4401             sprintf(user_move, "%c%c%c%c=%c\n",
4402                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4403                 PieceToChar(PromoPiece(moveType)));
4404         break;
4405       case WhiteDrop:
4406       case BlackDrop:
4407         sprintf(user_move, "%c@%c%c\n",
4408                 ToUpper(PieceToChar((ChessSquare) fromX)),
4409                 AAA + toX, ONE + toY);
4410         break;
4411       case NormalMove:
4412       case WhiteCapturesEnPassant:
4413       case BlackCapturesEnPassant:
4414       case IllegalMove:  /* could be a variant we don't quite understand */
4415         sprintf(user_move, "%c%c%c%c\n",
4416                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4417         break;
4418     }
4419     SendToICS(user_move);
4420     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4421         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4422 }
4423
4424 void
4425 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4426      int rf, ff, rt, ft;
4427      char promoChar;
4428      char move[7];
4429 {
4430     if (rf == DROP_RANK) {
4431         sprintf(move, "%c@%c%c\n",
4432                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4433     } else {
4434         if (promoChar == 'x' || promoChar == NULLCHAR) {
4435             sprintf(move, "%c%c%c%c\n",
4436                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4437         } else {
4438             sprintf(move, "%c%c%c%c%c\n",
4439                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4440         }
4441     }
4442 }
4443
4444 void
4445 ProcessICSInitScript(f)
4446      FILE *f;
4447 {
4448     char buf[MSG_SIZ];
4449
4450     while (fgets(buf, MSG_SIZ, f)) {
4451         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4452     }
4453
4454     fclose(f);
4455 }
4456
4457
4458 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4459 void
4460 AlphaRank(char *move, int n)
4461 {
4462 //    char *p = move, c; int x, y;
4463
4464     if (appData.debugMode) {
4465         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4466     }
4467
4468     if(move[1]=='*' && 
4469        move[2]>='0' && move[2]<='9' &&
4470        move[3]>='a' && move[3]<='x'    ) {
4471         move[1] = '@';
4472         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4473         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4474     } else
4475     if(move[0]>='0' && move[0]<='9' &&
4476        move[1]>='a' && move[1]<='x' &&
4477        move[2]>='0' && move[2]<='9' &&
4478        move[3]>='a' && move[3]<='x'    ) {
4479         /* input move, Shogi -> normal */
4480         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4481         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4482         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4483         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4484     } else
4485     if(move[1]=='@' &&
4486        move[3]>='0' && move[3]<='9' &&
4487        move[2]>='a' && move[2]<='x'    ) {
4488         move[1] = '*';
4489         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4490         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4491     } else
4492     if(
4493        move[0]>='a' && move[0]<='x' &&
4494        move[3]>='0' && move[3]<='9' &&
4495        move[2]>='a' && move[2]<='x'    ) {
4496          /* output move, normal -> Shogi */
4497         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4498         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4499         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4500         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4501         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4502     }
4503     if (appData.debugMode) {
4504         fprintf(debugFP, "   out = '%s'\n", move);
4505     }
4506 }
4507
4508 /* Parser for moves from gnuchess, ICS, or user typein box */
4509 Boolean
4510 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4511      char *move;
4512      int moveNum;
4513      ChessMove *moveType;
4514      int *fromX, *fromY, *toX, *toY;
4515      char *promoChar;
4516 {       
4517     if (appData.debugMode) {
4518         fprintf(debugFP, "move to parse: %s\n", move);
4519     }
4520     *moveType = yylexstr(moveNum, move);
4521
4522     switch (*moveType) {
4523       case WhitePromotionChancellor:
4524       case BlackPromotionChancellor:
4525       case WhitePromotionArchbishop:
4526       case BlackPromotionArchbishop:
4527       case WhitePromotionQueen:
4528       case BlackPromotionQueen:
4529       case WhitePromotionRook:
4530       case BlackPromotionRook:
4531       case WhitePromotionBishop:
4532       case BlackPromotionBishop:
4533       case WhitePromotionKnight:
4534       case BlackPromotionKnight:
4535       case WhitePromotionKing:
4536       case BlackPromotionKing:
4537       case NormalMove:
4538       case WhiteCapturesEnPassant:
4539       case BlackCapturesEnPassant:
4540       case WhiteKingSideCastle:
4541       case WhiteQueenSideCastle:
4542       case BlackKingSideCastle:
4543       case BlackQueenSideCastle:
4544       case WhiteKingSideCastleWild:
4545       case WhiteQueenSideCastleWild:
4546       case BlackKingSideCastleWild:
4547       case BlackQueenSideCastleWild:
4548       /* Code added by Tord: */
4549       case WhiteHSideCastleFR:
4550       case WhiteASideCastleFR:
4551       case BlackHSideCastleFR:
4552       case BlackASideCastleFR:
4553       /* End of code added by Tord */
4554       case IllegalMove:         /* bug or odd chess variant */
4555         *fromX = currentMoveString[0] - AAA;
4556         *fromY = currentMoveString[1] - ONE;
4557         *toX = currentMoveString[2] - AAA;
4558         *toY = currentMoveString[3] - ONE;
4559         *promoChar = currentMoveString[4];
4560         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4561             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4562     if (appData.debugMode) {
4563         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4564     }
4565             *fromX = *fromY = *toX = *toY = 0;
4566             return FALSE;
4567         }
4568         if (appData.testLegality) {
4569           return (*moveType != IllegalMove);
4570         } else {
4571           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4573         }
4574
4575       case WhiteDrop:
4576       case BlackDrop:
4577         *fromX = *moveType == WhiteDrop ?
4578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4579           (int) CharToPiece(ToLower(currentMoveString[0]));
4580         *fromY = DROP_RANK;
4581         *toX = currentMoveString[2] - AAA;
4582         *toY = currentMoveString[3] - ONE;
4583         *promoChar = NULLCHAR;
4584         return TRUE;
4585
4586       case AmbiguousMove:
4587       case ImpossibleMove:
4588       case (ChessMove) 0:       /* end of file */
4589       case ElapsedTime:
4590       case Comment:
4591       case PGNTag:
4592       case NAG:
4593       case WhiteWins:
4594       case BlackWins:
4595       case GameIsDrawn:
4596       default:
4597     if (appData.debugMode) {
4598         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4599     }
4600         /* bug? */
4601         *fromX = *fromY = *toX = *toY = 0;
4602         *promoChar = NULLCHAR;
4603         return FALSE;
4604     }
4605 }
4606
4607
4608 void
4609 ParsePV(char *pv)
4610 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4611   int fromX, fromY, toX, toY; char promoChar;
4612   ChessMove moveType;
4613   Boolean valid;
4614   int nr = 0;
4615
4616   endPV = forwardMostMove;
4617   do {
4618     while(*pv == ' ') pv++;
4619     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4620     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4621 if(appData.debugMode){
4622 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4623 }
4624     if(!valid && nr == 0 &&
4625        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4626         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4627     }
4628     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4629     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4630     nr++;
4631     if(endPV+1 > framePtr) break; // no space, truncate
4632     if(!valid) break;
4633     endPV++;
4634     CopyBoard(boards[endPV], boards[endPV-1]);
4635     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4636     moveList[endPV-1][0] = fromX + AAA;
4637     moveList[endPV-1][1] = fromY + ONE;
4638     moveList[endPV-1][2] = toX + AAA;
4639     moveList[endPV-1][3] = toY + ONE;
4640     parseList[endPV-1][0] = NULLCHAR;
4641   } while(valid);
4642   currentMove = endPV;
4643   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4644   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4645                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4646   DrawPosition(TRUE, boards[currentMove]);
4647 }
4648
4649 static int lastX, lastY;
4650
4651 Boolean
4652 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4653 {
4654         int startPV;
4655
4656         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4657         lastX = x; lastY = y;
4658         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4659         startPV = index;
4660       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4661       index = startPV;
4662         while(buf[index] && buf[index] != '\n') index++;
4663         buf[index] = 0;
4664         ParsePV(buf+startPV);
4665         *start = startPV; *end = index-1;
4666         return TRUE;
4667 }
4668
4669 Boolean
4670 LoadPV(int x, int y)
4671 { // called on right mouse click to load PV
4672   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4673   lastX = x; lastY = y;
4674   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4675   return TRUE;
4676 }
4677
4678 void
4679 UnLoadPV()
4680 {
4681   if(endPV < 0) return;
4682   endPV = -1;
4683   currentMove = forwardMostMove;
4684   ClearPremoveHighlights();
4685   DrawPosition(TRUE, boards[currentMove]);
4686 }
4687
4688 void
4689 MovePV(int x, int y, int h)
4690 { // step through PV based on mouse coordinates (called on mouse move)
4691   int margin = h>>3, step = 0;
4692
4693   if(endPV < 0) return;
4694   // we must somehow check if right button is still down (might be released off board!)
4695   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4696   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4697   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4698   if(!step) return;
4699   lastX = x; lastY = y;
4700   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4701   currentMove += step;
4702   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4703   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4704                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4705   DrawPosition(FALSE, boards[currentMove]);
4706 }
4707
4708
4709 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4710 // All positions will have equal probability, but the current method will not provide a unique
4711 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4712 #define DARK 1
4713 #define LITE 2
4714 #define ANY 3
4715
4716 int squaresLeft[4];
4717 int piecesLeft[(int)BlackPawn];
4718 int seed, nrOfShuffles;
4719
4720 void GetPositionNumber()
4721 {       // sets global variable seed
4722         int i;
4723
4724         seed = appData.defaultFrcPosition;
4725         if(seed < 0) { // randomize based on time for negative FRC position numbers
4726                 for(i=0; i<50; i++) seed += random();
4727                 seed = random() ^ random() >> 8 ^ random() << 8;
4728                 if(seed<0) seed = -seed;
4729         }
4730 }
4731
4732 int put(Board board, int pieceType, int rank, int n, int shade)
4733 // put the piece on the (n-1)-th empty squares of the given shade
4734 {
4735         int i;
4736
4737         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4738                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4739                         board[rank][i] = (ChessSquare) pieceType;
4740                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4741                         squaresLeft[ANY]--;
4742                         piecesLeft[pieceType]--; 
4743                         return i;
4744                 }
4745         }
4746         return -1;
4747 }
4748
4749
4750 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4751 // calculate where the next piece goes, (any empty square), and put it there
4752 {
4753         int i;
4754
4755         i = seed % squaresLeft[shade];
4756         nrOfShuffles *= squaresLeft[shade];
4757         seed /= squaresLeft[shade];
4758         put(board, pieceType, rank, i, shade);
4759 }
4760
4761 void AddTwoPieces(Board board, int pieceType, int rank)
4762 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4763 {
4764         int i, n=squaresLeft[ANY], j=n-1, k;
4765
4766         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4767         i = seed % k;  // pick one
4768         nrOfShuffles *= k;
4769         seed /= k;
4770         while(i >= j) i -= j--;
4771         j = n - 1 - j; i += j;
4772         put(board, pieceType, rank, j, ANY);
4773         put(board, pieceType, rank, i, ANY);
4774 }
4775
4776 void SetUpShuffle(Board board, int number)
4777 {
4778         int i, p, first=1;
4779
4780         GetPositionNumber(); nrOfShuffles = 1;
4781
4782         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4783         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4784         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4785
4786         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4787
4788         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4789             p = (int) board[0][i];
4790             if(p < (int) BlackPawn) piecesLeft[p] ++;
4791             board[0][i] = EmptySquare;
4792         }
4793
4794         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4795             // shuffles restricted to allow normal castling put KRR first
4796             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4797                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4798             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4799                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4800             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4801                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4802             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4803                 put(board, WhiteRook, 0, 0, ANY);
4804             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4805         }
4806
4807         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4808             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4809             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4810                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4811                 while(piecesLeft[p] >= 2) {
4812                     AddOnePiece(board, p, 0, LITE);
4813                     AddOnePiece(board, p, 0, DARK);
4814                 }
4815                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4816             }
4817
4818         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4819             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4820             // but we leave King and Rooks for last, to possibly obey FRC restriction
4821             if(p == (int)WhiteRook) continue;
4822             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4823             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4824         }
4825
4826         // now everything is placed, except perhaps King (Unicorn) and Rooks
4827
4828         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4829             // Last King gets castling rights
4830             while(piecesLeft[(int)WhiteUnicorn]) {
4831                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4832                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4833             }
4834
4835             while(piecesLeft[(int)WhiteKing]) {
4836                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4837                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4838             }
4839
4840
4841         } else {
4842             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4843             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4844         }
4845
4846         // Only Rooks can be left; simply place them all
4847         while(piecesLeft[(int)WhiteRook]) {
4848                 i = put(board, WhiteRook, 0, 0, ANY);
4849                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4850                         if(first) {
4851                                 first=0;
4852                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4853                         }
4854                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4855                 }
4856         }
4857         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4858             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4859         }
4860
4861         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4862 }
4863
4864 int SetCharTable( char *table, const char * map )
4865 /* [HGM] moved here from winboard.c because of its general usefulness */
4866 /*       Basically a safe strcpy that uses the last character as King */
4867 {
4868     int result = FALSE; int NrPieces;
4869
4870     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4871                     && NrPieces >= 12 && !(NrPieces&1)) {
4872         int i; /* [HGM] Accept even length from 12 to 34 */
4873
4874         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4875         for( i=0; i<NrPieces/2-1; i++ ) {
4876             table[i] = map[i];
4877             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4878         }
4879         table[(int) WhiteKing]  = map[NrPieces/2-1];
4880         table[(int) BlackKing]  = map[NrPieces-1];
4881
4882         result = TRUE;
4883     }
4884
4885     return result;
4886 }
4887
4888 void Prelude(Board board)
4889 {       // [HGM] superchess: random selection of exo-pieces
4890         int i, j, k; ChessSquare p; 
4891         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4892
4893         GetPositionNumber(); // use FRC position number
4894
4895         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4896             SetCharTable(pieceToChar, appData.pieceToCharTable);
4897             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4898                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4899         }
4900
4901         j = seed%4;                 seed /= 4; 
4902         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4903         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4904         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4905         j = seed%3 + (seed%3 >= j); seed /= 3; 
4906         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4907         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4908         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4909         j = seed%3;                 seed /= 3; 
4910         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4911         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4912         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4913         j = seed%2 + (seed%2 >= j); seed /= 2; 
4914         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4915         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4916         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4917         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4918         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4919         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4920         put(board, exoPieces[0],    0, 0, ANY);
4921         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4922 }
4923
4924 void
4925 InitPosition(redraw)
4926      int redraw;
4927 {
4928     ChessSquare (* pieces)[BOARD_FILES];
4929     int i, j, pawnRow, overrule,
4930     oldx = gameInfo.boardWidth,
4931     oldy = gameInfo.boardHeight,
4932     oldh = gameInfo.holdingsWidth,
4933     oldv = gameInfo.variant;
4934
4935     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4936
4937     /* [AS] Initialize pv info list [HGM] and game status */
4938     {
4939         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4940             pvInfoList[i].depth = 0;
4941             boards[i][EP_STATUS] = EP_NONE;
4942             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4943         }
4944
4945         initialRulePlies = 0; /* 50-move counter start */
4946
4947         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4948         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4949     }
4950
4951     
4952     /* [HGM] logic here is completely changed. In stead of full positions */
4953     /* the initialized data only consist of the two backranks. The switch */
4954     /* selects which one we will use, which is than copied to the Board   */
4955     /* initialPosition, which for the rest is initialized by Pawns and    */
4956     /* empty squares. This initial position is then copied to boards[0],  */
4957     /* possibly after shuffling, so that it remains available.            */
4958
4959     gameInfo.holdingsWidth = 0; /* default board sizes */
4960     gameInfo.boardWidth    = 8;
4961     gameInfo.boardHeight   = 8;
4962     gameInfo.holdingsSize  = 0;
4963     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4964     for(i=0; i<BOARD_FILES-2; i++)
4965       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4966     initialPosition[EP_STATUS] = EP_NONE;
4967     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4968
4969     switch (gameInfo.variant) {
4970     case VariantFischeRandom:
4971       shuffleOpenings = TRUE;
4972     default:
4973       pieces = FIDEArray;
4974       break;
4975     case VariantShatranj:
4976       pieces = ShatranjArray;
4977       nrCastlingRights = 0;
4978       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4979       break;
4980     case VariantMakruk:
4981       pieces = makrukArray;
4982       nrCastlingRights = 0;
4983       startedFromSetupPosition = TRUE;
4984       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4985       break;
4986     case VariantTwoKings:
4987       pieces = twoKingsArray;
4988       break;
4989     case VariantCapaRandom:
4990       shuffleOpenings = TRUE;
4991     case VariantCapablanca:
4992       pieces = CapablancaArray;
4993       gameInfo.boardWidth = 10;
4994       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4995       break;
4996     case VariantGothic:
4997       pieces = GothicArray;
4998       gameInfo.boardWidth = 10;
4999       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5000       break;
5001     case VariantJanus:
5002       pieces = JanusArray;
5003       gameInfo.boardWidth = 10;
5004       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5005       nrCastlingRights = 6;
5006         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5007         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5008         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5009         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5010         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5011         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5012       break;
5013     case VariantFalcon:
5014       pieces = FalconArray;
5015       gameInfo.boardWidth = 10;
5016       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5017       break;
5018     case VariantXiangqi:
5019       pieces = XiangqiArray;
5020       gameInfo.boardWidth  = 9;
5021       gameInfo.boardHeight = 10;
5022       nrCastlingRights = 0;
5023       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5024       break;
5025     case VariantShogi:
5026       pieces = ShogiArray;
5027       gameInfo.boardWidth  = 9;
5028       gameInfo.boardHeight = 9;
5029       gameInfo.holdingsSize = 7;
5030       nrCastlingRights = 0;
5031       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5032       break;
5033     case VariantCourier:
5034       pieces = CourierArray;
5035       gameInfo.boardWidth  = 12;
5036       nrCastlingRights = 0;
5037       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5038       break;
5039     case VariantKnightmate:
5040       pieces = KnightmateArray;
5041       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5042       break;
5043     case VariantFairy:
5044       pieces = fairyArray;
5045       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5046       break;
5047     case VariantGreat:
5048       pieces = GreatArray;
5049       gameInfo.boardWidth = 10;
5050       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5051       gameInfo.holdingsSize = 8;
5052       break;
5053     case VariantSuper:
5054       pieces = FIDEArray;
5055       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5056       gameInfo.holdingsSize = 8;
5057       startedFromSetupPosition = TRUE;
5058       break;
5059     case VariantCrazyhouse:
5060     case VariantBughouse:
5061       pieces = FIDEArray;
5062       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5063       gameInfo.holdingsSize = 5;
5064       break;
5065     case VariantWildCastle:
5066       pieces = FIDEArray;
5067       /* !!?shuffle with kings guaranteed to be on d or e file */
5068       shuffleOpenings = 1;
5069       break;
5070     case VariantNoCastle:
5071       pieces = FIDEArray;
5072       nrCastlingRights = 0;
5073       /* !!?unconstrained back-rank shuffle */
5074       shuffleOpenings = 1;
5075       break;
5076     }
5077
5078     overrule = 0;
5079     if(appData.NrFiles >= 0) {
5080         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5081         gameInfo.boardWidth = appData.NrFiles;
5082     }
5083     if(appData.NrRanks >= 0) {
5084         gameInfo.boardHeight = appData.NrRanks;
5085     }
5086     if(appData.holdingsSize >= 0) {
5087         i = appData.holdingsSize;
5088         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5089         gameInfo.holdingsSize = i;
5090     }
5091     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5092     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5093         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5094
5095     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5096     if(pawnRow < 1) pawnRow = 1;
5097     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5098
5099     /* User pieceToChar list overrules defaults */
5100     if(appData.pieceToCharTable != NULL)
5101         SetCharTable(pieceToChar, appData.pieceToCharTable);
5102
5103     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5104
5105         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5106             s = (ChessSquare) 0; /* account holding counts in guard band */
5107         for( i=0; i<BOARD_HEIGHT; i++ )
5108             initialPosition[i][j] = s;
5109
5110         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5111         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5112         initialPosition[pawnRow][j] = WhitePawn;
5113         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5114         if(gameInfo.variant == VariantXiangqi) {
5115             if(j&1) {
5116                 initialPosition[pawnRow][j] = 
5117                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5118                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5119                    initialPosition[2][j] = WhiteCannon;
5120                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5121                 }
5122             }
5123         }
5124         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5125     }
5126     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5127
5128             j=BOARD_LEFT+1;
5129             initialPosition[1][j] = WhiteBishop;
5130             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5131             j=BOARD_RGHT-2;
5132             initialPosition[1][j] = WhiteRook;
5133             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5134     }
5135
5136     if( nrCastlingRights == -1) {
5137         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5138         /*       This sets default castling rights from none to normal corners   */
5139         /* Variants with other castling rights must set them themselves above    */
5140         nrCastlingRights = 6;
5141        
5142         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5143         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5144         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5145         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5146         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5147         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5148      }
5149
5150      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5151      if(gameInfo.variant == VariantGreat) { // promotion commoners
5152         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5153         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5154         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5155         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5156      }
5157   if (appData.debugMode) {
5158     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5159   }
5160     if(shuffleOpenings) {
5161         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5162         startedFromSetupPosition = TRUE;
5163     }
5164     if(startedFromPositionFile) {
5165       /* [HGM] loadPos: use PositionFile for every new game */
5166       CopyBoard(initialPosition, filePosition);
5167       for(i=0; i<nrCastlingRights; i++)
5168           initialRights[i] = filePosition[CASTLING][i];
5169       startedFromSetupPosition = TRUE;
5170     }
5171
5172     CopyBoard(boards[0], initialPosition);
5173
5174     if(oldx != gameInfo.boardWidth ||
5175        oldy != gameInfo.boardHeight ||
5176        oldh != gameInfo.holdingsWidth
5177 #ifdef GOTHIC
5178        || oldv == VariantGothic ||        // For licensing popups
5179        gameInfo.variant == VariantGothic
5180 #endif
5181 #ifdef FALCON
5182        || oldv == VariantFalcon ||
5183        gameInfo.variant == VariantFalcon
5184 #endif
5185                                          )
5186             InitDrawingSizes(-2 ,0);
5187
5188     if (redraw)
5189       DrawPosition(TRUE, boards[currentMove]);
5190 }
5191
5192 void
5193 SendBoard(cps, moveNum)
5194      ChessProgramState *cps;
5195      int moveNum;
5196 {
5197     char message[MSG_SIZ];
5198     
5199     if (cps->useSetboard) {
5200       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5201       sprintf(message, "setboard %s\n", fen);
5202       SendToProgram(message, cps);
5203       free(fen);
5204
5205     } else {
5206       ChessSquare *bp;
5207       int i, j;
5208       /* Kludge to set black to move, avoiding the troublesome and now
5209        * deprecated "black" command.
5210        */
5211       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5212
5213       SendToProgram("edit\n", cps);
5214       SendToProgram("#\n", cps);
5215       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5216         bp = &boards[moveNum][i][BOARD_LEFT];
5217         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5218           if ((int) *bp < (int) BlackPawn) {
5219             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5220                     AAA + j, ONE + i);
5221             if(message[0] == '+' || message[0] == '~') {
5222                 sprintf(message, "%c%c%c+\n",
5223                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5224                         AAA + j, ONE + i);
5225             }
5226             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5227                 message[1] = BOARD_RGHT   - 1 - j + '1';
5228                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5229             }
5230             SendToProgram(message, cps);
5231           }
5232         }
5233       }
5234     
5235       SendToProgram("c\n", cps);
5236       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5237         bp = &boards[moveNum][i][BOARD_LEFT];
5238         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5239           if (((int) *bp != (int) EmptySquare)
5240               && ((int) *bp >= (int) BlackPawn)) {
5241             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5242                     AAA + j, ONE + i);
5243             if(message[0] == '+' || message[0] == '~') {
5244                 sprintf(message, "%c%c%c+\n",
5245                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5246                         AAA + j, ONE + i);
5247             }
5248             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5249                 message[1] = BOARD_RGHT   - 1 - j + '1';
5250                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5251             }
5252             SendToProgram(message, cps);
5253           }
5254         }
5255       }
5256     
5257       SendToProgram(".\n", cps);
5258     }
5259     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5260 }
5261
5262 int
5263 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5264 {
5265     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5266     /* [HGM] add Shogi promotions */
5267     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5268     ChessSquare piece;
5269     ChessMove moveType;
5270     Boolean premove;
5271
5272     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5273     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5274
5275     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5276       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5277         return FALSE;
5278
5279     piece = boards[currentMove][fromY][fromX];
5280     if(gameInfo.variant == VariantShogi) {
5281         promotionZoneSize = 3;
5282         highestPromotingPiece = (int)WhiteFerz;
5283     } else if(gameInfo.variant == VariantMakruk) {
5284         promotionZoneSize = 3;
5285     }
5286
5287     // next weed out all moves that do not touch the promotion zone at all
5288     if((int)piece >= BlackPawn) {
5289         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5290              return FALSE;
5291         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5292     } else {
5293         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5294            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5295     }
5296
5297     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5298
5299     // weed out mandatory Shogi promotions
5300     if(gameInfo.variant == VariantShogi) {
5301         if(piece >= BlackPawn) {
5302             if(toY == 0 && piece == BlackPawn ||
5303                toY == 0 && piece == BlackQueen ||
5304                toY <= 1 && piece == BlackKnight) {
5305                 *promoChoice = '+';
5306                 return FALSE;
5307             }
5308         } else {
5309             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5310                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5311                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5312                 *promoChoice = '+';
5313                 return FALSE;
5314             }
5315         }
5316     }
5317
5318     // weed out obviously illegal Pawn moves
5319     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5320         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5321         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5322         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5323         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5324         // note we are not allowed to test for valid (non-)capture, due to premove
5325     }
5326
5327     // we either have a choice what to promote to, or (in Shogi) whether to promote
5328     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5329         *promoChoice = PieceToChar(BlackFerz);  // no choice
5330         return FALSE;
5331     }
5332     if(appData.alwaysPromoteToQueen) { // predetermined
5333         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5334              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5335         else *promoChoice = PieceToChar(BlackQueen);
5336         return FALSE;
5337     }
5338
5339     // suppress promotion popup on illegal moves that are not premoves
5340     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5341               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5342     if(appData.testLegality && !premove) {
5343         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5344                         fromY, fromX, toY, toX, NULLCHAR);
5345         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5346            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5347             return FALSE;
5348     }
5349
5350     return TRUE;
5351 }
5352
5353 int
5354 InPalace(row, column)
5355      int row, column;
5356 {   /* [HGM] for Xiangqi */
5357     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5358          column < (BOARD_WIDTH + 4)/2 &&
5359          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5360     return FALSE;
5361 }
5362
5363 int
5364 PieceForSquare (x, y)
5365      int x;
5366      int y;
5367 {
5368   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5369      return -1;
5370   else
5371      return boards[currentMove][y][x];
5372 }
5373
5374 int
5375 OKToStartUserMove(x, y)
5376      int x, y;
5377 {
5378     ChessSquare from_piece;
5379     int white_piece;
5380
5381     if (matchMode) return FALSE;
5382     if (gameMode == EditPosition) return TRUE;
5383
5384     if (x >= 0 && y >= 0)
5385       from_piece = boards[currentMove][y][x];
5386     else
5387       from_piece = EmptySquare;
5388
5389     if (from_piece == EmptySquare) return FALSE;
5390
5391     white_piece = (int)from_piece >= (int)WhitePawn &&
5392       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5393
5394     switch (gameMode) {
5395       case PlayFromGameFile:
5396       case AnalyzeFile:
5397       case TwoMachinesPlay:
5398       case EndOfGame:
5399         return FALSE;
5400
5401       case IcsObserving:
5402       case IcsIdle:
5403         return FALSE;
5404
5405       case MachinePlaysWhite:
5406       case IcsPlayingBlack:
5407         if (appData.zippyPlay) return FALSE;
5408         if (white_piece) {
5409             DisplayMoveError(_("You are playing Black"));
5410             return FALSE;
5411         }
5412         break;
5413
5414       case MachinePlaysBlack:
5415       case IcsPlayingWhite:
5416         if (appData.zippyPlay) return FALSE;
5417         if (!white_piece) {
5418             DisplayMoveError(_("You are playing White"));
5419             return FALSE;
5420         }
5421         break;
5422
5423       case EditGame:
5424         if (!white_piece && WhiteOnMove(currentMove)) {
5425             DisplayMoveError(_("It is White's turn"));
5426             return FALSE;
5427         }           
5428         if (white_piece && !WhiteOnMove(currentMove)) {
5429             DisplayMoveError(_("It is Black's turn"));
5430             return FALSE;
5431         }           
5432         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5433             /* Editing correspondence game history */
5434             /* Could disallow this or prompt for confirmation */
5435             cmailOldMove = -1;
5436         }
5437         break;
5438
5439       case BeginningOfGame:
5440         if (appData.icsActive) return FALSE;
5441         if (!appData.noChessProgram) {
5442             if (!white_piece) {
5443                 DisplayMoveError(_("You are playing White"));
5444                 return FALSE;
5445             }
5446         }
5447         break;
5448         
5449       case Training:
5450         if (!white_piece && WhiteOnMove(currentMove)) {
5451             DisplayMoveError(_("It is White's turn"));
5452             return FALSE;
5453         }           
5454         if (white_piece && !WhiteOnMove(currentMove)) {
5455             DisplayMoveError(_("It is Black's turn"));
5456             return FALSE;
5457         }           
5458         break;
5459
5460       default:
5461       case IcsExamining:
5462         break;
5463     }
5464     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5465         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5466         && gameMode != AnalyzeFile && gameMode != Training) {
5467         DisplayMoveError(_("Displayed position is not current"));
5468         return FALSE;
5469     }
5470     return TRUE;
5471 }
5472
5473 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5474 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5475 int lastLoadGameUseList = FALSE;
5476 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5477 ChessMove lastLoadGameStart = (ChessMove) 0;
5478
5479 ChessMove
5480 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5481      int fromX, fromY, toX, toY;
5482      int promoChar;
5483      Boolean captureOwn;
5484 {
5485     ChessMove moveType;
5486     ChessSquare pdown, pup;
5487
5488     /* Check if the user is playing in turn.  This is complicated because we
5489        let the user "pick up" a piece before it is his turn.  So the piece he
5490        tried to pick up may have been captured by the time he puts it down!
5491        Therefore we use the color the user is supposed to be playing in this
5492        test, not the color of the piece that is currently on the starting
5493        square---except in EditGame mode, where the user is playing both
5494        sides; fortunately there the capture race can't happen.  (It can
5495        now happen in IcsExamining mode, but that's just too bad.  The user
5496        will get a somewhat confusing message in that case.)
5497        */
5498
5499     switch (gameMode) {
5500       case PlayFromGameFile:
5501       case AnalyzeFile:
5502       case TwoMachinesPlay:
5503       case EndOfGame:
5504       case IcsObserving:
5505       case IcsIdle:
5506         /* We switched into a game mode where moves are not accepted,
5507            perhaps while the mouse button was down. */
5508         return ImpossibleMove;
5509
5510       case MachinePlaysWhite:
5511         /* User is moving for Black */
5512         if (WhiteOnMove(currentMove)) {
5513             DisplayMoveError(_("It is White's turn"));
5514             return ImpossibleMove;
5515         }
5516         break;
5517
5518       case MachinePlaysBlack:
5519         /* User is moving for White */
5520         if (!WhiteOnMove(currentMove)) {
5521             DisplayMoveError(_("It is Black's turn"));
5522             return ImpossibleMove;
5523         }
5524         break;
5525
5526       case EditGame:
5527       case IcsExamining:
5528       case BeginningOfGame:
5529       case AnalyzeMode:
5530       case Training:
5531         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5532             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5533             /* User is moving for Black */
5534             if (WhiteOnMove(currentMove)) {
5535                 DisplayMoveError(_("It is White's turn"));
5536                 return ImpossibleMove;
5537             }
5538         } else {
5539             /* User is moving for White */
5540             if (!WhiteOnMove(currentMove)) {
5541                 DisplayMoveError(_("It is Black's turn"));
5542                 return ImpossibleMove;
5543             }
5544         }
5545         break;
5546
5547       case IcsPlayingBlack:
5548         /* User is moving for Black */
5549         if (WhiteOnMove(currentMove)) {
5550             if (!appData.premove) {
5551                 DisplayMoveError(_("It is White's turn"));
5552             } else if (toX >= 0 && toY >= 0) {
5553                 premoveToX = toX;
5554                 premoveToY = toY;
5555                 premoveFromX = fromX;
5556                 premoveFromY = fromY;
5557                 premovePromoChar = promoChar;
5558                 gotPremove = 1;
5559                 if (appData.debugMode) 
5560                     fprintf(debugFP, "Got premove: fromX %d,"
5561                             "fromY %d, toX %d, toY %d\n",
5562                             fromX, fromY, toX, toY);
5563             }
5564             return ImpossibleMove;
5565         }
5566         break;
5567
5568       case IcsPlayingWhite:
5569         /* User is moving for White */
5570         if (!WhiteOnMove(currentMove)) {
5571             if (!appData.premove) {
5572                 DisplayMoveError(_("It is Black's turn"));
5573             } else if (toX >= 0 && toY >= 0) {
5574                 premoveToX = toX;
5575                 premoveToY = toY;
5576                 premoveFromX = fromX;
5577                 premoveFromY = fromY;
5578                 premovePromoChar = promoChar;
5579                 gotPremove = 1;
5580                 if (appData.debugMode) 
5581                     fprintf(debugFP, "Got premove: fromX %d,"
5582                             "fromY %d, toX %d, toY %d\n",
5583                             fromX, fromY, toX, toY);
5584             }
5585             return ImpossibleMove;
5586         }
5587         break;
5588
5589       default:
5590         break;
5591
5592       case EditPosition:
5593         /* EditPosition, empty square, or different color piece;
5594            click-click move is possible */
5595         if (toX == -2 || toY == -2) {
5596             boards[0][fromY][fromX] = EmptySquare;
5597             return AmbiguousMove;
5598         } else if (toX >= 0 && toY >= 0) {
5599             boards[0][toY][toX] = boards[0][fromY][fromX];
5600             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5601                 if(boards[0][fromY][0] != EmptySquare) {
5602                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5603                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5604                 }
5605             } else
5606             if(fromX == BOARD_RGHT+1) {
5607                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5608                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5609                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5610                 }
5611             } else
5612             boards[0][fromY][fromX] = EmptySquare;
5613             return AmbiguousMove;
5614         }
5615         return ImpossibleMove;
5616     }
5617
5618     if(toX < 0 || toY < 0) return ImpossibleMove;
5619     pdown = boards[currentMove][fromY][fromX];
5620     pup = boards[currentMove][toY][toX];
5621
5622     /* [HGM] If move started in holdings, it means a drop */
5623     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5624          if( pup != EmptySquare ) return ImpossibleMove;
5625          if(appData.testLegality) {
5626              /* it would be more logical if LegalityTest() also figured out
5627               * which drops are legal. For now we forbid pawns on back rank.
5628               * Shogi is on its own here...
5629               */
5630              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5631                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5632                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5633          }
5634          return WhiteDrop; /* Not needed to specify white or black yet */
5635     }
5636
5637     /* [HGM] always test for legality, to get promotion info */
5638     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5639                                          fromY, fromX, toY, toX, promoChar);
5640     /* [HGM] but possibly ignore an IllegalMove result */
5641     if (appData.testLegality) {
5642         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5643             DisplayMoveError(_("Illegal move"));
5644             return ImpossibleMove;
5645         }
5646     }
5647
5648     return moveType;
5649     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5650        function is made into one that returns an OK move type if FinishMove
5651        should be called. This to give the calling driver routine the
5652        opportunity to finish the userMove input with a promotion popup,
5653        without bothering the user with this for invalid or illegal moves */
5654
5655 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5656 }
5657
5658 /* Common tail of UserMoveEvent and DropMenuEvent */
5659 int
5660 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5661      ChessMove moveType;
5662      int fromX, fromY, toX, toY;
5663      /*char*/int promoChar;
5664 {
5665     char *bookHit = 0;
5666
5667     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5668         // [HGM] superchess: suppress promotions to non-available piece
5669         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5670         if(WhiteOnMove(currentMove)) {
5671             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5672         } else {
5673             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5674         }
5675     }
5676
5677     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5678        move type in caller when we know the move is a legal promotion */
5679     if(moveType == NormalMove && promoChar)
5680         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5681
5682     /* [HGM] convert drag-and-drop piece drops to standard form */
5683     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5684          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5685            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5686                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5687            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5688            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5689            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5690            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5691          fromY = DROP_RANK;
5692     }
5693
5694     /* [HGM] <popupFix> The following if has been moved here from
5695        UserMoveEvent(). Because it seemed to belong here (why not allow
5696        piece drops in training games?), and because it can only be
5697        performed after it is known to what we promote. */
5698     if (gameMode == Training) {
5699       /* compare the move played on the board to the next move in the
5700        * game. If they match, display the move and the opponent's response. 
5701        * If they don't match, display an error message.
5702        */
5703       int saveAnimate;
5704       Board testBoard;
5705       CopyBoard(testBoard, boards[currentMove]);
5706       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5707
5708       if (CompareBoards(testBoard, boards[currentMove+1])) {
5709         ForwardInner(currentMove+1);
5710
5711         /* Autoplay the opponent's response.
5712          * if appData.animate was TRUE when Training mode was entered,
5713          * the response will be animated.
5714          */
5715         saveAnimate = appData.animate;
5716         appData.animate = animateTraining;
5717         ForwardInner(currentMove+1);
5718         appData.animate = saveAnimate;
5719
5720         /* check for the end of the game */
5721         if (currentMove >= forwardMostMove) {
5722           gameMode = PlayFromGameFile;
5723           ModeHighlight();
5724           SetTrainingModeOff();
5725           DisplayInformation(_("End of game"));
5726         }
5727       } else {
5728         DisplayError(_("Incorrect move"), 0);
5729       }
5730       return 1;
5731     }
5732
5733   /* Ok, now we know that the move is good, so we can kill
5734      the previous line in Analysis Mode */
5735   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5736                                 && currentMove < forwardMostMove) {
5737     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5738   }
5739
5740   /* If we need the chess program but it's dead, restart it */
5741   ResurrectChessProgram();
5742
5743   /* A user move restarts a paused game*/
5744   if (pausing)
5745     PauseEvent();
5746
5747   thinkOutput[0] = NULLCHAR;
5748
5749   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5750
5751   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5752
5753   if (gameMode == BeginningOfGame) {
5754     if (appData.noChessProgram) {
5755       gameMode = EditGame;
5756       SetGameInfo();
5757     } else {
5758       char buf[MSG_SIZ];
5759       gameMode = MachinePlaysBlack;
5760       StartClocks();
5761       SetGameInfo();
5762       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5763       DisplayTitle(buf);
5764       if (first.sendName) {
5765         sprintf(buf, "name %s\n", gameInfo.white);
5766         SendToProgram(buf, &first);
5767       }
5768       StartClocks();
5769     }
5770     ModeHighlight();
5771   }
5772
5773   /* Relay move to ICS or chess engine */
5774   if (appData.icsActive) {
5775     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5776         gameMode == IcsExamining) {
5777       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5778         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5779         SendToICS("draw ");
5780         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5781       }
5782       // also send plain move, in case ICS does not understand atomic claims
5783       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5784       ics_user_moved = 1;
5785     }
5786   } else {
5787     if (first.sendTime && (gameMode == BeginningOfGame ||
5788                            gameMode == MachinePlaysWhite ||
5789                            gameMode == MachinePlaysBlack)) {
5790       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5791     }
5792     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5793          // [HGM] book: if program might be playing, let it use book
5794         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5795         first.maybeThinking = TRUE;
5796     } else SendMoveToProgram(forwardMostMove-1, &first);
5797     if (currentMove == cmailOldMove + 1) {
5798       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5799     }
5800   }
5801
5802   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5803
5804   switch (gameMode) {
5805   case EditGame:
5806     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5807     case MT_NONE:
5808     case MT_CHECK:
5809       break;
5810     case MT_CHECKMATE:
5811     case MT_STAINMATE:
5812       if (WhiteOnMove(currentMove)) {
5813         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5814       } else {
5815         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5816       }
5817       break;
5818     case MT_STALEMATE:
5819       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5820       break;
5821     }
5822     break;
5823     
5824   case MachinePlaysBlack:
5825   case MachinePlaysWhite:
5826     /* disable certain menu options while machine is thinking */
5827     SetMachineThinkingEnables();
5828     break;
5829
5830   default:
5831     break;
5832   }
5833
5834   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5835         
5836   if(bookHit) { // [HGM] book: simulate book reply
5837         static char bookMove[MSG_SIZ]; // a bit generous?
5838
5839         programStats.nodes = programStats.depth = programStats.time = 
5840         programStats.score = programStats.got_only_move = 0;
5841         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5842
5843         strcpy(bookMove, "move ");
5844         strcat(bookMove, bookHit);
5845         HandleMachineMove(bookMove, &first);
5846   }
5847   return 1;
5848 }
5849
5850 void
5851 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5852      int fromX, fromY, toX, toY;
5853      int promoChar;
5854 {
5855     /* [HGM] This routine was added to allow calling of its two logical
5856        parts from other modules in the old way. Before, UserMoveEvent()
5857        automatically called FinishMove() if the move was OK, and returned
5858        otherwise. I separated the two, in order to make it possible to
5859        slip a promotion popup in between. But that it always needs two
5860        calls, to the first part, (now called UserMoveTest() ), and to
5861        FinishMove if the first part succeeded. Calls that do not need
5862        to do anything in between, can call this routine the old way. 
5863     */
5864     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5865 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5866     if(moveType == AmbiguousMove)
5867         DrawPosition(FALSE, boards[currentMove]);
5868     else if(moveType != ImpossibleMove && moveType != Comment)
5869         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5870 }
5871
5872 void
5873 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5874      Board board;
5875      int flags;
5876      ChessMove kind;
5877      int rf, ff, rt, ft;
5878      VOIDSTAR closure;
5879 {
5880     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5881     Markers *m = (Markers *) closure;
5882     if(rf == fromY && ff == fromX)
5883         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5884                          || kind == WhiteCapturesEnPassant
5885                          || kind == BlackCapturesEnPassant);
5886     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5887 }
5888
5889 void
5890 MarkTargetSquares(int clear)
5891 {
5892   int x, y;
5893   if(!appData.markers || !appData.highlightDragging || 
5894      !appData.testLegality || gameMode == EditPosition) return;
5895   if(clear) {
5896     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5897   } else {
5898     int capt = 0;
5899     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5900     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5901       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5902       if(capt)
5903       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5904     }
5905   }
5906   DrawPosition(TRUE, NULL);
5907 }
5908
5909 void LeftClick(ClickType clickType, int xPix, int yPix)
5910 {
5911     int x, y;
5912     Boolean saveAnimate;
5913     static int second = 0, promotionChoice = 0;
5914     char promoChoice = NULLCHAR;
5915
5916     if(appData.seekGraph && appData.icsActive && 
5917         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5918         SeekGraphClick(clickType, xPix, yPix, FALSE);
5919         return;
5920     }
5921
5922     if (clickType == Press) ErrorPopDown();
5923     MarkTargetSquares(1);
5924
5925     x = EventToSquare(xPix, BOARD_WIDTH);
5926     y = EventToSquare(yPix, BOARD_HEIGHT);
5927     if (!flipView && y >= 0) {
5928         y = BOARD_HEIGHT - 1 - y;
5929     }
5930     if (flipView && x >= 0) {
5931         x = BOARD_WIDTH - 1 - x;
5932     }
5933
5934     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5935         if(clickType == Release) return; // ignore upclick of click-click destination
5936         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5937         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5938         if(gameInfo.holdingsWidth && 
5939                 (WhiteOnMove(currentMove) 
5940                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5941                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5942             // click in right holdings, for determining promotion piece
5943             ChessSquare p = boards[currentMove][y][x];
5944             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5945             if(p != EmptySquare) {
5946                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5947                 fromX = fromY = -1;
5948                 return;
5949             }
5950         }
5951         DrawPosition(FALSE, boards[currentMove]);
5952         return;
5953     }
5954
5955     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5956     if(clickType == Press
5957             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5958               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5959               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5960         return;
5961
5962     if (fromX == -1) {
5963         if (clickType == Press) {
5964             /* First square */
5965             if (OKToStartUserMove(x, y)) {
5966                 fromX = x;
5967                 fromY = y;
5968                 second = 0;
5969                 MarkTargetSquares(0);
5970                 DragPieceBegin(xPix, yPix);
5971                 if (appData.highlightDragging) {
5972                     SetHighlights(x, y, -1, -1);
5973                 }
5974             }
5975         }
5976         return;
5977     }
5978
5979     /* fromX != -1 */
5980     if (clickType == Press && gameMode != EditPosition) {
5981         ChessSquare fromP;
5982         ChessSquare toP;
5983         int frc;
5984
5985         // ignore off-board to clicks
5986         if(y < 0 || x < 0) return;
5987
5988         /* Check if clicking again on the same color piece */
5989         fromP = boards[currentMove][fromY][fromX];
5990         toP = boards[currentMove][y][x];
5991         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5992         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5993              WhitePawn <= toP && toP <= WhiteKing &&
5994              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5995              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5996             (BlackPawn <= fromP && fromP <= BlackKing && 
5997              BlackPawn <= toP && toP <= BlackKing &&
5998              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5999              !(fromP == BlackKing && toP == BlackRook && frc))) {
6000             /* Clicked again on same color piece -- changed his mind */
6001             second = (x == fromX && y == fromY);
6002             if (appData.highlightDragging) {
6003                 SetHighlights(x, y, -1, -1);
6004             } else {
6005                 ClearHighlights();
6006             }
6007             if (OKToStartUserMove(x, y)) {
6008                 fromX = x;
6009                 fromY = y;
6010                 MarkTargetSquares(0);
6011                 DragPieceBegin(xPix, yPix);
6012             }
6013             return;
6014         }
6015         // ignore clicks on holdings
6016         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6017     }
6018
6019     if (clickType == Release && x == fromX && y == fromY) {
6020         DragPieceEnd(xPix, yPix);
6021         if (appData.animateDragging) {
6022             /* Undo animation damage if any */
6023             DrawPosition(FALSE, NULL);
6024         }
6025         if (second) {
6026             /* Second up/down in same square; just abort move */
6027             second = 0;
6028             fromX = fromY = -1;
6029             ClearHighlights();
6030             gotPremove = 0;
6031             ClearPremoveHighlights();
6032         } else {
6033             /* First upclick in same square; start click-click mode */
6034             SetHighlights(x, y, -1, -1);
6035         }
6036         return;
6037     }
6038
6039     /* we now have a different from- and (possibly off-board) to-square */
6040     /* Completed move */
6041     toX = x;
6042     toY = y;
6043     saveAnimate = appData.animate;
6044     if (clickType == Press) {
6045         /* Finish clickclick move */
6046         if (appData.animate || appData.highlightLastMove) {
6047             SetHighlights(fromX, fromY, toX, toY);
6048         } else {
6049             ClearHighlights();
6050         }
6051     } else {
6052         /* Finish drag move */
6053         if (appData.highlightLastMove) {
6054             SetHighlights(fromX, fromY, toX, toY);
6055         } else {
6056             ClearHighlights();
6057         }
6058         DragPieceEnd(xPix, yPix);
6059         /* Don't animate move and drag both */
6060         appData.animate = FALSE;
6061     }
6062
6063     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6064     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6065         ChessSquare piece = boards[currentMove][fromY][fromX];
6066         if(gameMode == EditPosition && piece != EmptySquare &&
6067            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6068             int n;
6069              
6070             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6071                 n = PieceToNumber(piece - (int)BlackPawn);
6072                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6073                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6074                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6075             } else
6076             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6077                 n = PieceToNumber(piece);
6078                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6079                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6080                 boards[currentMove][n][BOARD_WIDTH-2]++;
6081             }
6082             boards[currentMove][fromY][fromX] = EmptySquare;
6083         }
6084         ClearHighlights();
6085         fromX = fromY = -1;
6086         DrawPosition(TRUE, boards[currentMove]);
6087         return;
6088     }
6089
6090     // off-board moves should not be highlighted
6091     if(x < 0 || x < 0) ClearHighlights();
6092
6093     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6094         SetHighlights(fromX, fromY, toX, toY);
6095         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6096             // [HGM] super: promotion to captured piece selected from holdings
6097             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6098             promotionChoice = TRUE;
6099             // kludge follows to temporarily execute move on display, without promoting yet
6100             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6101             boards[currentMove][toY][toX] = p;
6102             DrawPosition(FALSE, boards[currentMove]);
6103             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6104             boards[currentMove][toY][toX] = q;
6105             DisplayMessage("Click in holdings to choose piece", "");
6106             return;
6107         }
6108         PromotionPopUp();
6109     } else {
6110         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6111         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6112         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6113         fromX = fromY = -1;
6114     }
6115     appData.animate = saveAnimate;
6116     if (appData.animate || appData.animateDragging) {
6117         /* Undo animation damage if needed */
6118         DrawPosition(FALSE, NULL);
6119     }
6120 }
6121
6122 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6123 {   // front-end-free part taken out of PieceMenuPopup
6124     int whichMenu; int xSqr, ySqr;
6125
6126     xSqr = EventToSquare(x, BOARD_WIDTH);
6127     ySqr = EventToSquare(y, BOARD_HEIGHT);
6128     if (action == Release) UnLoadPV(); // [HGM] pv
6129     if (action != Press) return -2;
6130     switch (gameMode) {
6131       case IcsExamining:
6132         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6133       case EditPosition:
6134         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6135         if (xSqr < 0 || ySqr < 0) return -1;\r
6136         whichMenu = 0; // edit-position menu
6137         break;
6138       case IcsObserving:
6139         if(!appData.icsEngineAnalyze) return -1;
6140       case IcsPlayingWhite:
6141       case IcsPlayingBlack:
6142         if(!appData.zippyPlay) goto noZip;
6143       case AnalyzeMode:
6144       case AnalyzeFile:
6145       case MachinePlaysWhite:
6146       case MachinePlaysBlack:
6147       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6148         if (!appData.dropMenu) {
6149           LoadPV(x, y);
6150           return 2; // flag front-end to grab mouse events
6151         }
6152         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6153            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6154       case EditGame:
6155       noZip:
6156         if (xSqr < 0 || ySqr < 0) return -1;
6157         if (!appData.dropMenu || appData.testLegality &&
6158             gameInfo.variant != VariantBughouse &&
6159             gameInfo.variant != VariantCrazyhouse) return -1;
6160         whichMenu = 1; // drop menu
6161         break;
6162       default:
6163         return -1;
6164     }
6165
6166     if (((*fromX = xSqr) < 0) ||
6167         ((*fromY = ySqr) < 0)) {
6168         *fromX = *fromY = -1;
6169         return -1;
6170     }
6171     if (flipView)
6172       *fromX = BOARD_WIDTH - 1 - *fromX;
6173     else
6174       *fromY = BOARD_HEIGHT - 1 - *fromY;
6175
6176     return whichMenu;
6177 }
6178
6179 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6180 {
6181 //    char * hint = lastHint;
6182     FrontEndProgramStats stats;
6183
6184     stats.which = cps == &first ? 0 : 1;
6185     stats.depth = cpstats->depth;
6186     stats.nodes = cpstats->nodes;
6187     stats.score = cpstats->score;
6188     stats.time = cpstats->time;
6189     stats.pv = cpstats->movelist;
6190     stats.hint = lastHint;
6191     stats.an_move_index = 0;
6192     stats.an_move_count = 0;
6193
6194     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6195         stats.hint = cpstats->move_name;
6196         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6197         stats.an_move_count = cpstats->nr_moves;
6198     }
6199
6200     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6201
6202     SetProgramStats( &stats );
6203 }
6204
6205 int
6206 Adjudicate(ChessProgramState *cps)
6207 {       // [HGM] some adjudications useful with buggy engines
6208         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6209         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6210         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6211         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6212         int k, count = 0; static int bare = 1;
6213         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6214         Boolean canAdjudicate = !appData.icsActive;
6215
6216         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6217         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6218             if( appData.testLegality )
6219             {   /* [HGM] Some more adjudications for obstinate engines */
6220                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6221                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6222                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6223                 static int moveCount = 6;
6224                 ChessMove result;
6225                 char *reason = NULL;
6226
6227                 /* Count what is on board. */
6228                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6229                 {   ChessSquare p = boards[forwardMostMove][i][j];
6230                     int m=i;
6231
6232                     switch((int) p)
6233                     {   /* count B,N,R and other of each side */
6234                         case WhiteKing:
6235                         case BlackKing:
6236                              NrK++; break; // [HGM] atomic: count Kings
6237                         case WhiteKnight:
6238                              NrWN++; break;
6239                         case WhiteBishop:
6240                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6241                              bishopsColor |= 1 << ((i^j)&1);
6242                              NrWB++; break;
6243                         case BlackKnight:
6244                              NrBN++; break;
6245                         case BlackBishop:
6246                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6247                              bishopsColor |= 1 << ((i^j)&1);
6248                              NrBB++; break;
6249                         case WhiteRook:
6250                              NrWR++; break;
6251                         case BlackRook:
6252                              NrBR++; break;
6253                         case WhiteQueen:
6254                              NrWQ++; break;
6255                         case BlackQueen:
6256                              NrBQ++; break;
6257                         case EmptySquare: 
6258                              break;
6259                         case BlackPawn:
6260                              m = 7-i;
6261                         case WhitePawn:
6262                              PawnAdvance += m; NrPawns++;
6263                     }
6264                     NrPieces += (p != EmptySquare);
6265                     NrW += ((int)p < (int)BlackPawn);
6266                     if(gameInfo.variant == VariantXiangqi && 
6267                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6268                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6269                         NrW -= ((int)p < (int)BlackPawn);
6270                     }
6271                 }
6272
6273                 /* Some material-based adjudications that have to be made before stalemate test */
6274                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6275                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6276                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6277                      if(canAdjudicate && appData.checkMates) {
6278                          if(engineOpponent)
6279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6280                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6281                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6282                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6283                          return 1;
6284                      }
6285                 }
6286
6287                 /* Bare King in Shatranj (loses) or Losers (wins) */
6288                 if( NrW == 1 || NrPieces - NrW == 1) {
6289                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6290                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6291                      if(canAdjudicate && appData.checkMates) {
6292                          if(engineOpponent)
6293                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6294                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6295                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6296                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6297                          return 1;
6298                      }
6299                   } else
6300                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6301                   {    /* bare King */
6302                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6303                         if(canAdjudicate && appData.checkMates) {
6304                             /* but only adjudicate if adjudication enabled */
6305                             if(engineOpponent)
6306                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6307                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6308                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6309                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6310                             return 1;
6311                         }
6312                   }
6313                 } else bare = 1;
6314
6315
6316             // don't wait for engine to announce game end if we can judge ourselves
6317             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6318               case MT_CHECK:
6319                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6320                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6321                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6322                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6323                             checkCnt++;
6324                         if(checkCnt >= 2) {
6325                             reason = "Xboard adjudication: 3rd check";
6326                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6327                             break;
6328                         }
6329                     }
6330                 }
6331               case MT_NONE:
6332               default:
6333                 break;
6334               case MT_STALEMATE:
6335               case MT_STAINMATE:
6336                 reason = "Xboard adjudication: Stalemate";
6337                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6338                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6339                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6340                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6341                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6342                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6343                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6344                                                                         EP_CHECKMATE : EP_WINS);
6345                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6346                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6347                 }
6348                 break;
6349               case MT_CHECKMATE:
6350                 reason = "Xboard adjudication: Checkmate";
6351                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6352                 break;
6353             }
6354
6355                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6356                     case EP_STALEMATE:
6357                         result = GameIsDrawn; break;
6358                     case EP_CHECKMATE:
6359                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6360                     case EP_WINS:
6361                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6362                     default:
6363                         result = (ChessMove) 0;
6364                 }
6365                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6366                     if(engineOpponent)
6367                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6368                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369                     GameEnds( result, reason, GE_XBOARD );
6370                     return 1;
6371                 }
6372
6373                 /* Next absolutely insufficient mating material. */
6374                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6375                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6376                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6377                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6378                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6379
6380                      /* always flag draws, for judging claims */
6381                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6382
6383                      if(canAdjudicate && appData.materialDraws) {
6384                          /* but only adjudicate them if adjudication enabled */
6385                          if(engineOpponent) {
6386                            SendToProgram("force\n", engineOpponent); // suppress reply
6387                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6388                          }
6389                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6390                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6391                          return 1;
6392                      }
6393                 }
6394
6395                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6396                 if(NrPieces == 4 && 
6397                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6398                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6399                    || NrWN==2 || NrBN==2     /* KNNK */
6400                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6401                   ) ) {
6402                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6403                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6404                           if(engineOpponent) {
6405                             SendToProgram("force\n", engineOpponent); // suppress reply
6406                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6407                           }
6408                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6409                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6410                           return 1;
6411                      }
6412                 } else moveCount = 6;
6413             }
6414         }
6415           
6416         if (appData.debugMode) { int i;
6417             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6418                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6419                     appData.drawRepeats);
6420             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6421               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6422             
6423         }
6424
6425         // Repetition draws and 50-move rule can be applied independently of legality testing
6426
6427                 /* Check for rep-draws */
6428                 count = 0;
6429                 for(k = forwardMostMove-2;
6430                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6431                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6432                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6433                     k-=2)
6434                 {   int rights=0;
6435                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6436                         /* compare castling rights */
6437                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6438                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6439                                 rights++; /* King lost rights, while rook still had them */
6440                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6441                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6442                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6443                                    rights++; /* but at least one rook lost them */
6444                         }
6445                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6446                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6447                                 rights++; 
6448                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6449                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6450                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6451                                    rights++;
6452                         }
6453                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6454                             && appData.drawRepeats > 1) {
6455                              /* adjudicate after user-specified nr of repeats */
6456                              if(engineOpponent) {
6457                                SendToProgram("force\n", engineOpponent); // suppress reply
6458                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6459                              }
6460                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6461                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6462                                 // [HGM] xiangqi: check for forbidden perpetuals
6463                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6464                                 for(m=forwardMostMove; m>k; m-=2) {
6465                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6466                                         ourPerpetual = 0; // the current mover did not always check
6467                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6468                                         hisPerpetual = 0; // the opponent did not always check
6469                                 }
6470                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6471                                                                         ourPerpetual, hisPerpetual);
6472                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6473                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6474                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6475                                     return 1;
6476                                 }
6477                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6478                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6479                                 // Now check for perpetual chases
6480                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6481                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6482                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6483                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6484                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6485                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6486                                         return 1;
6487                                     }
6488                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6489                                         break; // Abort repetition-checking loop.
6490                                 }
6491                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6492                              }
6493                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6494                              return 1;
6495                         }
6496                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6497                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6498                     }
6499                 }
6500
6501                 /* Now we test for 50-move draws. Determine ply count */
6502                 count = forwardMostMove;
6503                 /* look for last irreversble move */
6504                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6505                     count--;
6506                 /* if we hit starting position, add initial plies */
6507                 if( count == backwardMostMove )
6508                     count -= initialRulePlies;
6509                 count = forwardMostMove - count; 
6510                 if( count >= 100)
6511                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6512                          /* this is used to judge if draw claims are legal */
6513                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6514                          if(engineOpponent) {
6515                            SendToProgram("force\n", engineOpponent); // suppress reply
6516                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6517                          }
6518                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6519                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6520                          return 1;
6521                 }
6522
6523                 /* if draw offer is pending, treat it as a draw claim
6524                  * when draw condition present, to allow engines a way to
6525                  * claim draws before making their move to avoid a race
6526                  * condition occurring after their move
6527                  */
6528                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6529                          char *p = NULL;
6530                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6531                              p = "Draw claim: 50-move rule";
6532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6533                              p = "Draw claim: 3-fold repetition";
6534                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6535                              p = "Draw claim: insufficient mating material";
6536                          if( p != NULL && canAdjudicate) {
6537                              if(engineOpponent) {
6538                                SendToProgram("force\n", engineOpponent); // suppress reply
6539                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6540                              }
6541                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6542                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6543                              return 1;
6544                          }
6545                 }
6546
6547                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6548                     if(engineOpponent) {
6549                       SendToProgram("force\n", engineOpponent); // suppress reply
6550                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6551                     }
6552                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6553                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6554                     return 1;
6555                 }
6556         return 0;
6557 }
6558
6559 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6560 {   // [HGM] book: this routine intercepts moves to simulate book replies
6561     char *bookHit = NULL;
6562
6563     //first determine if the incoming move brings opponent into his book
6564     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6565         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6566     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6567     if(bookHit != NULL && !cps->bookSuspend) {
6568         // make sure opponent is not going to reply after receiving move to book position
6569         SendToProgram("force\n", cps);
6570         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6571     }
6572     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6573     // now arrange restart after book miss
6574     if(bookHit) {
6575         // after a book hit we never send 'go', and the code after the call to this routine
6576         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6577         char buf[MSG_SIZ];
6578         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6579         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6580         SendToProgram(buf, cps);
6581         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6582     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6583         SendToProgram("go\n", cps);
6584         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6585     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6586         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6587             SendToProgram("go\n", cps); 
6588         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6589     }
6590     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6591 }
6592
6593 char *savedMessage;
6594 ChessProgramState *savedState;
6595 void DeferredBookMove(void)
6596 {
6597         if(savedState->lastPing != savedState->lastPong)
6598                     ScheduleDelayedEvent(DeferredBookMove, 10);
6599         else
6600         HandleMachineMove(savedMessage, savedState);
6601 }
6602
6603 void
6604 HandleMachineMove(message, cps)
6605      char *message;
6606      ChessProgramState *cps;
6607 {
6608     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6609     char realname[MSG_SIZ];
6610     int fromX, fromY, toX, toY;
6611     ChessMove moveType;
6612     char promoChar;
6613     char *p;
6614     int machineWhite;
6615     char *bookHit;
6616
6617     cps->userError = 0;
6618
6619 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6620     /*
6621      * Kludge to ignore BEL characters
6622      */
6623     while (*message == '\007') message++;
6624
6625     /*
6626      * [HGM] engine debug message: ignore lines starting with '#' character
6627      */
6628     if(cps->debug && *message == '#') return;
6629
6630     /*
6631      * Look for book output
6632      */
6633     if (cps == &first && bookRequested) {
6634         if (message[0] == '\t' || message[0] == ' ') {
6635             /* Part of the book output is here; append it */
6636             strcat(bookOutput, message);
6637             strcat(bookOutput, "  \n");
6638             return;
6639         } else if (bookOutput[0] != NULLCHAR) {
6640             /* All of book output has arrived; display it */
6641             char *p = bookOutput;
6642             while (*p != NULLCHAR) {
6643                 if (*p == '\t') *p = ' ';
6644                 p++;
6645             }
6646             DisplayInformation(bookOutput);
6647             bookRequested = FALSE;
6648             /* Fall through to parse the current output */
6649         }
6650     }
6651
6652     /*
6653      * Look for machine move.
6654      */
6655     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6656         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6657     {
6658         /* This method is only useful on engines that support ping */
6659         if (cps->lastPing != cps->lastPong) {
6660           if (gameMode == BeginningOfGame) {
6661             /* Extra move from before last new; ignore */
6662             if (appData.debugMode) {
6663                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6664             }
6665           } else {
6666             if (appData.debugMode) {
6667                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6668                         cps->which, gameMode);
6669             }
6670
6671             SendToProgram("undo\n", cps);
6672           }
6673           return;
6674         }
6675
6676         switch (gameMode) {
6677           case BeginningOfGame:
6678             /* Extra move from before last reset; ignore */
6679             if (appData.debugMode) {
6680                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6681             }
6682             return;
6683
6684           case EndOfGame:
6685           case IcsIdle:
6686           default:
6687             /* Extra move after we tried to stop.  The mode test is
6688                not a reliable way of detecting this problem, but it's
6689                the best we can do on engines that don't support ping.
6690             */
6691             if (appData.debugMode) {
6692                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6693                         cps->which, gameMode);
6694             }
6695             SendToProgram("undo\n", cps);
6696             return;
6697
6698           case MachinePlaysWhite:
6699           case IcsPlayingWhite:
6700             machineWhite = TRUE;
6701             break;
6702
6703           case MachinePlaysBlack:
6704           case IcsPlayingBlack:
6705             machineWhite = FALSE;
6706             break;
6707
6708           case TwoMachinesPlay:
6709             machineWhite = (cps->twoMachinesColor[0] == 'w');
6710             break;
6711         }
6712         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6713             if (appData.debugMode) {
6714                 fprintf(debugFP,
6715                         "Ignoring move out of turn by %s, gameMode %d"
6716                         ", forwardMost %d\n",
6717                         cps->which, gameMode, forwardMostMove);
6718             }
6719             return;
6720         }
6721
6722     if (appData.debugMode) { int f = forwardMostMove;
6723         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6724                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6725                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6726     }
6727         if(cps->alphaRank) AlphaRank(machineMove, 4);
6728         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6729                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6730             /* Machine move could not be parsed; ignore it. */
6731             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6732                     machineMove, cps->which);
6733             DisplayError(buf1, 0);
6734             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6735                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6736             if (gameMode == TwoMachinesPlay) {
6737               GameEnds(machineWhite ? BlackWins : WhiteWins,
6738                        buf1, GE_XBOARD);
6739             }
6740             return;
6741         }
6742
6743         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6744         /* So we have to redo legality test with true e.p. status here,  */
6745         /* to make sure an illegal e.p. capture does not slip through,   */
6746         /* to cause a forfeit on a justified illegal-move complaint      */
6747         /* of the opponent.                                              */
6748         if( gameMode==TwoMachinesPlay && appData.testLegality
6749             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6750                                                               ) {
6751            ChessMove moveType;
6752            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6753                              fromY, fromX, toY, toX, promoChar);
6754             if (appData.debugMode) {
6755                 int i;
6756                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6757                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6758                 fprintf(debugFP, "castling rights\n");
6759             }
6760             if(moveType == IllegalMove) {
6761                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6762                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6763                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6764                            buf1, GE_XBOARD);
6765                 return;
6766            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6767            /* [HGM] Kludge to handle engines that send FRC-style castling
6768               when they shouldn't (like TSCP-Gothic) */
6769            switch(moveType) {
6770              case WhiteASideCastleFR:
6771              case BlackASideCastleFR:
6772                toX+=2;
6773                currentMoveString[2]++;
6774                break;
6775              case WhiteHSideCastleFR:
6776              case BlackHSideCastleFR:
6777                toX--;
6778                currentMoveString[2]--;
6779                break;
6780              default: ; // nothing to do, but suppresses warning of pedantic compilers
6781            }
6782         }
6783         hintRequested = FALSE;
6784         lastHint[0] = NULLCHAR;
6785         bookRequested = FALSE;
6786         /* Program may be pondering now */
6787         cps->maybeThinking = TRUE;
6788         if (cps->sendTime == 2) cps->sendTime = 1;
6789         if (cps->offeredDraw) cps->offeredDraw--;
6790
6791         /* currentMoveString is set as a side-effect of ParseOneMove */
6792         strcpy(machineMove, currentMoveString);
6793         strcat(machineMove, "\n");
6794         strcpy(moveList[forwardMostMove], machineMove);
6795
6796         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6797
6798         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6799         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6800             int count = 0;
6801
6802             while( count < adjudicateLossPlies ) {
6803                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6804
6805                 if( count & 1 ) {
6806                     score = -score; /* Flip score for winning side */
6807                 }
6808
6809                 if( score > adjudicateLossThreshold ) {
6810                     break;
6811                 }
6812
6813                 count++;
6814             }
6815
6816             if( count >= adjudicateLossPlies ) {
6817                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6818
6819                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6820                     "Xboard adjudication", 
6821                     GE_XBOARD );
6822
6823                 return;
6824             }
6825         }
6826
6827         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6828
6829 #if ZIPPY
6830         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6831             first.initDone) {
6832           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6833                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6834                 SendToICS("draw ");
6835                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6836           }
6837           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6838           ics_user_moved = 1;
6839           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6840                 char buf[3*MSG_SIZ];
6841
6842                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6843                         programStats.score / 100.,
6844                         programStats.depth,
6845                         programStats.time / 100.,
6846                         (unsigned int)programStats.nodes,
6847                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6848                         programStats.movelist);
6849                 SendToICS(buf);
6850 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6851           }
6852         }
6853 #endif
6854
6855         /* [AS] Save move info and clear stats for next move */
6856         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6857         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6858         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6859         ClearProgramStats();
6860         thinkOutput[0] = NULLCHAR;
6861         hiddenThinkOutputState = 0;
6862
6863         bookHit = NULL;
6864         if (gameMode == TwoMachinesPlay) {
6865             /* [HGM] relaying draw offers moved to after reception of move */
6866             /* and interpreting offer as claim if it brings draw condition */
6867             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6868                 SendToProgram("draw\n", cps->other);
6869             }
6870             if (cps->other->sendTime) {
6871                 SendTimeRemaining(cps->other,
6872                                   cps->other->twoMachinesColor[0] == 'w');
6873             }
6874             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6875             if (firstMove && !bookHit) {
6876                 firstMove = FALSE;
6877                 if (cps->other->useColors) {
6878                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6879                 }
6880                 SendToProgram("go\n", cps->other);
6881             }
6882             cps->other->maybeThinking = TRUE;
6883         }
6884
6885         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6886         
6887         if (!pausing && appData.ringBellAfterMoves) {
6888             RingBell();
6889         }
6890
6891         /* 
6892          * Reenable menu items that were disabled while
6893          * machine was thinking
6894          */
6895         if (gameMode != TwoMachinesPlay)
6896             SetUserThinkingEnables();
6897
6898         // [HGM] book: after book hit opponent has received move and is now in force mode
6899         // force the book reply into it, and then fake that it outputted this move by jumping
6900         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6901         if(bookHit) {
6902                 static char bookMove[MSG_SIZ]; // a bit generous?
6903
6904                 strcpy(bookMove, "move ");
6905                 strcat(bookMove, bookHit);
6906                 message = bookMove;
6907                 cps = cps->other;
6908                 programStats.nodes = programStats.depth = programStats.time = 
6909                 programStats.score = programStats.got_only_move = 0;
6910                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6911
6912                 if(cps->lastPing != cps->lastPong) {
6913                     savedMessage = message; // args for deferred call
6914                     savedState = cps;
6915                     ScheduleDelayedEvent(DeferredBookMove, 10);
6916                     return;
6917                 }
6918                 goto FakeBookMove;
6919         }
6920
6921         return;
6922     }
6923
6924     /* Set special modes for chess engines.  Later something general
6925      *  could be added here; for now there is just one kludge feature,
6926      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6927      *  when "xboard" is given as an interactive command.
6928      */
6929     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6930         cps->useSigint = FALSE;
6931         cps->useSigterm = FALSE;
6932     }
6933     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6934       ParseFeatures(message+8, cps);
6935       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6936     }
6937
6938     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6939      * want this, I was asked to put it in, and obliged.
6940      */
6941     if (!strncmp(message, "setboard ", 9)) {
6942         Board initial_position;
6943
6944         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6945
6946         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6947             DisplayError(_("Bad FEN received from engine"), 0);
6948             return ;
6949         } else {
6950            Reset(TRUE, FALSE);
6951            CopyBoard(boards[0], initial_position);
6952            initialRulePlies = FENrulePlies;
6953            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6954            else gameMode = MachinePlaysBlack;                 
6955            DrawPosition(FALSE, boards[currentMove]);
6956         }
6957         return;
6958     }
6959
6960     /*
6961      * Look for communication commands
6962      */
6963     if (!strncmp(message, "telluser ", 9)) {
6964         DisplayNote(message + 9);
6965         return;
6966     }
6967     if (!strncmp(message, "tellusererror ", 14)) {
6968         cps->userError = 1;
6969         DisplayError(message + 14, 0);
6970         return;
6971     }
6972     if (!strncmp(message, "tellopponent ", 13)) {
6973       if (appData.icsActive) {
6974         if (loggedOn) {
6975           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6976           SendToICS(buf1);
6977         }
6978       } else {
6979         DisplayNote(message + 13);
6980       }
6981       return;
6982     }
6983     if (!strncmp(message, "tellothers ", 11)) {
6984       if (appData.icsActive) {
6985         if (loggedOn) {
6986           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6987           SendToICS(buf1);
6988         }
6989       }
6990       return;
6991     }
6992     if (!strncmp(message, "tellall ", 8)) {
6993       if (appData.icsActive) {
6994         if (loggedOn) {
6995           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6996           SendToICS(buf1);
6997         }
6998       } else {
6999         DisplayNote(message + 8);
7000       }
7001       return;
7002     }
7003     if (strncmp(message, "warning", 7) == 0) {
7004         /* Undocumented feature, use tellusererror in new code */
7005         DisplayError(message, 0);
7006         return;
7007     }
7008     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7009         strcpy(realname, cps->tidy);
7010         strcat(realname, " query");
7011         AskQuestion(realname, buf2, buf1, cps->pr);
7012         return;
7013     }
7014     /* Commands from the engine directly to ICS.  We don't allow these to be 
7015      *  sent until we are logged on. Crafty kibitzes have been known to 
7016      *  interfere with the login process.
7017      */
7018     if (loggedOn) {
7019         if (!strncmp(message, "tellics ", 8)) {
7020             SendToICS(message + 8);
7021             SendToICS("\n");
7022             return;
7023         }
7024         if (!strncmp(message, "tellicsnoalias ", 15)) {
7025             SendToICS(ics_prefix);
7026             SendToICS(message + 15);
7027             SendToICS("\n");
7028             return;
7029         }
7030         /* The following are for backward compatibility only */
7031         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7032             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7033             SendToICS(ics_prefix);
7034             SendToICS(message);
7035             SendToICS("\n");
7036             return;
7037         }
7038     }
7039     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7040         return;
7041     }
7042     /*
7043      * If the move is illegal, cancel it and redraw the board.
7044      * Also deal with other error cases.  Matching is rather loose
7045      * here to accommodate engines written before the spec.
7046      */
7047     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7048         strncmp(message, "Error", 5) == 0) {
7049         if (StrStr(message, "name") || 
7050             StrStr(message, "rating") || StrStr(message, "?") ||
7051             StrStr(message, "result") || StrStr(message, "board") ||
7052             StrStr(message, "bk") || StrStr(message, "computer") ||
7053             StrStr(message, "variant") || StrStr(message, "hint") ||
7054             StrStr(message, "random") || StrStr(message, "depth") ||
7055             StrStr(message, "accepted")) {
7056             return;
7057         }
7058         if (StrStr(message, "protover")) {
7059           /* Program is responding to input, so it's apparently done
7060              initializing, and this error message indicates it is
7061              protocol version 1.  So we don't need to wait any longer
7062              for it to initialize and send feature commands. */
7063           FeatureDone(cps, 1);
7064           cps->protocolVersion = 1;
7065           return;
7066         }
7067         cps->maybeThinking = FALSE;
7068
7069         if (StrStr(message, "draw")) {
7070             /* Program doesn't have "draw" command */
7071             cps->sendDrawOffers = 0;
7072             return;
7073         }
7074         if (cps->sendTime != 1 &&
7075             (StrStr(message, "time") || StrStr(message, "otim"))) {
7076           /* Program apparently doesn't have "time" or "otim" command */
7077           cps->sendTime = 0;
7078           return;
7079         }
7080         if (StrStr(message, "analyze")) {
7081             cps->analysisSupport = FALSE;
7082             cps->analyzing = FALSE;
7083             Reset(FALSE, TRUE);
7084             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7085             DisplayError(buf2, 0);
7086             return;
7087         }
7088         if (StrStr(message, "(no matching move)st")) {
7089           /* Special kludge for GNU Chess 4 only */
7090           cps->stKludge = TRUE;
7091           SendTimeControl(cps, movesPerSession, timeControl,
7092                           timeIncrement, appData.searchDepth,
7093                           searchTime);
7094           return;
7095         }
7096         if (StrStr(message, "(no matching move)sd")) {
7097           /* Special kludge for GNU Chess 4 only */
7098           cps->sdKludge = TRUE;
7099           SendTimeControl(cps, movesPerSession, timeControl,
7100                           timeIncrement, appData.searchDepth,
7101                           searchTime);
7102           return;
7103         }
7104         if (!StrStr(message, "llegal")) {
7105             return;
7106         }
7107         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7108             gameMode == IcsIdle) return;
7109         if (forwardMostMove <= backwardMostMove) return;
7110         if (pausing) PauseEvent();
7111       if(appData.forceIllegal) {
7112             // [HGM] illegal: machine refused move; force position after move into it
7113           SendToProgram("force\n", cps);
7114           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7115                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7116                 // when black is to move, while there might be nothing on a2 or black
7117                 // might already have the move. So send the board as if white has the move.
7118                 // But first we must change the stm of the engine, as it refused the last move
7119                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7120                 if(WhiteOnMove(forwardMostMove)) {
7121                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7122                     SendBoard(cps, forwardMostMove); // kludgeless board
7123                 } else {
7124                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7125                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7126                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7127                 }
7128           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7129             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7130                  gameMode == TwoMachinesPlay)
7131               SendToProgram("go\n", cps);
7132             return;
7133       } else
7134         if (gameMode == PlayFromGameFile) {
7135             /* Stop reading this game file */
7136             gameMode = EditGame;
7137             ModeHighlight();
7138         }
7139         currentMove = --forwardMostMove;
7140         DisplayMove(currentMove-1); /* before DisplayMoveError */
7141         SwitchClocks();
7142         DisplayBothClocks();
7143         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7144                 parseList[currentMove], cps->which);
7145         DisplayMoveError(buf1);
7146         DrawPosition(FALSE, boards[currentMove]);
7147
7148         /* [HGM] illegal-move claim should forfeit game when Xboard */
7149         /* only passes fully legal moves                            */
7150         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7151             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7152                                 "False illegal-move claim", GE_XBOARD );
7153         }
7154         return;
7155     }
7156     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7157         /* Program has a broken "time" command that
7158            outputs a string not ending in newline.
7159            Don't use it. */
7160         cps->sendTime = 0;
7161     }
7162     
7163     /*
7164      * If chess program startup fails, exit with an error message.
7165      * Attempts to recover here are futile.
7166      */
7167     if ((StrStr(message, "unknown host") != NULL)
7168         || (StrStr(message, "No remote directory") != NULL)
7169         || (StrStr(message, "not found") != NULL)
7170         || (StrStr(message, "No such file") != NULL)
7171         || (StrStr(message, "can't alloc") != NULL)
7172         || (StrStr(message, "Permission denied") != NULL)) {
7173
7174         cps->maybeThinking = FALSE;
7175         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7176                 cps->which, cps->program, cps->host, message);
7177         RemoveInputSource(cps->isr);
7178         DisplayFatalError(buf1, 0, 1);
7179         return;
7180     }
7181     
7182     /* 
7183      * Look for hint output
7184      */
7185     if (sscanf(message, "Hint: %s", buf1) == 1) {
7186         if (cps == &first && hintRequested) {
7187             hintRequested = FALSE;
7188             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7189                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7190                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7191                                     PosFlags(forwardMostMove),
7192                                     fromY, fromX, toY, toX, promoChar, buf1);
7193                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7194                 DisplayInformation(buf2);
7195             } else {
7196                 /* Hint move could not be parsed!? */
7197               snprintf(buf2, sizeof(buf2),
7198                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7199                         buf1, cps->which);
7200                 DisplayError(buf2, 0);
7201             }
7202         } else {
7203             strcpy(lastHint, buf1);
7204         }
7205         return;
7206     }
7207
7208     /*
7209      * Ignore other messages if game is not in progress
7210      */
7211     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7212         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7213
7214     /*
7215      * look for win, lose, draw, or draw offer
7216      */
7217     if (strncmp(message, "1-0", 3) == 0) {
7218         char *p, *q, *r = "";
7219         p = strchr(message, '{');
7220         if (p) {
7221             q = strchr(p, '}');
7222             if (q) {
7223                 *q = NULLCHAR;
7224                 r = p + 1;
7225             }
7226         }
7227         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7228         return;
7229     } else if (strncmp(message, "0-1", 3) == 0) {
7230         char *p, *q, *r = "";
7231         p = strchr(message, '{');
7232         if (p) {
7233             q = strchr(p, '}');
7234             if (q) {
7235                 *q = NULLCHAR;
7236                 r = p + 1;
7237             }
7238         }
7239         /* Kludge for Arasan 4.1 bug */
7240         if (strcmp(r, "Black resigns") == 0) {
7241             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7242             return;
7243         }
7244         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7245         return;
7246     } else if (strncmp(message, "1/2", 3) == 0) {
7247         char *p, *q, *r = "";
7248         p = strchr(message, '{');
7249         if (p) {
7250             q = strchr(p, '}');
7251             if (q) {
7252                 *q = NULLCHAR;
7253                 r = p + 1;
7254             }
7255         }
7256             
7257         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7258         return;
7259
7260     } else if (strncmp(message, "White resign", 12) == 0) {
7261         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7262         return;
7263     } else if (strncmp(message, "Black resign", 12) == 0) {
7264         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7265         return;
7266     } else if (strncmp(message, "White matches", 13) == 0 ||
7267                strncmp(message, "Black matches", 13) == 0   ) {
7268         /* [HGM] ignore GNUShogi noises */
7269         return;
7270     } else if (strncmp(message, "White", 5) == 0 &&
7271                message[5] != '(' &&
7272                StrStr(message, "Black") == NULL) {
7273         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7274         return;
7275     } else if (strncmp(message, "Black", 5) == 0 &&
7276                message[5] != '(') {
7277         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7278         return;
7279     } else if (strcmp(message, "resign") == 0 ||
7280                strcmp(message, "computer resigns") == 0) {
7281         switch (gameMode) {
7282           case MachinePlaysBlack:
7283           case IcsPlayingBlack:
7284             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7285             break;
7286           case MachinePlaysWhite:
7287           case IcsPlayingWhite:
7288             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7289             break;
7290           case TwoMachinesPlay:
7291             if (cps->twoMachinesColor[0] == 'w')
7292               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7293             else
7294               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7295             break;
7296           default:
7297             /* can't happen */
7298             break;
7299         }
7300         return;
7301     } else if (strncmp(message, "opponent mates", 14) == 0) {
7302         switch (gameMode) {
7303           case MachinePlaysBlack:
7304           case IcsPlayingBlack:
7305             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7306             break;
7307           case MachinePlaysWhite:
7308           case IcsPlayingWhite:
7309             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7310             break;
7311           case TwoMachinesPlay:
7312             if (cps->twoMachinesColor[0] == 'w')
7313               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7314             else
7315               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7316             break;
7317           default:
7318             /* can't happen */
7319             break;
7320         }
7321         return;
7322     } else if (strncmp(message, "computer mates", 14) == 0) {
7323         switch (gameMode) {
7324           case MachinePlaysBlack:
7325           case IcsPlayingBlack:
7326             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7327             break;
7328           case MachinePlaysWhite:
7329           case IcsPlayingWhite:
7330             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7331             break;
7332           case TwoMachinesPlay:
7333             if (cps->twoMachinesColor[0] == 'w')
7334               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7335             else
7336               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7337             break;
7338           default:
7339             /* can't happen */
7340             break;
7341         }
7342         return;
7343     } else if (strncmp(message, "checkmate", 9) == 0) {
7344         if (WhiteOnMove(forwardMostMove)) {
7345             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7346         } else {
7347             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7348         }
7349         return;
7350     } else if (strstr(message, "Draw") != NULL ||
7351                strstr(message, "game is a draw") != NULL) {
7352         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7353         return;
7354     } else if (strstr(message, "offer") != NULL &&
7355                strstr(message, "draw") != NULL) {
7356 #if ZIPPY
7357         if (appData.zippyPlay && first.initDone) {
7358             /* Relay offer to ICS */
7359             SendToICS(ics_prefix);
7360             SendToICS("draw\n");
7361         }
7362 #endif
7363         cps->offeredDraw = 2; /* valid until this engine moves twice */
7364         if (gameMode == TwoMachinesPlay) {
7365             if (cps->other->offeredDraw) {
7366                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7367             /* [HGM] in two-machine mode we delay relaying draw offer      */
7368             /* until after we also have move, to see if it is really claim */
7369             }
7370         } else if (gameMode == MachinePlaysWhite ||
7371                    gameMode == MachinePlaysBlack) {
7372           if (userOfferedDraw) {
7373             DisplayInformation(_("Machine accepts your draw offer"));
7374             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7375           } else {
7376             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7377           }
7378         }
7379     }
7380
7381     
7382     /*
7383      * Look for thinking output
7384      */
7385     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7386           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7387                                 ) {
7388         int plylev, mvleft, mvtot, curscore, time;
7389         char mvname[MOVE_LEN];
7390         u64 nodes; // [DM]
7391         char plyext;
7392         int ignore = FALSE;
7393         int prefixHint = FALSE;
7394         mvname[0] = NULLCHAR;
7395
7396         switch (gameMode) {
7397           case MachinePlaysBlack:
7398           case IcsPlayingBlack:
7399             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7400             break;
7401           case MachinePlaysWhite:
7402           case IcsPlayingWhite:
7403             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7404             break;
7405           case AnalyzeMode:
7406           case AnalyzeFile:
7407             break;
7408           case IcsObserving: /* [DM] icsEngineAnalyze */
7409             if (!appData.icsEngineAnalyze) ignore = TRUE;
7410             break;
7411           case TwoMachinesPlay:
7412             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7413                 ignore = TRUE;
7414             }
7415             break;
7416           default:
7417             ignore = TRUE;
7418             break;
7419         }
7420
7421         if (!ignore) {
7422             buf1[0] = NULLCHAR;
7423             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7424                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7425
7426                 if (plyext != ' ' && plyext != '\t') {
7427                     time *= 100;
7428                 }
7429
7430                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7431                 if( cps->scoreIsAbsolute && 
7432                     ( gameMode == MachinePlaysBlack ||
7433                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7434                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7435                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7436                      !WhiteOnMove(currentMove)
7437                     ) )
7438                 {
7439                     curscore = -curscore;
7440                 }
7441
7442
7443                 programStats.depth = plylev;
7444                 programStats.nodes = nodes;
7445                 programStats.time = time;
7446                 programStats.score = curscore;
7447                 programStats.got_only_move = 0;
7448
7449                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7450                         int ticklen;
7451
7452                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7453                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7454                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7455                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7456                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7457                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7458                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7459                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7460                 }
7461
7462                 /* Buffer overflow protection */
7463                 if (buf1[0] != NULLCHAR) {
7464                     if (strlen(buf1) >= sizeof(programStats.movelist)
7465                         && appData.debugMode) {
7466                         fprintf(debugFP,
7467                                 "PV is too long; using the first %u bytes.\n",
7468                                 (unsigned) sizeof(programStats.movelist) - 1);
7469                     }
7470
7471                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7472                 } else {
7473                     sprintf(programStats.movelist, " no PV\n");
7474                 }
7475
7476                 if (programStats.seen_stat) {
7477                     programStats.ok_to_send = 1;
7478                 }
7479
7480                 if (strchr(programStats.movelist, '(') != NULL) {
7481                     programStats.line_is_book = 1;
7482                     programStats.nr_moves = 0;
7483                     programStats.moves_left = 0;
7484                 } else {
7485                     programStats.line_is_book = 0;
7486                 }
7487
7488                 SendProgramStatsToFrontend( cps, &programStats );
7489
7490                 /* 
7491                     [AS] Protect the thinkOutput buffer from overflow... this
7492                     is only useful if buf1 hasn't overflowed first!
7493                 */
7494                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7495                         plylev, 
7496                         (gameMode == TwoMachinesPlay ?
7497                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7498                         ((double) curscore) / 100.0,
7499                         prefixHint ? lastHint : "",
7500                         prefixHint ? " " : "" );
7501
7502                 if( buf1[0] != NULLCHAR ) {
7503                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7504
7505                     if( strlen(buf1) > max_len ) {
7506                         if( appData.debugMode) {
7507                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7508                         }
7509                         buf1[max_len+1] = '\0';
7510                     }
7511
7512                     strcat( thinkOutput, buf1 );
7513                 }
7514
7515                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7516                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7517                     DisplayMove(currentMove - 1);
7518                 }
7519                 return;
7520
7521             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7522                 /* crafty (9.25+) says "(only move) <move>"
7523                  * if there is only 1 legal move
7524                  */
7525                 sscanf(p, "(only move) %s", buf1);
7526                 sprintf(thinkOutput, "%s (only move)", buf1);
7527                 sprintf(programStats.movelist, "%s (only move)", buf1);
7528                 programStats.depth = 1;
7529                 programStats.nr_moves = 1;
7530                 programStats.moves_left = 1;
7531                 programStats.nodes = 1;
7532                 programStats.time = 1;
7533                 programStats.got_only_move = 1;
7534
7535                 /* Not really, but we also use this member to
7536                    mean "line isn't going to change" (Crafty
7537                    isn't searching, so stats won't change) */
7538                 programStats.line_is_book = 1;
7539
7540                 SendProgramStatsToFrontend( cps, &programStats );
7541                 
7542                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7543                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7544                     DisplayMove(currentMove - 1);
7545                 }
7546                 return;
7547             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7548                               &time, &nodes, &plylev, &mvleft,
7549                               &mvtot, mvname) >= 5) {
7550                 /* The stat01: line is from Crafty (9.29+) in response
7551                    to the "." command */
7552                 programStats.seen_stat = 1;
7553                 cps->maybeThinking = TRUE;
7554
7555                 if (programStats.got_only_move || !appData.periodicUpdates)
7556                   return;
7557
7558                 programStats.depth = plylev;
7559                 programStats.time = time;
7560                 programStats.nodes = nodes;
7561                 programStats.moves_left = mvleft;
7562                 programStats.nr_moves = mvtot;
7563                 strcpy(programStats.move_name, mvname);
7564                 programStats.ok_to_send = 1;
7565                 programStats.movelist[0] = '\0';
7566
7567                 SendProgramStatsToFrontend( cps, &programStats );
7568
7569                 return;
7570
7571             } else if (strncmp(message,"++",2) == 0) {
7572                 /* Crafty 9.29+ outputs this */
7573                 programStats.got_fail = 2;
7574                 return;
7575
7576             } else if (strncmp(message,"--",2) == 0) {
7577                 /* Crafty 9.29+ outputs this */
7578                 programStats.got_fail = 1;
7579                 return;
7580
7581             } else if (thinkOutput[0] != NULLCHAR &&
7582                        strncmp(message, "    ", 4) == 0) {
7583                 unsigned message_len;
7584
7585                 p = message;
7586                 while (*p && *p == ' ') p++;
7587
7588                 message_len = strlen( p );
7589
7590                 /* [AS] Avoid buffer overflow */
7591                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7592                     strcat(thinkOutput, " ");
7593                     strcat(thinkOutput, p);
7594                 }
7595
7596                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7597                     strcat(programStats.movelist, " ");
7598                     strcat(programStats.movelist, p);
7599                 }
7600
7601                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7602                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7603                     DisplayMove(currentMove - 1);
7604                 }
7605                 return;
7606             }
7607         }
7608         else {
7609             buf1[0] = NULLCHAR;
7610
7611             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7612                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7613             {
7614                 ChessProgramStats cpstats;
7615
7616                 if (plyext != ' ' && plyext != '\t') {
7617                     time *= 100;
7618                 }
7619
7620                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7621                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7622                     curscore = -curscore;
7623                 }
7624
7625                 cpstats.depth = plylev;
7626                 cpstats.nodes = nodes;
7627                 cpstats.time = time;
7628                 cpstats.score = curscore;
7629                 cpstats.got_only_move = 0;
7630                 cpstats.movelist[0] = '\0';
7631
7632                 if (buf1[0] != NULLCHAR) {
7633                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7634                 }
7635
7636                 cpstats.ok_to_send = 0;
7637                 cpstats.line_is_book = 0;
7638                 cpstats.nr_moves = 0;
7639                 cpstats.moves_left = 0;
7640
7641                 SendProgramStatsToFrontend( cps, &cpstats );
7642             }
7643         }
7644     }
7645 }
7646
7647
7648 /* Parse a game score from the character string "game", and
7649    record it as the history of the current game.  The game
7650    score is NOT assumed to start from the standard position. 
7651    The display is not updated in any way.
7652    */
7653 void
7654 ParseGameHistory(game)
7655      char *game;
7656 {
7657     ChessMove moveType;
7658     int fromX, fromY, toX, toY, boardIndex;
7659     char promoChar;
7660     char *p, *q;
7661     char buf[MSG_SIZ];
7662
7663     if (appData.debugMode)
7664       fprintf(debugFP, "Parsing game history: %s\n", game);
7665
7666     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7667     gameInfo.site = StrSave(appData.icsHost);
7668     gameInfo.date = PGNDate();
7669     gameInfo.round = StrSave("-");
7670
7671     /* Parse out names of players */
7672     while (*game == ' ') game++;
7673     p = buf;
7674     while (*game != ' ') *p++ = *game++;
7675     *p = NULLCHAR;
7676     gameInfo.white = StrSave(buf);
7677     while (*game == ' ') game++;
7678     p = buf;
7679     while (*game != ' ' && *game != '\n') *p++ = *game++;
7680     *p = NULLCHAR;
7681     gameInfo.black = StrSave(buf);
7682
7683     /* Parse moves */
7684     boardIndex = blackPlaysFirst ? 1 : 0;
7685     yynewstr(game);
7686     for (;;) {
7687         yyboardindex = boardIndex;
7688         moveType = (ChessMove) yylex();
7689         switch (moveType) {
7690           case IllegalMove:             /* maybe suicide chess, etc. */
7691   if (appData.debugMode) {
7692     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7693     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7694     setbuf(debugFP, NULL);
7695   }
7696           case WhitePromotionChancellor:
7697           case BlackPromotionChancellor:
7698           case WhitePromotionArchbishop:
7699           case BlackPromotionArchbishop:
7700           case WhitePromotionQueen:
7701           case BlackPromotionQueen:
7702           case WhitePromotionRook:
7703           case BlackPromotionRook:
7704           case WhitePromotionBishop:
7705           case BlackPromotionBishop:
7706           case WhitePromotionKnight:
7707           case BlackPromotionKnight:
7708           case WhitePromotionKing:
7709           case BlackPromotionKing:
7710           case NormalMove:
7711           case WhiteCapturesEnPassant:
7712           case BlackCapturesEnPassant:
7713           case WhiteKingSideCastle:
7714           case WhiteQueenSideCastle:
7715           case BlackKingSideCastle:
7716           case BlackQueenSideCastle:
7717           case WhiteKingSideCastleWild:
7718           case WhiteQueenSideCastleWild:
7719           case BlackKingSideCastleWild:
7720           case BlackQueenSideCastleWild:
7721           /* PUSH Fabien */
7722           case WhiteHSideCastleFR:
7723           case WhiteASideCastleFR:
7724           case BlackHSideCastleFR:
7725           case BlackASideCastleFR:
7726           /* POP Fabien */
7727             fromX = currentMoveString[0] - AAA;
7728             fromY = currentMoveString[1] - ONE;
7729             toX = currentMoveString[2] - AAA;
7730             toY = currentMoveString[3] - ONE;
7731             promoChar = currentMoveString[4];
7732             break;
7733           case WhiteDrop:
7734           case BlackDrop:
7735             fromX = moveType == WhiteDrop ?
7736               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7737             (int) CharToPiece(ToLower(currentMoveString[0]));
7738             fromY = DROP_RANK;
7739             toX = currentMoveString[2] - AAA;
7740             toY = currentMoveString[3] - ONE;
7741             promoChar = NULLCHAR;
7742             break;
7743           case AmbiguousMove:
7744             /* bug? */
7745             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7746   if (appData.debugMode) {
7747     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7748     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7749     setbuf(debugFP, NULL);
7750   }
7751             DisplayError(buf, 0);
7752             return;
7753           case ImpossibleMove:
7754             /* bug? */
7755             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7756   if (appData.debugMode) {
7757     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7758     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7759     setbuf(debugFP, NULL);
7760   }
7761             DisplayError(buf, 0);
7762             return;
7763           case (ChessMove) 0:   /* end of file */
7764             if (boardIndex < backwardMostMove) {
7765                 /* Oops, gap.  How did that happen? */
7766                 DisplayError(_("Gap in move list"), 0);
7767                 return;
7768             }
7769             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7770             if (boardIndex > forwardMostMove) {
7771                 forwardMostMove = boardIndex;
7772             }
7773             return;
7774           case ElapsedTime:
7775             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7776                 strcat(parseList[boardIndex-1], " ");
7777                 strcat(parseList[boardIndex-1], yy_text);
7778             }
7779             continue;
7780           case Comment:
7781           case PGNTag:
7782           case NAG:
7783           default:
7784             /* ignore */
7785             continue;
7786           case WhiteWins:
7787           case BlackWins:
7788           case GameIsDrawn:
7789           case GameUnfinished:
7790             if (gameMode == IcsExamining) {
7791                 if (boardIndex < backwardMostMove) {
7792                     /* Oops, gap.  How did that happen? */
7793                     return;
7794                 }
7795                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7796                 return;
7797             }
7798             gameInfo.result = moveType;
7799             p = strchr(yy_text, '{');
7800             if (p == NULL) p = strchr(yy_text, '(');
7801             if (p == NULL) {
7802                 p = yy_text;
7803                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7804             } else {
7805                 q = strchr(p, *p == '{' ? '}' : ')');
7806                 if (q != NULL) *q = NULLCHAR;
7807                 p++;
7808             }
7809             gameInfo.resultDetails = StrSave(p);
7810             continue;
7811         }
7812         if (boardIndex >= forwardMostMove &&
7813             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7814             backwardMostMove = blackPlaysFirst ? 1 : 0;
7815             return;
7816         }
7817         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7818                                  fromY, fromX, toY, toX, promoChar,
7819                                  parseList[boardIndex]);
7820         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7821         /* currentMoveString is set as a side-effect of yylex */
7822         strcpy(moveList[boardIndex], currentMoveString);
7823         strcat(moveList[boardIndex], "\n");
7824         boardIndex++;
7825         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7826         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7827           case MT_NONE:
7828           case MT_STALEMATE:
7829           default:
7830             break;
7831           case MT_CHECK:
7832             if(gameInfo.variant != VariantShogi)
7833                 strcat(parseList[boardIndex - 1], "+");
7834             break;
7835           case MT_CHECKMATE:
7836           case MT_STAINMATE:
7837             strcat(parseList[boardIndex - 1], "#");
7838             break;
7839         }
7840     }
7841 }
7842
7843
7844 /* Apply a move to the given board  */
7845 void
7846 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7847      int fromX, fromY, toX, toY;
7848      int promoChar;
7849      Board board;
7850 {
7851   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7852   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7853
7854     /* [HGM] compute & store e.p. status and castling rights for new position */
7855     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7856     { int i;
7857
7858       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7859       oldEP = (signed char)board[EP_STATUS];
7860       board[EP_STATUS] = EP_NONE;
7861
7862       if( board[toY][toX] != EmptySquare ) 
7863            board[EP_STATUS] = EP_CAPTURE;  
7864
7865       if( board[fromY][fromX] == WhitePawn ) {
7866            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7867                board[EP_STATUS] = EP_PAWN_MOVE;
7868            if( toY-fromY==2) {
7869                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7870                         gameInfo.variant != VariantBerolina || toX < fromX)
7871                       board[EP_STATUS] = toX | berolina;
7872                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7873                         gameInfo.variant != VariantBerolina || toX > fromX) 
7874                       board[EP_STATUS] = toX;
7875            }
7876       } else 
7877       if( board[fromY][fromX] == BlackPawn ) {
7878            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7879                board[EP_STATUS] = EP_PAWN_MOVE; 
7880            if( toY-fromY== -2) {
7881                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7882                         gameInfo.variant != VariantBerolina || toX < fromX)
7883                       board[EP_STATUS] = toX | berolina;
7884                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7885                         gameInfo.variant != VariantBerolina || toX > fromX) 
7886                       board[EP_STATUS] = toX;
7887            }
7888        }
7889
7890        for(i=0; i<nrCastlingRights; i++) {
7891            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7892               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7893              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7894        }
7895
7896     }
7897
7898   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7899   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7900        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7901          
7902   if (fromX == toX && fromY == toY) return;
7903
7904   if (fromY == DROP_RANK) {
7905         /* must be first */
7906         piece = board[toY][toX] = (ChessSquare) fromX;
7907   } else {
7908      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7909      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7910      if(gameInfo.variant == VariantKnightmate)
7911          king += (int) WhiteUnicorn - (int) WhiteKing;
7912
7913     /* Code added by Tord: */
7914     /* FRC castling assumed when king captures friendly rook. */
7915     if (board[fromY][fromX] == WhiteKing &&
7916              board[toY][toX] == WhiteRook) {
7917       board[fromY][fromX] = EmptySquare;
7918       board[toY][toX] = EmptySquare;
7919       if(toX > fromX) {
7920         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7921       } else {
7922         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7923       }
7924     } else if (board[fromY][fromX] == BlackKing &&
7925                board[toY][toX] == BlackRook) {
7926       board[fromY][fromX] = EmptySquare;
7927       board[toY][toX] = EmptySquare;
7928       if(toX > fromX) {
7929         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7930       } else {
7931         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7932       }
7933     /* End of code added by Tord */
7934
7935     } else if (board[fromY][fromX] == king
7936         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7937         && toY == fromY && toX > fromX+1) {
7938         board[fromY][fromX] = EmptySquare;
7939         board[toY][toX] = king;
7940         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7941         board[fromY][BOARD_RGHT-1] = EmptySquare;
7942     } else if (board[fromY][fromX] == king
7943         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7944                && toY == fromY && toX < fromX-1) {
7945         board[fromY][fromX] = EmptySquare;
7946         board[toY][toX] = king;
7947         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7948         board[fromY][BOARD_LEFT] = EmptySquare;
7949     } else if (board[fromY][fromX] == WhitePawn
7950                && toY >= BOARD_HEIGHT-promoRank
7951                && gameInfo.variant != VariantXiangqi
7952                ) {
7953         /* white pawn promotion */
7954         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7955         if (board[toY][toX] == EmptySquare) {
7956             board[toY][toX] = WhiteQueen;
7957         }
7958         if(gameInfo.variant==VariantBughouse ||
7959            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7960             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7961         board[fromY][fromX] = EmptySquare;
7962     } else if ((fromY == BOARD_HEIGHT-4)
7963                && (toX != fromX)
7964                && gameInfo.variant != VariantXiangqi
7965                && gameInfo.variant != VariantBerolina
7966                && (board[fromY][fromX] == WhitePawn)
7967                && (board[toY][toX] == EmptySquare)) {
7968         board[fromY][fromX] = EmptySquare;
7969         board[toY][toX] = WhitePawn;
7970         captured = board[toY - 1][toX];
7971         board[toY - 1][toX] = EmptySquare;
7972     } else if ((fromY == BOARD_HEIGHT-4)
7973                && (toX == fromX)
7974                && gameInfo.variant == VariantBerolina
7975                && (board[fromY][fromX] == WhitePawn)
7976                && (board[toY][toX] == EmptySquare)) {
7977         board[fromY][fromX] = EmptySquare;
7978         board[toY][toX] = WhitePawn;
7979         if(oldEP & EP_BEROLIN_A) {
7980                 captured = board[fromY][fromX-1];
7981                 board[fromY][fromX-1] = EmptySquare;
7982         }else{  captured = board[fromY][fromX+1];
7983                 board[fromY][fromX+1] = EmptySquare;
7984         }
7985     } else if (board[fromY][fromX] == king
7986         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7987                && toY == fromY && toX > fromX+1) {
7988         board[fromY][fromX] = EmptySquare;
7989         board[toY][toX] = king;
7990         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7991         board[fromY][BOARD_RGHT-1] = EmptySquare;
7992     } else if (board[fromY][fromX] == king
7993         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7994                && toY == fromY && toX < fromX-1) {
7995         board[fromY][fromX] = EmptySquare;
7996         board[toY][toX] = king;
7997         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7998         board[fromY][BOARD_LEFT] = EmptySquare;
7999     } else if (fromY == 7 && fromX == 3
8000                && board[fromY][fromX] == BlackKing
8001                && toY == 7 && toX == 5) {
8002         board[fromY][fromX] = EmptySquare;
8003         board[toY][toX] = BlackKing;
8004         board[fromY][7] = EmptySquare;
8005         board[toY][4] = BlackRook;
8006     } else if (fromY == 7 && fromX == 3
8007                && board[fromY][fromX] == BlackKing
8008                && toY == 7 && toX == 1) {
8009         board[fromY][fromX] = EmptySquare;
8010         board[toY][toX] = BlackKing;
8011         board[fromY][0] = EmptySquare;
8012         board[toY][2] = BlackRook;
8013     } else if (board[fromY][fromX] == BlackPawn
8014                && toY < promoRank
8015                && gameInfo.variant != VariantXiangqi
8016                ) {
8017         /* black pawn promotion */
8018         board[toY][toX] = CharToPiece(ToLower(promoChar));
8019         if (board[toY][toX] == EmptySquare) {
8020             board[toY][toX] = BlackQueen;
8021         }
8022         if(gameInfo.variant==VariantBughouse ||
8023            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8024             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8025         board[fromY][fromX] = EmptySquare;
8026     } else if ((fromY == 3)
8027                && (toX != fromX)
8028                && gameInfo.variant != VariantXiangqi
8029                && gameInfo.variant != VariantBerolina
8030                && (board[fromY][fromX] == BlackPawn)
8031                && (board[toY][toX] == EmptySquare)) {
8032         board[fromY][fromX] = EmptySquare;
8033         board[toY][toX] = BlackPawn;
8034         captured = board[toY + 1][toX];
8035         board[toY + 1][toX] = EmptySquare;
8036     } else if ((fromY == 3)
8037                && (toX == fromX)
8038                && gameInfo.variant == VariantBerolina
8039                && (board[fromY][fromX] == BlackPawn)
8040                && (board[toY][toX] == EmptySquare)) {
8041         board[fromY][fromX] = EmptySquare;
8042         board[toY][toX] = BlackPawn;
8043         if(oldEP & EP_BEROLIN_A) {
8044                 captured = board[fromY][fromX-1];
8045                 board[fromY][fromX-1] = EmptySquare;
8046         }else{  captured = board[fromY][fromX+1];
8047                 board[fromY][fromX+1] = EmptySquare;
8048         }
8049     } else {
8050         board[toY][toX] = board[fromY][fromX];
8051         board[fromY][fromX] = EmptySquare;
8052     }
8053
8054     /* [HGM] now we promote for Shogi, if needed */
8055     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8056         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8057   }
8058
8059     if (gameInfo.holdingsWidth != 0) {
8060
8061       /* !!A lot more code needs to be written to support holdings  */
8062       /* [HGM] OK, so I have written it. Holdings are stored in the */
8063       /* penultimate board files, so they are automaticlly stored   */
8064       /* in the game history.                                       */
8065       if (fromY == DROP_RANK) {
8066         /* Delete from holdings, by decreasing count */
8067         /* and erasing image if necessary            */
8068         p = (int) fromX;
8069         if(p < (int) BlackPawn) { /* white drop */
8070              p -= (int)WhitePawn;
8071                  p = PieceToNumber((ChessSquare)p);
8072              if(p >= gameInfo.holdingsSize) p = 0;
8073              if(--board[p][BOARD_WIDTH-2] <= 0)
8074                   board[p][BOARD_WIDTH-1] = EmptySquare;
8075              if((int)board[p][BOARD_WIDTH-2] < 0)
8076                         board[p][BOARD_WIDTH-2] = 0;
8077         } else {                  /* black drop */
8078              p -= (int)BlackPawn;
8079                  p = PieceToNumber((ChessSquare)p);
8080              if(p >= gameInfo.holdingsSize) p = 0;
8081              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8082                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8083              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8084                         board[BOARD_HEIGHT-1-p][1] = 0;
8085         }
8086       }
8087       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8088           && gameInfo.variant != VariantBughouse        ) {
8089         /* [HGM] holdings: Add to holdings, if holdings exist */
8090         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8091                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8092                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8093         }
8094         p = (int) captured;
8095         if (p >= (int) BlackPawn) {
8096           p -= (int)BlackPawn;
8097           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8098                   /* in Shogi restore piece to its original  first */
8099                   captured = (ChessSquare) (DEMOTED captured);
8100                   p = DEMOTED p;
8101           }
8102           p = PieceToNumber((ChessSquare)p);
8103           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8104           board[p][BOARD_WIDTH-2]++;
8105           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8106         } else {
8107           p -= (int)WhitePawn;
8108           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8109                   captured = (ChessSquare) (DEMOTED captured);
8110                   p = DEMOTED p;
8111           }
8112           p = PieceToNumber((ChessSquare)p);
8113           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8114           board[BOARD_HEIGHT-1-p][1]++;
8115           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8116         }
8117       }
8118     } else if (gameInfo.variant == VariantAtomic) {
8119       if (captured != EmptySquare) {
8120         int y, x;
8121         for (y = toY-1; y <= toY+1; y++) {
8122           for (x = toX-1; x <= toX+1; x++) {
8123             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8124                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8125               board[y][x] = EmptySquare;
8126             }
8127           }
8128         }
8129         board[toY][toX] = EmptySquare;
8130       }
8131     }
8132     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8133         /* [HGM] Shogi promotions */
8134         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8135     }
8136
8137     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8138                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8139         // [HGM] superchess: take promotion piece out of holdings
8140         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8141         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8142             if(!--board[k][BOARD_WIDTH-2])
8143                 board[k][BOARD_WIDTH-1] = EmptySquare;
8144         } else {
8145             if(!--board[BOARD_HEIGHT-1-k][1])
8146                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8147         }
8148     }
8149
8150 }
8151
8152 /* Updates forwardMostMove */
8153 void
8154 MakeMove(fromX, fromY, toX, toY, promoChar)
8155      int fromX, fromY, toX, toY;
8156      int promoChar;
8157 {
8158 //    forwardMostMove++; // [HGM] bare: moved downstream
8159
8160     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8161         int timeLeft; static int lastLoadFlag=0; int king, piece;
8162         piece = boards[forwardMostMove][fromY][fromX];
8163         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8164         if(gameInfo.variant == VariantKnightmate)
8165             king += (int) WhiteUnicorn - (int) WhiteKing;
8166         if(forwardMostMove == 0) {
8167             if(blackPlaysFirst) 
8168                 fprintf(serverMoves, "%s;", second.tidy);
8169             fprintf(serverMoves, "%s;", first.tidy);
8170             if(!blackPlaysFirst) 
8171                 fprintf(serverMoves, "%s;", second.tidy);
8172         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8173         lastLoadFlag = loadFlag;
8174         // print base move
8175         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8176         // print castling suffix
8177         if( toY == fromY && piece == king ) {
8178             if(toX-fromX > 1)
8179                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8180             if(fromX-toX >1)
8181                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8182         }
8183         // e.p. suffix
8184         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8185              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8186              boards[forwardMostMove][toY][toX] == EmptySquare
8187              && fromX != toX )
8188                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8189         // promotion suffix
8190         if(promoChar != NULLCHAR)
8191                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8192         if(!loadFlag) {
8193             fprintf(serverMoves, "/%d/%d",
8194                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8195             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8196             else                      timeLeft = blackTimeRemaining/1000;
8197             fprintf(serverMoves, "/%d", timeLeft);
8198         }
8199         fflush(serverMoves);
8200     }
8201
8202     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8203       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8204                         0, 1);
8205       return;
8206     }
8207     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8208     if (commentList[forwardMostMove+1] != NULL) {
8209         free(commentList[forwardMostMove+1]);
8210         commentList[forwardMostMove+1] = NULL;
8211     }
8212     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8213     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8214     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8215     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8216     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8217     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8218     gameInfo.result = GameUnfinished;
8219     if (gameInfo.resultDetails != NULL) {
8220         free(gameInfo.resultDetails);
8221         gameInfo.resultDetails = NULL;
8222     }
8223     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8224                               moveList[forwardMostMove - 1]);
8225     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8226                              PosFlags(forwardMostMove - 1),
8227                              fromY, fromX, toY, toX, promoChar,
8228                              parseList[forwardMostMove - 1]);
8229     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8230       case MT_NONE:
8231       case MT_STALEMATE:
8232       default:
8233         break;
8234       case MT_CHECK:
8235         if(gameInfo.variant != VariantShogi)
8236             strcat(parseList[forwardMostMove - 1], "+");
8237         break;
8238       case MT_CHECKMATE:
8239       case MT_STAINMATE:
8240         strcat(parseList[forwardMostMove - 1], "#");
8241         break;
8242     }
8243     if (appData.debugMode) {
8244         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8245     }
8246
8247 }
8248
8249 /* Updates currentMove if not pausing */
8250 void
8251 ShowMove(fromX, fromY, toX, toY)
8252 {
8253     int instant = (gameMode == PlayFromGameFile) ?
8254         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8255     if(appData.noGUI) return;
8256     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8257         if (!instant) {
8258             if (forwardMostMove == currentMove + 1) {
8259                 AnimateMove(boards[forwardMostMove - 1],
8260                             fromX, fromY, toX, toY);
8261             }
8262             if (appData.highlightLastMove) {
8263                 SetHighlights(fromX, fromY, toX, toY);
8264             }
8265         }
8266         currentMove = forwardMostMove;
8267     }
8268
8269     if (instant) return;
8270
8271     DisplayMove(currentMove - 1);
8272     DrawPosition(FALSE, boards[currentMove]);
8273     DisplayBothClocks();
8274     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8275 }
8276
8277 void SendEgtPath(ChessProgramState *cps)
8278 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8279         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8280
8281         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8282
8283         while(*p) {
8284             char c, *q = name+1, *r, *s;
8285
8286             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8287             while(*p && *p != ',') *q++ = *p++;
8288             *q++ = ':'; *q = 0;
8289             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8290                 strcmp(name, ",nalimov:") == 0 ) {
8291                 // take nalimov path from the menu-changeable option first, if it is defined
8292                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8293                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8294             } else
8295             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8296                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8297                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8298                 s = r = StrStr(s, ":") + 1; // beginning of path info
8299                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8300                 c = *r; *r = 0;             // temporarily null-terminate path info
8301                     *--q = 0;               // strip of trailig ':' from name
8302                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8303                 *r = c;
8304                 SendToProgram(buf,cps);     // send egtbpath command for this format
8305             }
8306             if(*p == ',') p++; // read away comma to position for next format name
8307         }
8308 }
8309
8310 void
8311 InitChessProgram(cps, setup)
8312      ChessProgramState *cps;
8313      int setup; /* [HGM] needed to setup FRC opening position */
8314 {
8315     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8316     if (appData.noChessProgram) return;
8317     hintRequested = FALSE;
8318     bookRequested = FALSE;
8319
8320     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8321     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8322     if(cps->memSize) { /* [HGM] memory */
8323         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8324         SendToProgram(buf, cps);
8325     }
8326     SendEgtPath(cps); /* [HGM] EGT */
8327     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8328         sprintf(buf, "cores %d\n", appData.smpCores);
8329         SendToProgram(buf, cps);
8330     }
8331
8332     SendToProgram(cps->initString, cps);
8333     if (gameInfo.variant != VariantNormal &&
8334         gameInfo.variant != VariantLoadable
8335         /* [HGM] also send variant if board size non-standard */
8336         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8337                                             ) {
8338       char *v = VariantName(gameInfo.variant);
8339       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8340         /* [HGM] in protocol 1 we have to assume all variants valid */
8341         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8342         DisplayFatalError(buf, 0, 1);
8343         return;
8344       }
8345
8346       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8347       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8348       if( gameInfo.variant == VariantXiangqi )
8349            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8350       if( gameInfo.variant == VariantShogi )
8351            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8352       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8353            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8354       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8355                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8356            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8357       if( gameInfo.variant == VariantCourier )
8358            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8359       if( gameInfo.variant == VariantSuper )
8360            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8361       if( gameInfo.variant == VariantGreat )
8362            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8363
8364       if(overruled) {
8365            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8366                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8367            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8368            if(StrStr(cps->variants, b) == NULL) { 
8369                // specific sized variant not known, check if general sizing allowed
8370                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8371                    if(StrStr(cps->variants, "boardsize") == NULL) {
8372                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8373                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8374                        DisplayFatalError(buf, 0, 1);
8375                        return;
8376                    }
8377                    /* [HGM] here we really should compare with the maximum supported board size */
8378                }
8379            }
8380       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8381       sprintf(buf, "variant %s\n", b);
8382       SendToProgram(buf, cps);
8383     }
8384     currentlyInitializedVariant = gameInfo.variant;
8385
8386     /* [HGM] send opening position in FRC to first engine */
8387     if(setup) {
8388           SendToProgram("force\n", cps);
8389           SendBoard(cps, 0);
8390           /* engine is now in force mode! Set flag to wake it up after first move. */
8391           setboardSpoiledMachineBlack = 1;
8392     }
8393
8394     if (cps->sendICS) {
8395       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8396       SendToProgram(buf, cps);
8397     }
8398     cps->maybeThinking = FALSE;
8399     cps->offeredDraw = 0;
8400     if (!appData.icsActive) {
8401         SendTimeControl(cps, movesPerSession, timeControl,
8402                         timeIncrement, appData.searchDepth,
8403                         searchTime);
8404     }
8405     if (appData.showThinking 
8406         // [HGM] thinking: four options require thinking output to be sent
8407         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8408                                 ) {
8409         SendToProgram("post\n", cps);
8410     }
8411     SendToProgram("hard\n", cps);
8412     if (!appData.ponderNextMove) {
8413         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8414            it without being sure what state we are in first.  "hard"
8415            is not a toggle, so that one is OK.
8416          */
8417         SendToProgram("easy\n", cps);
8418     }
8419     if (cps->usePing) {
8420       sprintf(buf, "ping %d\n", ++cps->lastPing);
8421       SendToProgram(buf, cps);
8422     }
8423     cps->initDone = TRUE;
8424 }   
8425
8426
8427 void
8428 StartChessProgram(cps)
8429      ChessProgramState *cps;
8430 {
8431     char buf[MSG_SIZ];
8432     int err;
8433
8434     if (appData.noChessProgram) return;
8435     cps->initDone = FALSE;
8436
8437     if (strcmp(cps->host, "localhost") == 0) {
8438         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8439     } else if (*appData.remoteShell == NULLCHAR) {
8440         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8441     } else {
8442         if (*appData.remoteUser == NULLCHAR) {
8443           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8444                     cps->program);
8445         } else {
8446           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8447                     cps->host, appData.remoteUser, cps->program);
8448         }
8449         err = StartChildProcess(buf, "", &cps->pr);
8450     }
8451     
8452     if (err != 0) {
8453         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8454         DisplayFatalError(buf, err, 1);
8455         cps->pr = NoProc;
8456         cps->isr = NULL;
8457         return;
8458     }
8459     
8460     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8461     if (cps->protocolVersion > 1) {
8462       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8463       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8464       cps->comboCnt = 0;  //                and values of combo boxes
8465       SendToProgram(buf, cps);
8466     } else {
8467       SendToProgram("xboard\n", cps);
8468     }
8469 }
8470
8471
8472 void
8473 TwoMachinesEventIfReady P((void))
8474 {
8475   if (first.lastPing != first.lastPong) {
8476     DisplayMessage("", _("Waiting for first chess program"));
8477     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8478     return;
8479   }
8480   if (second.lastPing != second.lastPong) {
8481     DisplayMessage("", _("Waiting for second chess program"));
8482     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8483     return;
8484   }
8485   ThawUI();
8486   TwoMachinesEvent();
8487 }
8488
8489 void
8490 NextMatchGame P((void))
8491 {
8492     int index; /* [HGM] autoinc: step load index during match */
8493     Reset(FALSE, TRUE);
8494     if (*appData.loadGameFile != NULLCHAR) {
8495         index = appData.loadGameIndex;
8496         if(index < 0) { // [HGM] autoinc
8497             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8498             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8499         } 
8500         LoadGameFromFile(appData.loadGameFile,
8501                          index,
8502                          appData.loadGameFile, FALSE);
8503     } else if (*appData.loadPositionFile != NULLCHAR) {
8504         index = appData.loadPositionIndex;
8505         if(index < 0) { // [HGM] autoinc
8506             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8507             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8508         } 
8509         LoadPositionFromFile(appData.loadPositionFile,
8510                              index,
8511                              appData.loadPositionFile);
8512     }
8513     TwoMachinesEventIfReady();
8514 }
8515
8516 void UserAdjudicationEvent( int result )
8517 {
8518     ChessMove gameResult = GameIsDrawn;
8519
8520     if( result > 0 ) {
8521         gameResult = WhiteWins;
8522     }
8523     else if( result < 0 ) {
8524         gameResult = BlackWins;
8525     }
8526
8527     if( gameMode == TwoMachinesPlay ) {
8528         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8529     }
8530 }
8531
8532
8533 // [HGM] save: calculate checksum of game to make games easily identifiable
8534 int StringCheckSum(char *s)
8535 {
8536         int i = 0;
8537         if(s==NULL) return 0;
8538         while(*s) i = i*259 + *s++;
8539         return i;
8540 }
8541
8542 int GameCheckSum()
8543 {
8544         int i, sum=0;
8545         for(i=backwardMostMove; i<forwardMostMove; i++) {
8546                 sum += pvInfoList[i].depth;
8547                 sum += StringCheckSum(parseList[i]);
8548                 sum += StringCheckSum(commentList[i]);
8549                 sum *= 261;
8550         }
8551         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8552         return sum + StringCheckSum(commentList[i]);
8553 } // end of save patch
8554
8555 void
8556 GameEnds(result, resultDetails, whosays)
8557      ChessMove result;
8558      char *resultDetails;
8559      int whosays;
8560 {
8561     GameMode nextGameMode;
8562     int isIcsGame;
8563     char buf[MSG_SIZ];
8564
8565     if(endingGame) return; /* [HGM] crash: forbid recursion */
8566     endingGame = 1;
8567
8568     if (appData.debugMode) {
8569       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8570               result, resultDetails ? resultDetails : "(null)", whosays);
8571     }
8572
8573     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8574         /* If we are playing on ICS, the server decides when the
8575            game is over, but the engine can offer to draw, claim 
8576            a draw, or resign. 
8577          */
8578 #if ZIPPY
8579         if (appData.zippyPlay && first.initDone) {
8580             if (result == GameIsDrawn) {
8581                 /* In case draw still needs to be claimed */
8582                 SendToICS(ics_prefix);
8583                 SendToICS("draw\n");
8584             } else if (StrCaseStr(resultDetails, "resign")) {
8585                 SendToICS(ics_prefix);
8586                 SendToICS("resign\n");
8587             }
8588         }
8589 #endif
8590         endingGame = 0; /* [HGM] crash */
8591         return;
8592     }
8593
8594     /* If we're loading the game from a file, stop */
8595     if (whosays == GE_FILE) {
8596       (void) StopLoadGameTimer();
8597       gameFileFP = NULL;
8598     }
8599
8600     /* Cancel draw offers */
8601     first.offeredDraw = second.offeredDraw = 0;
8602
8603     /* If this is an ICS game, only ICS can really say it's done;
8604        if not, anyone can. */
8605     isIcsGame = (gameMode == IcsPlayingWhite || 
8606                  gameMode == IcsPlayingBlack || 
8607                  gameMode == IcsObserving    || 
8608                  gameMode == IcsExamining);
8609
8610     if (!isIcsGame || whosays == GE_ICS) {
8611         /* OK -- not an ICS game, or ICS said it was done */
8612         StopClocks();
8613         if (!isIcsGame && !appData.noChessProgram) 
8614           SetUserThinkingEnables();
8615     
8616         /* [HGM] if a machine claims the game end we verify this claim */
8617         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8618             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8619                 char claimer;
8620                 ChessMove trueResult = (ChessMove) -1;
8621
8622                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8623                                             first.twoMachinesColor[0] :
8624                                             second.twoMachinesColor[0] ;
8625
8626                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8627                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8628                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8629                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8630                 } else
8631                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8632                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8633                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8634                 } else
8635                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8636                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8637                 }
8638
8639                 // now verify win claims, but not in drop games, as we don't understand those yet
8640                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8641                                                  || gameInfo.variant == VariantGreat) &&
8642                     (result == WhiteWins && claimer == 'w' ||
8643                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8644                       if (appData.debugMode) {
8645                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8646                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8647                       }
8648                       if(result != trueResult) {
8649                               sprintf(buf, "False win claim: '%s'", resultDetails);
8650                               result = claimer == 'w' ? BlackWins : WhiteWins;
8651                               resultDetails = buf;
8652                       }
8653                 } else
8654                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8655                     && (forwardMostMove <= backwardMostMove ||
8656                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8657                         (claimer=='b')==(forwardMostMove&1))
8658                                                                                   ) {
8659                       /* [HGM] verify: draws that were not flagged are false claims */
8660                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8661                       result = claimer == 'w' ? BlackWins : WhiteWins;
8662                       resultDetails = buf;
8663                 }
8664                 /* (Claiming a loss is accepted no questions asked!) */
8665             }
8666             /* [HGM] bare: don't allow bare King to win */
8667             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8668                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8669                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8670                && result != GameIsDrawn)
8671             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8672                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8673                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8674                         if(p >= 0 && p <= (int)WhiteKing) k++;
8675                 }
8676                 if (appData.debugMode) {
8677                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8678                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8679                 }
8680                 if(k <= 1) {
8681                         result = GameIsDrawn;
8682                         sprintf(buf, "%s but bare king", resultDetails);
8683                         resultDetails = buf;
8684                 }
8685             }
8686         }
8687
8688
8689         if(serverMoves != NULL && !loadFlag) { char c = '=';
8690             if(result==WhiteWins) c = '+';
8691             if(result==BlackWins) c = '-';
8692             if(resultDetails != NULL)
8693                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8694         }
8695         if (resultDetails != NULL) {
8696             gameInfo.result = result;
8697             gameInfo.resultDetails = StrSave(resultDetails);
8698
8699             /* display last move only if game was not loaded from file */
8700             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8701                 DisplayMove(currentMove - 1);
8702     
8703             if (forwardMostMove != 0) {
8704                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8705                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8706                                                                 ) {
8707                     if (*appData.saveGameFile != NULLCHAR) {
8708                         SaveGameToFile(appData.saveGameFile, TRUE);
8709                     } else if (appData.autoSaveGames) {
8710                         AutoSaveGame();
8711                     }
8712                     if (*appData.savePositionFile != NULLCHAR) {
8713                         SavePositionToFile(appData.savePositionFile);
8714                     }
8715                 }
8716             }
8717
8718             /* Tell program how game ended in case it is learning */
8719             /* [HGM] Moved this to after saving the PGN, just in case */
8720             /* engine died and we got here through time loss. In that */
8721             /* case we will get a fatal error writing the pipe, which */
8722             /* would otherwise lose us the PGN.                       */
8723             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8724             /* output during GameEnds should never be fatal anymore   */
8725             if (gameMode == MachinePlaysWhite ||
8726                 gameMode == MachinePlaysBlack ||
8727                 gameMode == TwoMachinesPlay ||
8728                 gameMode == IcsPlayingWhite ||
8729                 gameMode == IcsPlayingBlack ||
8730                 gameMode == BeginningOfGame) {
8731                 char buf[MSG_SIZ];
8732                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8733                         resultDetails);
8734                 if (first.pr != NoProc) {
8735                     SendToProgram(buf, &first);
8736                 }
8737                 if (second.pr != NoProc &&
8738                     gameMode == TwoMachinesPlay) {
8739                     SendToProgram(buf, &second);
8740                 }
8741             }
8742         }
8743
8744         if (appData.icsActive) {
8745             if (appData.quietPlay &&
8746                 (gameMode == IcsPlayingWhite ||
8747                  gameMode == IcsPlayingBlack)) {
8748                 SendToICS(ics_prefix);
8749                 SendToICS("set shout 1\n");
8750             }
8751             nextGameMode = IcsIdle;
8752             ics_user_moved = FALSE;
8753             /* clean up premove.  It's ugly when the game has ended and the
8754              * premove highlights are still on the board.
8755              */
8756             if (gotPremove) {
8757               gotPremove = FALSE;
8758               ClearPremoveHighlights();
8759               DrawPosition(FALSE, boards[currentMove]);
8760             }
8761             if (whosays == GE_ICS) {
8762                 switch (result) {
8763                 case WhiteWins:
8764                     if (gameMode == IcsPlayingWhite)
8765                         PlayIcsWinSound();
8766                     else if(gameMode == IcsPlayingBlack)
8767                         PlayIcsLossSound();
8768                     break;
8769                 case BlackWins:
8770                     if (gameMode == IcsPlayingBlack)
8771                         PlayIcsWinSound();
8772                     else if(gameMode == IcsPlayingWhite)
8773                         PlayIcsLossSound();
8774                     break;
8775                 case GameIsDrawn:
8776                     PlayIcsDrawSound();
8777                     break;
8778                 default:
8779                     PlayIcsUnfinishedSound();
8780                 }
8781             }
8782         } else if (gameMode == EditGame ||
8783                    gameMode == PlayFromGameFile || 
8784                    gameMode == AnalyzeMode || 
8785                    gameMode == AnalyzeFile) {
8786             nextGameMode = gameMode;
8787         } else {
8788             nextGameMode = EndOfGame;
8789         }
8790         pausing = FALSE;
8791         ModeHighlight();
8792     } else {
8793         nextGameMode = gameMode;
8794     }
8795
8796     if (appData.noChessProgram) {
8797         gameMode = nextGameMode;
8798         ModeHighlight();
8799         endingGame = 0; /* [HGM] crash */
8800         return;
8801     }
8802
8803     if (first.reuse) {
8804         /* Put first chess program into idle state */
8805         if (first.pr != NoProc &&
8806             (gameMode == MachinePlaysWhite ||
8807              gameMode == MachinePlaysBlack ||
8808              gameMode == TwoMachinesPlay ||
8809              gameMode == IcsPlayingWhite ||
8810              gameMode == IcsPlayingBlack ||
8811              gameMode == BeginningOfGame)) {
8812             SendToProgram("force\n", &first);
8813             if (first.usePing) {
8814               char buf[MSG_SIZ];
8815               sprintf(buf, "ping %d\n", ++first.lastPing);
8816               SendToProgram(buf, &first);
8817             }
8818         }
8819     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8820         /* Kill off first chess program */
8821         if (first.isr != NULL)
8822           RemoveInputSource(first.isr);
8823         first.isr = NULL;
8824     
8825         if (first.pr != NoProc) {
8826             ExitAnalyzeMode();
8827             DoSleep( appData.delayBeforeQuit );
8828             SendToProgram("quit\n", &first);
8829             DoSleep( appData.delayAfterQuit );
8830             DestroyChildProcess(first.pr, first.useSigterm);
8831         }
8832         first.pr = NoProc;
8833     }
8834     if (second.reuse) {
8835         /* Put second chess program into idle state */
8836         if (second.pr != NoProc &&
8837             gameMode == TwoMachinesPlay) {
8838             SendToProgram("force\n", &second);
8839             if (second.usePing) {
8840               char buf[MSG_SIZ];
8841               sprintf(buf, "ping %d\n", ++second.lastPing);
8842               SendToProgram(buf, &second);
8843             }
8844         }
8845     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8846         /* Kill off second chess program */
8847         if (second.isr != NULL)
8848           RemoveInputSource(second.isr);
8849         second.isr = NULL;
8850     
8851         if (second.pr != NoProc) {
8852             DoSleep( appData.delayBeforeQuit );
8853             SendToProgram("quit\n", &second);
8854             DoSleep( appData.delayAfterQuit );
8855             DestroyChildProcess(second.pr, second.useSigterm);
8856         }
8857         second.pr = NoProc;
8858     }
8859
8860     if (matchMode && gameMode == TwoMachinesPlay) {
8861         switch (result) {
8862         case WhiteWins:
8863           if (first.twoMachinesColor[0] == 'w') {
8864             first.matchWins++;
8865           } else {
8866             second.matchWins++;
8867           }
8868           break;
8869         case BlackWins:
8870           if (first.twoMachinesColor[0] == 'b') {
8871             first.matchWins++;
8872           } else {
8873             second.matchWins++;
8874           }
8875           break;
8876         default:
8877           break;
8878         }
8879         if (matchGame < appData.matchGames) {
8880             char *tmp;
8881             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8882                 tmp = first.twoMachinesColor;
8883                 first.twoMachinesColor = second.twoMachinesColor;
8884                 second.twoMachinesColor = tmp;
8885             }
8886             gameMode = nextGameMode;
8887             matchGame++;
8888             if(appData.matchPause>10000 || appData.matchPause<10)
8889                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8890             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8891             endingGame = 0; /* [HGM] crash */
8892             return;
8893         } else {
8894             char buf[MSG_SIZ];
8895             gameMode = nextGameMode;
8896             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8897                     first.tidy, second.tidy,
8898                     first.matchWins, second.matchWins,
8899                     appData.matchGames - (first.matchWins + second.matchWins));
8900             DisplayFatalError(buf, 0, 0);
8901         }
8902     }
8903     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8904         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8905       ExitAnalyzeMode();
8906     gameMode = nextGameMode;
8907     ModeHighlight();
8908     endingGame = 0;  /* [HGM] crash */
8909 }
8910
8911 /* Assumes program was just initialized (initString sent).
8912    Leaves program in force mode. */
8913 void
8914 FeedMovesToProgram(cps, upto) 
8915      ChessProgramState *cps;
8916      int upto;
8917 {
8918     int i;
8919     
8920     if (appData.debugMode)
8921       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8922               startedFromSetupPosition ? "position and " : "",
8923               backwardMostMove, upto, cps->which);
8924     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8925         // [HGM] variantswitch: make engine aware of new variant
8926         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8927                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8928         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8929         SendToProgram(buf, cps);
8930         currentlyInitializedVariant = gameInfo.variant;
8931     }
8932     SendToProgram("force\n", cps);
8933     if (startedFromSetupPosition) {
8934         SendBoard(cps, backwardMostMove);
8935     if (appData.debugMode) {
8936         fprintf(debugFP, "feedMoves\n");
8937     }
8938     }
8939     for (i = backwardMostMove; i < upto; i++) {
8940         SendMoveToProgram(i, cps);
8941     }
8942 }
8943
8944
8945 void
8946 ResurrectChessProgram()
8947 {
8948      /* The chess program may have exited.
8949         If so, restart it and feed it all the moves made so far. */
8950
8951     if (appData.noChessProgram || first.pr != NoProc) return;
8952     
8953     StartChessProgram(&first);
8954     InitChessProgram(&first, FALSE);
8955     FeedMovesToProgram(&first, currentMove);
8956
8957     if (!first.sendTime) {
8958         /* can't tell gnuchess what its clock should read,
8959            so we bow to its notion. */
8960         ResetClocks();
8961         timeRemaining[0][currentMove] = whiteTimeRemaining;
8962         timeRemaining[1][currentMove] = blackTimeRemaining;
8963     }
8964
8965     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8966                 appData.icsEngineAnalyze) && first.analysisSupport) {
8967       SendToProgram("analyze\n", &first);
8968       first.analyzing = TRUE;
8969     }
8970 }
8971
8972 /*
8973  * Button procedures
8974  */
8975 void
8976 Reset(redraw, init)
8977      int redraw, init;
8978 {
8979     int i;
8980
8981     if (appData.debugMode) {
8982         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8983                 redraw, init, gameMode);
8984     }
8985     CleanupTail(); // [HGM] vari: delete any stored variations
8986     pausing = pauseExamInvalid = FALSE;
8987     startedFromSetupPosition = blackPlaysFirst = FALSE;
8988     firstMove = TRUE;
8989     whiteFlag = blackFlag = FALSE;
8990     userOfferedDraw = FALSE;
8991     hintRequested = bookRequested = FALSE;
8992     first.maybeThinking = FALSE;
8993     second.maybeThinking = FALSE;
8994     first.bookSuspend = FALSE; // [HGM] book
8995     second.bookSuspend = FALSE;
8996     thinkOutput[0] = NULLCHAR;
8997     lastHint[0] = NULLCHAR;
8998     ClearGameInfo(&gameInfo);
8999     gameInfo.variant = StringToVariant(appData.variant);
9000     ics_user_moved = ics_clock_paused = FALSE;
9001     ics_getting_history = H_FALSE;
9002     ics_gamenum = -1;
9003     white_holding[0] = black_holding[0] = NULLCHAR;
9004     ClearProgramStats();
9005     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9006     
9007     ResetFrontEnd();
9008     ClearHighlights();
9009     flipView = appData.flipView;
9010     ClearPremoveHighlights();
9011     gotPremove = FALSE;
9012     alarmSounded = FALSE;
9013
9014     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9015     if(appData.serverMovesName != NULL) {
9016         /* [HGM] prepare to make moves file for broadcasting */
9017         clock_t t = clock();
9018         if(serverMoves != NULL) fclose(serverMoves);
9019         serverMoves = fopen(appData.serverMovesName, "r");
9020         if(serverMoves != NULL) {
9021             fclose(serverMoves);
9022             /* delay 15 sec before overwriting, so all clients can see end */
9023             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9024         }
9025         serverMoves = fopen(appData.serverMovesName, "w");
9026     }
9027
9028     ExitAnalyzeMode();
9029     gameMode = BeginningOfGame;
9030     ModeHighlight();
9031     if(appData.icsActive) gameInfo.variant = VariantNormal;
9032     currentMove = forwardMostMove = backwardMostMove = 0;
9033     InitPosition(redraw);
9034     for (i = 0; i < MAX_MOVES; i++) {
9035         if (commentList[i] != NULL) {
9036             free(commentList[i]);
9037             commentList[i] = NULL;
9038         }
9039     }
9040     ResetClocks();
9041     timeRemaining[0][0] = whiteTimeRemaining;
9042     timeRemaining[1][0] = blackTimeRemaining;
9043     if (first.pr == NULL) {
9044         StartChessProgram(&first);
9045     }
9046     if (init) {
9047             InitChessProgram(&first, startedFromSetupPosition);
9048     }
9049     DisplayTitle("");
9050     DisplayMessage("", "");
9051     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9052     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9053 }
9054
9055 void
9056 AutoPlayGameLoop()
9057 {
9058     for (;;) {
9059         if (!AutoPlayOneMove())
9060           return;
9061         if (matchMode || appData.timeDelay == 0)
9062           continue;
9063         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9064           return;
9065         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9066         break;
9067     }
9068 }
9069
9070
9071 int
9072 AutoPlayOneMove()
9073 {
9074     int fromX, fromY, toX, toY;
9075
9076     if (appData.debugMode) {
9077       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9078     }
9079
9080     if (gameMode != PlayFromGameFile)
9081       return FALSE;
9082
9083     if (currentMove >= forwardMostMove) {
9084       gameMode = EditGame;
9085       ModeHighlight();
9086
9087       /* [AS] Clear current move marker at the end of a game */
9088       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9089
9090       return FALSE;
9091     }
9092     
9093     toX = moveList[currentMove][2] - AAA;
9094     toY = moveList[currentMove][3] - ONE;
9095
9096     if (moveList[currentMove][1] == '@') {
9097         if (appData.highlightLastMove) {
9098             SetHighlights(-1, -1, toX, toY);
9099         }
9100     } else {
9101         fromX = moveList[currentMove][0] - AAA;
9102         fromY = moveList[currentMove][1] - ONE;
9103
9104         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9105
9106         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9107
9108         if (appData.highlightLastMove) {
9109             SetHighlights(fromX, fromY, toX, toY);
9110         }
9111     }
9112     DisplayMove(currentMove);
9113     SendMoveToProgram(currentMove++, &first);
9114     DisplayBothClocks();
9115     DrawPosition(FALSE, boards[currentMove]);
9116     // [HGM] PV info: always display, routine tests if empty
9117     DisplayComment(currentMove - 1, commentList[currentMove]);
9118     return TRUE;
9119 }
9120
9121
9122 int
9123 LoadGameOneMove(readAhead)
9124      ChessMove readAhead;
9125 {
9126     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9127     char promoChar = NULLCHAR;
9128     ChessMove moveType;
9129     char move[MSG_SIZ];
9130     char *p, *q;
9131     
9132     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9133         gameMode != AnalyzeMode && gameMode != Training) {
9134         gameFileFP = NULL;
9135         return FALSE;
9136     }
9137     
9138     yyboardindex = forwardMostMove;
9139     if (readAhead != (ChessMove)0) {
9140       moveType = readAhead;
9141     } else {
9142       if (gameFileFP == NULL)
9143           return FALSE;
9144       moveType = (ChessMove) yylex();
9145     }
9146     
9147     done = FALSE;
9148     switch (moveType) {
9149       case Comment:
9150         if (appData.debugMode) 
9151           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9152         p = yy_text;
9153
9154         /* append the comment but don't display it */
9155         AppendComment(currentMove, p, FALSE);
9156         return TRUE;
9157
9158       case WhiteCapturesEnPassant:
9159       case BlackCapturesEnPassant:
9160       case WhitePromotionChancellor:
9161       case BlackPromotionChancellor:
9162       case WhitePromotionArchbishop:
9163       case BlackPromotionArchbishop:
9164       case WhitePromotionCentaur:
9165       case BlackPromotionCentaur:
9166       case WhitePromotionQueen:
9167       case BlackPromotionQueen:
9168       case WhitePromotionRook:
9169       case BlackPromotionRook:
9170       case WhitePromotionBishop:
9171       case BlackPromotionBishop:
9172       case WhitePromotionKnight:
9173       case BlackPromotionKnight:
9174       case WhitePromotionKing:
9175       case BlackPromotionKing:
9176       case NormalMove:
9177       case WhiteKingSideCastle:
9178       case WhiteQueenSideCastle:
9179       case BlackKingSideCastle:
9180       case BlackQueenSideCastle:
9181       case WhiteKingSideCastleWild:
9182       case WhiteQueenSideCastleWild:
9183       case BlackKingSideCastleWild:
9184       case BlackQueenSideCastleWild:
9185       /* PUSH Fabien */
9186       case WhiteHSideCastleFR:
9187       case WhiteASideCastleFR:
9188       case BlackHSideCastleFR:
9189       case BlackASideCastleFR:
9190       /* POP Fabien */
9191         if (appData.debugMode)
9192           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9193         fromX = currentMoveString[0] - AAA;
9194         fromY = currentMoveString[1] - ONE;
9195         toX = currentMoveString[2] - AAA;
9196         toY = currentMoveString[3] - ONE;
9197         promoChar = currentMoveString[4];
9198         break;
9199
9200       case WhiteDrop:
9201       case BlackDrop:
9202         if (appData.debugMode)
9203           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9204         fromX = moveType == WhiteDrop ?
9205           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9206         (int) CharToPiece(ToLower(currentMoveString[0]));
9207         fromY = DROP_RANK;
9208         toX = currentMoveString[2] - AAA;
9209         toY = currentMoveString[3] - ONE;
9210         break;
9211
9212       case WhiteWins:
9213       case BlackWins:
9214       case GameIsDrawn:
9215       case GameUnfinished:
9216         if (appData.debugMode)
9217           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9218         p = strchr(yy_text, '{');
9219         if (p == NULL) p = strchr(yy_text, '(');
9220         if (p == NULL) {
9221             p = yy_text;
9222             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9223         } else {
9224             q = strchr(p, *p == '{' ? '}' : ')');
9225             if (q != NULL) *q = NULLCHAR;
9226             p++;
9227         }
9228         GameEnds(moveType, p, GE_FILE);
9229         done = TRUE;
9230         if (cmailMsgLoaded) {
9231             ClearHighlights();
9232             flipView = WhiteOnMove(currentMove);
9233             if (moveType == GameUnfinished) flipView = !flipView;
9234             if (appData.debugMode)
9235               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9236         }
9237         break;
9238
9239       case (ChessMove) 0:       /* end of file */
9240         if (appData.debugMode)
9241           fprintf(debugFP, "Parser hit end of file\n");
9242         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9243           case MT_NONE:
9244           case MT_CHECK:
9245             break;
9246           case MT_CHECKMATE:
9247           case MT_STAINMATE:
9248             if (WhiteOnMove(currentMove)) {
9249                 GameEnds(BlackWins, "Black mates", GE_FILE);
9250             } else {
9251                 GameEnds(WhiteWins, "White mates", GE_FILE);
9252             }
9253             break;
9254           case MT_STALEMATE:
9255             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9256             break;
9257         }
9258         done = TRUE;
9259         break;
9260
9261       case MoveNumberOne:
9262         if (lastLoadGameStart == GNUChessGame) {
9263             /* GNUChessGames have numbers, but they aren't move numbers */
9264             if (appData.debugMode)
9265               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9266                       yy_text, (int) moveType);
9267             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9268         }
9269         /* else fall thru */
9270
9271       case XBoardGame:
9272       case GNUChessGame:
9273       case PGNTag:
9274         /* Reached start of next game in file */
9275         if (appData.debugMode)
9276           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9277         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9278           case MT_NONE:
9279           case MT_CHECK:
9280             break;
9281           case MT_CHECKMATE:
9282           case MT_STAINMATE:
9283             if (WhiteOnMove(currentMove)) {
9284                 GameEnds(BlackWins, "Black mates", GE_FILE);
9285             } else {
9286                 GameEnds(WhiteWins, "White mates", GE_FILE);
9287             }
9288             break;
9289           case MT_STALEMATE:
9290             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9291             break;
9292         }
9293         done = TRUE;
9294         break;
9295
9296       case PositionDiagram:     /* should not happen; ignore */
9297       case ElapsedTime:         /* ignore */
9298       case NAG:                 /* ignore */
9299         if (appData.debugMode)
9300           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9301                   yy_text, (int) moveType);
9302         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9303
9304       case IllegalMove:
9305         if (appData.testLegality) {
9306             if (appData.debugMode)
9307               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9308             sprintf(move, _("Illegal move: %d.%s%s"),
9309                     (forwardMostMove / 2) + 1,
9310                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9311             DisplayError(move, 0);
9312             done = TRUE;
9313         } else {
9314             if (appData.debugMode)
9315               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9316                       yy_text, currentMoveString);
9317             fromX = currentMoveString[0] - AAA;
9318             fromY = currentMoveString[1] - ONE;
9319             toX = currentMoveString[2] - AAA;
9320             toY = currentMoveString[3] - ONE;
9321             promoChar = currentMoveString[4];
9322         }
9323         break;
9324
9325       case AmbiguousMove:
9326         if (appData.debugMode)
9327           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9328         sprintf(move, _("Ambiguous move: %d.%s%s"),
9329                 (forwardMostMove / 2) + 1,
9330                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9331         DisplayError(move, 0);
9332         done = TRUE;
9333         break;
9334
9335       default:
9336       case ImpossibleMove:
9337         if (appData.debugMode)
9338           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9339         sprintf(move, _("Illegal move: %d.%s%s"),
9340                 (forwardMostMove / 2) + 1,
9341                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9342         DisplayError(move, 0);
9343         done = TRUE;
9344         break;
9345     }
9346
9347     if (done) {
9348         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9349             DrawPosition(FALSE, boards[currentMove]);
9350             DisplayBothClocks();
9351             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9352               DisplayComment(currentMove - 1, commentList[currentMove]);
9353         }
9354         (void) StopLoadGameTimer();
9355         gameFileFP = NULL;
9356         cmailOldMove = forwardMostMove;
9357         return FALSE;
9358     } else {
9359         /* currentMoveString is set as a side-effect of yylex */
9360         strcat(currentMoveString, "\n");
9361         strcpy(moveList[forwardMostMove], currentMoveString);
9362         
9363         thinkOutput[0] = NULLCHAR;
9364         MakeMove(fromX, fromY, toX, toY, promoChar);
9365         currentMove = forwardMostMove;
9366         return TRUE;
9367     }
9368 }
9369
9370 /* Load the nth game from the given file */
9371 int
9372 LoadGameFromFile(filename, n, title, useList)
9373      char *filename;
9374      int n;
9375      char *title;
9376      /*Boolean*/ int useList;
9377 {
9378     FILE *f;
9379     char buf[MSG_SIZ];
9380
9381     if (strcmp(filename, "-") == 0) {
9382         f = stdin;
9383         title = "stdin";
9384     } else {
9385         f = fopen(filename, "rb");
9386         if (f == NULL) {
9387           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9388             DisplayError(buf, errno);
9389             return FALSE;
9390         }
9391     }
9392     if (fseek(f, 0, 0) == -1) {
9393         /* f is not seekable; probably a pipe */
9394         useList = FALSE;
9395     }
9396     if (useList && n == 0) {
9397         int error = GameListBuild(f);
9398         if (error) {
9399             DisplayError(_("Cannot build game list"), error);
9400         } else if (!ListEmpty(&gameList) &&
9401                    ((ListGame *) gameList.tailPred)->number > 1) {
9402             GameListPopUp(f, title);
9403             return TRUE;
9404         }
9405         GameListDestroy();
9406         n = 1;
9407     }
9408     if (n == 0) n = 1;
9409     return LoadGame(f, n, title, FALSE);
9410 }
9411
9412
9413 void
9414 MakeRegisteredMove()
9415 {
9416     int fromX, fromY, toX, toY;
9417     char promoChar;
9418     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9419         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9420           case CMAIL_MOVE:
9421           case CMAIL_DRAW:
9422             if (appData.debugMode)
9423               fprintf(debugFP, "Restoring %s for game %d\n",
9424                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9425     
9426             thinkOutput[0] = NULLCHAR;
9427             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9428             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9429             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9430             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9431             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9432             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9433             MakeMove(fromX, fromY, toX, toY, promoChar);
9434             ShowMove(fromX, fromY, toX, toY);
9435               
9436             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9437               case MT_NONE:
9438               case MT_CHECK:
9439                 break;
9440                 
9441               case MT_CHECKMATE:
9442               case MT_STAINMATE:
9443                 if (WhiteOnMove(currentMove)) {
9444                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9445                 } else {
9446                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9447                 }
9448                 break;
9449                 
9450               case MT_STALEMATE:
9451                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9452                 break;
9453             }
9454
9455             break;
9456             
9457           case CMAIL_RESIGN:
9458             if (WhiteOnMove(currentMove)) {
9459                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9460             } else {
9461                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9462             }
9463             break;
9464             
9465           case CMAIL_ACCEPT:
9466             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9467             break;
9468               
9469           default:
9470             break;
9471         }
9472     }
9473
9474     return;
9475 }
9476
9477 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9478 int
9479 CmailLoadGame(f, gameNumber, title, useList)
9480      FILE *f;
9481      int gameNumber;
9482      char *title;
9483      int useList;
9484 {
9485     int retVal;
9486
9487     if (gameNumber > nCmailGames) {
9488         DisplayError(_("No more games in this message"), 0);
9489         return FALSE;
9490     }
9491     if (f == lastLoadGameFP) {
9492         int offset = gameNumber - lastLoadGameNumber;
9493         if (offset == 0) {
9494             cmailMsg[0] = NULLCHAR;
9495             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9496                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9497                 nCmailMovesRegistered--;
9498             }
9499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9500             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9501                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9502             }
9503         } else {
9504             if (! RegisterMove()) return FALSE;
9505         }
9506     }
9507
9508     retVal = LoadGame(f, gameNumber, title, useList);
9509
9510     /* Make move registered during previous look at this game, if any */
9511     MakeRegisteredMove();
9512
9513     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9514         commentList[currentMove]
9515           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9516         DisplayComment(currentMove - 1, commentList[currentMove]);
9517     }
9518
9519     return retVal;
9520 }
9521
9522 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9523 int
9524 ReloadGame(offset)
9525      int offset;
9526 {
9527     int gameNumber = lastLoadGameNumber + offset;
9528     if (lastLoadGameFP == NULL) {
9529         DisplayError(_("No game has been loaded yet"), 0);
9530         return FALSE;
9531     }
9532     if (gameNumber <= 0) {
9533         DisplayError(_("Can't back up any further"), 0);
9534         return FALSE;
9535     }
9536     if (cmailMsgLoaded) {
9537         return CmailLoadGame(lastLoadGameFP, gameNumber,
9538                              lastLoadGameTitle, lastLoadGameUseList);
9539     } else {
9540         return LoadGame(lastLoadGameFP, gameNumber,
9541                         lastLoadGameTitle, lastLoadGameUseList);
9542     }
9543 }
9544
9545
9546
9547 /* Load the nth game from open file f */
9548 int
9549 LoadGame(f, gameNumber, title, useList)
9550      FILE *f;
9551      int gameNumber;
9552      char *title;
9553      int useList;
9554 {
9555     ChessMove cm;
9556     char buf[MSG_SIZ];
9557     int gn = gameNumber;
9558     ListGame *lg = NULL;
9559     int numPGNTags = 0;
9560     int err;
9561     GameMode oldGameMode;
9562     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9563
9564     if (appData.debugMode) 
9565         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9566
9567     if (gameMode == Training )
9568         SetTrainingModeOff();
9569
9570     oldGameMode = gameMode;
9571     if (gameMode != BeginningOfGame) {
9572       Reset(FALSE, TRUE);
9573     }
9574
9575     gameFileFP = f;
9576     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9577         fclose(lastLoadGameFP);
9578     }
9579
9580     if (useList) {
9581         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9582         
9583         if (lg) {
9584             fseek(f, lg->offset, 0);
9585             GameListHighlight(gameNumber);
9586             gn = 1;
9587         }
9588         else {
9589             DisplayError(_("Game number out of range"), 0);
9590             return FALSE;
9591         }
9592     } else {
9593         GameListDestroy();
9594         if (fseek(f, 0, 0) == -1) {
9595             if (f == lastLoadGameFP ?
9596                 gameNumber == lastLoadGameNumber + 1 :
9597                 gameNumber == 1) {
9598                 gn = 1;
9599             } else {
9600                 DisplayError(_("Can't seek on game file"), 0);
9601                 return FALSE;
9602             }
9603         }
9604     }
9605     lastLoadGameFP = f;
9606     lastLoadGameNumber = gameNumber;
9607     strcpy(lastLoadGameTitle, title);
9608     lastLoadGameUseList = useList;
9609
9610     yynewfile(f);
9611
9612     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9613       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9614                 lg->gameInfo.black);
9615             DisplayTitle(buf);
9616     } else if (*title != NULLCHAR) {
9617         if (gameNumber > 1) {
9618             sprintf(buf, "%s %d", title, gameNumber);
9619             DisplayTitle(buf);
9620         } else {
9621             DisplayTitle(title);
9622         }
9623     }
9624
9625     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9626         gameMode = PlayFromGameFile;
9627         ModeHighlight();
9628     }
9629
9630     currentMove = forwardMostMove = backwardMostMove = 0;
9631     CopyBoard(boards[0], initialPosition);
9632     StopClocks();
9633
9634     /*
9635      * Skip the first gn-1 games in the file.
9636      * Also skip over anything that precedes an identifiable 
9637      * start of game marker, to avoid being confused by 
9638      * garbage at the start of the file.  Currently 
9639      * recognized start of game markers are the move number "1",
9640      * the pattern "gnuchess .* game", the pattern
9641      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9642      * A game that starts with one of the latter two patterns
9643      * will also have a move number 1, possibly
9644      * following a position diagram.
9645      * 5-4-02: Let's try being more lenient and allowing a game to
9646      * start with an unnumbered move.  Does that break anything?
9647      */
9648     cm = lastLoadGameStart = (ChessMove) 0;
9649     while (gn > 0) {
9650         yyboardindex = forwardMostMove;
9651         cm = (ChessMove) yylex();
9652         switch (cm) {
9653           case (ChessMove) 0:
9654             if (cmailMsgLoaded) {
9655                 nCmailGames = CMAIL_MAX_GAMES - gn;
9656             } else {
9657                 Reset(TRUE, TRUE);
9658                 DisplayError(_("Game not found in file"), 0);
9659             }
9660             return FALSE;
9661
9662           case GNUChessGame:
9663           case XBoardGame:
9664             gn--;
9665             lastLoadGameStart = cm;
9666             break;
9667             
9668           case MoveNumberOne:
9669             switch (lastLoadGameStart) {
9670               case GNUChessGame:
9671               case XBoardGame:
9672               case PGNTag:
9673                 break;
9674               case MoveNumberOne:
9675               case (ChessMove) 0:
9676                 gn--;           /* count this game */
9677                 lastLoadGameStart = cm;
9678                 break;
9679               default:
9680                 /* impossible */
9681                 break;
9682             }
9683             break;
9684
9685           case PGNTag:
9686             switch (lastLoadGameStart) {
9687               case GNUChessGame:
9688               case PGNTag:
9689               case MoveNumberOne:
9690               case (ChessMove) 0:
9691                 gn--;           /* count this game */
9692                 lastLoadGameStart = cm;
9693                 break;
9694               case XBoardGame:
9695                 lastLoadGameStart = cm; /* game counted already */
9696                 break;
9697               default:
9698                 /* impossible */
9699                 break;
9700             }
9701             if (gn > 0) {
9702                 do {
9703                     yyboardindex = forwardMostMove;
9704                     cm = (ChessMove) yylex();
9705                 } while (cm == PGNTag || cm == Comment);
9706             }
9707             break;
9708
9709           case WhiteWins:
9710           case BlackWins:
9711           case GameIsDrawn:
9712             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9713                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9714                     != CMAIL_OLD_RESULT) {
9715                     nCmailResults ++ ;
9716                     cmailResult[  CMAIL_MAX_GAMES
9717                                 - gn - 1] = CMAIL_OLD_RESULT;
9718                 }
9719             }
9720             break;
9721
9722           case NormalMove:
9723             /* Only a NormalMove can be at the start of a game
9724              * without a position diagram. */
9725             if (lastLoadGameStart == (ChessMove) 0) {
9726               gn--;
9727               lastLoadGameStart = MoveNumberOne;
9728             }
9729             break;
9730
9731           default:
9732             break;
9733         }
9734     }
9735     
9736     if (appData.debugMode)
9737       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9738
9739     if (cm == XBoardGame) {
9740         /* Skip any header junk before position diagram and/or move 1 */
9741         for (;;) {
9742             yyboardindex = forwardMostMove;
9743             cm = (ChessMove) yylex();
9744
9745             if (cm == (ChessMove) 0 ||
9746                 cm == GNUChessGame || cm == XBoardGame) {
9747                 /* Empty game; pretend end-of-file and handle later */
9748                 cm = (ChessMove) 0;
9749                 break;
9750             }
9751
9752             if (cm == MoveNumberOne || cm == PositionDiagram ||
9753                 cm == PGNTag || cm == Comment)
9754               break;
9755         }
9756     } else if (cm == GNUChessGame) {
9757         if (gameInfo.event != NULL) {
9758             free(gameInfo.event);
9759         }
9760         gameInfo.event = StrSave(yy_text);
9761     }   
9762
9763     startedFromSetupPosition = FALSE;
9764     while (cm == PGNTag) {
9765         if (appData.debugMode) 
9766           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9767         err = ParsePGNTag(yy_text, &gameInfo);
9768         if (!err) numPGNTags++;
9769
9770         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9771         if(gameInfo.variant != oldVariant) {
9772             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9773             InitPosition(TRUE);
9774             oldVariant = gameInfo.variant;
9775             if (appData.debugMode) 
9776               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9777         }
9778
9779
9780         if (gameInfo.fen != NULL) {
9781           Board initial_position;
9782           startedFromSetupPosition = TRUE;
9783           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9784             Reset(TRUE, TRUE);
9785             DisplayError(_("Bad FEN position in file"), 0);
9786             return FALSE;
9787           }
9788           CopyBoard(boards[0], initial_position);
9789           if (blackPlaysFirst) {
9790             currentMove = forwardMostMove = backwardMostMove = 1;
9791             CopyBoard(boards[1], initial_position);
9792             strcpy(moveList[0], "");
9793             strcpy(parseList[0], "");
9794             timeRemaining[0][1] = whiteTimeRemaining;
9795             timeRemaining[1][1] = blackTimeRemaining;
9796             if (commentList[0] != NULL) {
9797               commentList[1] = commentList[0];
9798               commentList[0] = NULL;
9799             }
9800           } else {
9801             currentMove = forwardMostMove = backwardMostMove = 0;
9802           }
9803           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9804           {   int i;
9805               initialRulePlies = FENrulePlies;
9806               for( i=0; i< nrCastlingRights; i++ )
9807                   initialRights[i] = initial_position[CASTLING][i];
9808           }
9809           yyboardindex = forwardMostMove;
9810           free(gameInfo.fen);
9811           gameInfo.fen = NULL;
9812         }
9813
9814         yyboardindex = forwardMostMove;
9815         cm = (ChessMove) yylex();
9816
9817         /* Handle comments interspersed among the tags */
9818         while (cm == Comment) {
9819             char *p;
9820             if (appData.debugMode) 
9821               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9822             p = yy_text;
9823             AppendComment(currentMove, p, FALSE);
9824             yyboardindex = forwardMostMove;
9825             cm = (ChessMove) yylex();
9826         }
9827     }
9828
9829     /* don't rely on existence of Event tag since if game was
9830      * pasted from clipboard the Event tag may not exist
9831      */
9832     if (numPGNTags > 0){
9833         char *tags;
9834         if (gameInfo.variant == VariantNormal) {
9835           gameInfo.variant = StringToVariant(gameInfo.event);
9836         }
9837         if (!matchMode) {
9838           if( appData.autoDisplayTags ) {
9839             tags = PGNTags(&gameInfo);
9840             TagsPopUp(tags, CmailMsg());
9841             free(tags);
9842           }
9843         }
9844     } else {
9845         /* Make something up, but don't display it now */
9846         SetGameInfo();
9847         TagsPopDown();
9848     }
9849
9850     if (cm == PositionDiagram) {
9851         int i, j;
9852         char *p;
9853         Board initial_position;
9854
9855         if (appData.debugMode)
9856           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9857
9858         if (!startedFromSetupPosition) {
9859             p = yy_text;
9860             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9861               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9862                 switch (*p) {
9863                   case '[':
9864                   case '-':
9865                   case ' ':
9866                   case '\t':
9867                   case '\n':
9868                   case '\r':
9869                     break;
9870                   default:
9871                     initial_position[i][j++] = CharToPiece(*p);
9872                     break;
9873                 }
9874             while (*p == ' ' || *p == '\t' ||
9875                    *p == '\n' || *p == '\r') p++;
9876         
9877             if (strncmp(p, "black", strlen("black"))==0)
9878               blackPlaysFirst = TRUE;
9879             else
9880               blackPlaysFirst = FALSE;
9881             startedFromSetupPosition = TRUE;
9882         
9883             CopyBoard(boards[0], initial_position);
9884             if (blackPlaysFirst) {
9885                 currentMove = forwardMostMove = backwardMostMove = 1;
9886                 CopyBoard(boards[1], initial_position);
9887                 strcpy(moveList[0], "");
9888                 strcpy(parseList[0], "");
9889                 timeRemaining[0][1] = whiteTimeRemaining;
9890                 timeRemaining[1][1] = blackTimeRemaining;
9891                 if (commentList[0] != NULL) {
9892                     commentList[1] = commentList[0];
9893                     commentList[0] = NULL;
9894                 }
9895             } else {
9896                 currentMove = forwardMostMove = backwardMostMove = 0;
9897             }
9898         }
9899         yyboardindex = forwardMostMove;
9900         cm = (ChessMove) yylex();
9901     }
9902
9903     if (first.pr == NoProc) {
9904         StartChessProgram(&first);
9905     }
9906     InitChessProgram(&first, FALSE);
9907     SendToProgram("force\n", &first);
9908     if (startedFromSetupPosition) {
9909         SendBoard(&first, forwardMostMove);
9910     if (appData.debugMode) {
9911         fprintf(debugFP, "Load Game\n");
9912     }
9913         DisplayBothClocks();
9914     }      
9915
9916     /* [HGM] server: flag to write setup moves in broadcast file as one */
9917     loadFlag = appData.suppressLoadMoves;
9918
9919     while (cm == Comment) {
9920         char *p;
9921         if (appData.debugMode) 
9922           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9923         p = yy_text;
9924         AppendComment(currentMove, p, FALSE);
9925         yyboardindex = forwardMostMove;
9926         cm = (ChessMove) yylex();
9927     }
9928
9929     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9930         cm == WhiteWins || cm == BlackWins ||
9931         cm == GameIsDrawn || cm == GameUnfinished) {
9932         DisplayMessage("", _("No moves in game"));
9933         if (cmailMsgLoaded) {
9934             if (appData.debugMode)
9935               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9936             ClearHighlights();
9937             flipView = FALSE;
9938         }
9939         DrawPosition(FALSE, boards[currentMove]);
9940         DisplayBothClocks();
9941         gameMode = EditGame;
9942         ModeHighlight();
9943         gameFileFP = NULL;
9944         cmailOldMove = 0;
9945         return TRUE;
9946     }
9947
9948     // [HGM] PV info: routine tests if comment empty
9949     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9950         DisplayComment(currentMove - 1, commentList[currentMove]);
9951     }
9952     if (!matchMode && appData.timeDelay != 0) 
9953       DrawPosition(FALSE, boards[currentMove]);
9954
9955     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9956       programStats.ok_to_send = 1;
9957     }
9958
9959     /* if the first token after the PGN tags is a move
9960      * and not move number 1, retrieve it from the parser 
9961      */
9962     if (cm != MoveNumberOne)
9963         LoadGameOneMove(cm);
9964
9965     /* load the remaining moves from the file */
9966     while (LoadGameOneMove((ChessMove)0)) {
9967       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9968       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9969     }
9970
9971     /* rewind to the start of the game */
9972     currentMove = backwardMostMove;
9973
9974     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9975
9976     if (oldGameMode == AnalyzeFile ||
9977         oldGameMode == AnalyzeMode) {
9978       AnalyzeFileEvent();
9979     }
9980
9981     if (matchMode || appData.timeDelay == 0) {
9982       ToEndEvent();
9983       gameMode = EditGame;
9984       ModeHighlight();
9985     } else if (appData.timeDelay > 0) {
9986       AutoPlayGameLoop();
9987     }
9988
9989     if (appData.debugMode) 
9990         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9991
9992     loadFlag = 0; /* [HGM] true game starts */
9993     return TRUE;
9994 }
9995
9996 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9997 int
9998 ReloadPosition(offset)
9999      int offset;
10000 {
10001     int positionNumber = lastLoadPositionNumber + offset;
10002     if (lastLoadPositionFP == NULL) {
10003         DisplayError(_("No position has been loaded yet"), 0);
10004         return FALSE;
10005     }
10006     if (positionNumber <= 0) {
10007         DisplayError(_("Can't back up any further"), 0);
10008         return FALSE;
10009     }
10010     return LoadPosition(lastLoadPositionFP, positionNumber,
10011                         lastLoadPositionTitle);
10012 }
10013
10014 /* Load the nth position from the given file */
10015 int
10016 LoadPositionFromFile(filename, n, title)
10017      char *filename;
10018      int n;
10019      char *title;
10020 {
10021     FILE *f;
10022     char buf[MSG_SIZ];
10023
10024     if (strcmp(filename, "-") == 0) {
10025         return LoadPosition(stdin, n, "stdin");
10026     } else {
10027         f = fopen(filename, "rb");
10028         if (f == NULL) {
10029             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10030             DisplayError(buf, errno);
10031             return FALSE;
10032         } else {
10033             return LoadPosition(f, n, title);
10034         }
10035     }
10036 }
10037
10038 /* Load the nth position from the given open file, and close it */
10039 int
10040 LoadPosition(f, positionNumber, title)
10041      FILE *f;
10042      int positionNumber;
10043      char *title;
10044 {
10045     char *p, line[MSG_SIZ];
10046     Board initial_position;
10047     int i, j, fenMode, pn;
10048     
10049     if (gameMode == Training )
10050         SetTrainingModeOff();
10051
10052     if (gameMode != BeginningOfGame) {
10053         Reset(FALSE, TRUE);
10054     }
10055     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10056         fclose(lastLoadPositionFP);
10057     }
10058     if (positionNumber == 0) positionNumber = 1;
10059     lastLoadPositionFP = f;
10060     lastLoadPositionNumber = positionNumber;
10061     strcpy(lastLoadPositionTitle, title);
10062     if (first.pr == NoProc) {
10063       StartChessProgram(&first);
10064       InitChessProgram(&first, FALSE);
10065     }    
10066     pn = positionNumber;
10067     if (positionNumber < 0) {
10068         /* Negative position number means to seek to that byte offset */
10069         if (fseek(f, -positionNumber, 0) == -1) {
10070             DisplayError(_("Can't seek on position file"), 0);
10071             return FALSE;
10072         };
10073         pn = 1;
10074     } else {
10075         if (fseek(f, 0, 0) == -1) {
10076             if (f == lastLoadPositionFP ?
10077                 positionNumber == lastLoadPositionNumber + 1 :
10078                 positionNumber == 1) {
10079                 pn = 1;
10080             } else {
10081                 DisplayError(_("Can't seek on position file"), 0);
10082                 return FALSE;
10083             }
10084         }
10085     }
10086     /* See if this file is FEN or old-style xboard */
10087     if (fgets(line, MSG_SIZ, f) == NULL) {
10088         DisplayError(_("Position not found in file"), 0);
10089         return FALSE;
10090     }
10091     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10092     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10093
10094     if (pn >= 2) {
10095         if (fenMode || line[0] == '#') pn--;
10096         while (pn > 0) {
10097             /* skip positions before number pn */
10098             if (fgets(line, MSG_SIZ, f) == NULL) {
10099                 Reset(TRUE, TRUE);
10100                 DisplayError(_("Position not found in file"), 0);
10101                 return FALSE;
10102             }
10103             if (fenMode || line[0] == '#') pn--;
10104         }
10105     }
10106
10107     if (fenMode) {
10108         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10109             DisplayError(_("Bad FEN position in file"), 0);
10110             return FALSE;
10111         }
10112     } else {
10113         (void) fgets(line, MSG_SIZ, f);
10114         (void) fgets(line, MSG_SIZ, f);
10115     
10116         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10117             (void) fgets(line, MSG_SIZ, f);
10118             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10119                 if (*p == ' ')
10120                   continue;
10121                 initial_position[i][j++] = CharToPiece(*p);
10122             }
10123         }
10124     
10125         blackPlaysFirst = FALSE;
10126         if (!feof(f)) {
10127             (void) fgets(line, MSG_SIZ, f);
10128             if (strncmp(line, "black", strlen("black"))==0)
10129               blackPlaysFirst = TRUE;
10130         }
10131     }
10132     startedFromSetupPosition = TRUE;
10133     
10134     SendToProgram("force\n", &first);
10135     CopyBoard(boards[0], initial_position);
10136     if (blackPlaysFirst) {
10137         currentMove = forwardMostMove = backwardMostMove = 1;
10138         strcpy(moveList[0], "");
10139         strcpy(parseList[0], "");
10140         CopyBoard(boards[1], initial_position);
10141         DisplayMessage("", _("Black to play"));
10142     } else {
10143         currentMove = forwardMostMove = backwardMostMove = 0;
10144         DisplayMessage("", _("White to play"));
10145     }
10146     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10147     SendBoard(&first, forwardMostMove);
10148     if (appData.debugMode) {
10149 int i, j;
10150   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10151   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10152         fprintf(debugFP, "Load Position\n");
10153     }
10154
10155     if (positionNumber > 1) {
10156         sprintf(line, "%s %d", title, positionNumber);
10157         DisplayTitle(line);
10158     } else {
10159         DisplayTitle(title);
10160     }
10161     gameMode = EditGame;
10162     ModeHighlight();
10163     ResetClocks();
10164     timeRemaining[0][1] = whiteTimeRemaining;
10165     timeRemaining[1][1] = blackTimeRemaining;
10166     DrawPosition(FALSE, boards[currentMove]);
10167    
10168     return TRUE;
10169 }
10170
10171
10172 void
10173 CopyPlayerNameIntoFileName(dest, src)
10174      char **dest, *src;
10175 {
10176     while (*src != NULLCHAR && *src != ',') {
10177         if (*src == ' ') {
10178             *(*dest)++ = '_';
10179             src++;
10180         } else {
10181             *(*dest)++ = *src++;
10182         }
10183     }
10184 }
10185
10186 char *DefaultFileName(ext)
10187      char *ext;
10188 {
10189     static char def[MSG_SIZ];
10190     char *p;
10191
10192     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10193         p = def;
10194         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10195         *p++ = '-';
10196         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10197         *p++ = '.';
10198         strcpy(p, ext);
10199     } else {
10200         def[0] = NULLCHAR;
10201     }
10202     return def;
10203 }
10204
10205 /* Save the current game to the given file */
10206 int
10207 SaveGameToFile(filename, append)
10208      char *filename;
10209      int append;
10210 {
10211     FILE *f;
10212     char buf[MSG_SIZ];
10213
10214     if (strcmp(filename, "-") == 0) {
10215         return SaveGame(stdout, 0, NULL);
10216     } else {
10217         f = fopen(filename, append ? "a" : "w");
10218         if (f == NULL) {
10219             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10220             DisplayError(buf, errno);
10221             return FALSE;
10222         } else {
10223             return SaveGame(f, 0, NULL);
10224         }
10225     }
10226 }
10227
10228 char *
10229 SavePart(str)
10230      char *str;
10231 {
10232     static char buf[MSG_SIZ];
10233     char *p;
10234     
10235     p = strchr(str, ' ');
10236     if (p == NULL) return str;
10237     strncpy(buf, str, p - str);
10238     buf[p - str] = NULLCHAR;
10239     return buf;
10240 }
10241
10242 #define PGN_MAX_LINE 75
10243
10244 #define PGN_SIDE_WHITE  0
10245 #define PGN_SIDE_BLACK  1
10246
10247 /* [AS] */
10248 static int FindFirstMoveOutOfBook( int side )
10249 {
10250     int result = -1;
10251
10252     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10253         int index = backwardMostMove;
10254         int has_book_hit = 0;
10255
10256         if( (index % 2) != side ) {
10257             index++;
10258         }
10259
10260         while( index < forwardMostMove ) {
10261             /* Check to see if engine is in book */
10262             int depth = pvInfoList[index].depth;
10263             int score = pvInfoList[index].score;
10264             int in_book = 0;
10265
10266             if( depth <= 2 ) {
10267                 in_book = 1;
10268             }
10269             else if( score == 0 && depth == 63 ) {
10270                 in_book = 1; /* Zappa */
10271             }
10272             else if( score == 2 && depth == 99 ) {
10273                 in_book = 1; /* Abrok */
10274             }
10275
10276             has_book_hit += in_book;
10277
10278             if( ! in_book ) {
10279                 result = index;
10280
10281                 break;
10282             }
10283
10284             index += 2;
10285         }
10286     }
10287
10288     return result;
10289 }
10290
10291 /* [AS] */
10292 void GetOutOfBookInfo( char * buf )
10293 {
10294     int oob[2];
10295     int i;
10296     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10297
10298     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10299     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10300
10301     *buf = '\0';
10302
10303     if( oob[0] >= 0 || oob[1] >= 0 ) {
10304         for( i=0; i<2; i++ ) {
10305             int idx = oob[i];
10306
10307             if( idx >= 0 ) {
10308                 if( i > 0 && oob[0] >= 0 ) {
10309                     strcat( buf, "   " );
10310                 }
10311
10312                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10313                 sprintf( buf+strlen(buf), "%s%.2f", 
10314                     pvInfoList[idx].score >= 0 ? "+" : "",
10315                     pvInfoList[idx].score / 100.0 );
10316             }
10317         }
10318     }
10319 }
10320
10321 /* Save game in PGN style and close the file */
10322 int
10323 SaveGamePGN(f)
10324      FILE *f;
10325 {
10326     int i, offset, linelen, newblock;
10327     time_t tm;
10328 //    char *movetext;
10329     char numtext[32];
10330     int movelen, numlen, blank;
10331     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10332
10333     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10334     
10335     tm = time((time_t *) NULL);
10336     
10337     PrintPGNTags(f, &gameInfo);
10338     
10339     if (backwardMostMove > 0 || startedFromSetupPosition) {
10340         char *fen = PositionToFEN(backwardMostMove, NULL);
10341         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10342         fprintf(f, "\n{--------------\n");
10343         PrintPosition(f, backwardMostMove);
10344         fprintf(f, "--------------}\n");
10345         free(fen);
10346     }
10347     else {
10348         /* [AS] Out of book annotation */
10349         if( appData.saveOutOfBookInfo ) {
10350             char buf[64];
10351
10352             GetOutOfBookInfo( buf );
10353
10354             if( buf[0] != '\0' ) {
10355                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10356             }
10357         }
10358
10359         fprintf(f, "\n");
10360     }
10361
10362     i = backwardMostMove;
10363     linelen = 0;
10364     newblock = TRUE;
10365
10366     while (i < forwardMostMove) {
10367         /* Print comments preceding this move */
10368         if (commentList[i] != NULL) {
10369             if (linelen > 0) fprintf(f, "\n");
10370             fprintf(f, "%s", commentList[i]);
10371             linelen = 0;
10372             newblock = TRUE;
10373         }
10374
10375         /* Format move number */
10376         if ((i % 2) == 0) {
10377             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10378         } else {
10379             if (newblock) {
10380                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10381             } else {
10382                 numtext[0] = NULLCHAR;
10383             }
10384         }
10385         numlen = strlen(numtext);
10386         newblock = FALSE;
10387
10388         /* Print move number */
10389         blank = linelen > 0 && numlen > 0;
10390         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10391             fprintf(f, "\n");
10392             linelen = 0;
10393             blank = 0;
10394         }
10395         if (blank) {
10396             fprintf(f, " ");
10397             linelen++;
10398         }
10399         fprintf(f, "%s", numtext);
10400         linelen += numlen;
10401
10402         /* Get move */
10403         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10404         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10405
10406         /* Print move */
10407         blank = linelen > 0 && movelen > 0;
10408         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10409             fprintf(f, "\n");
10410             linelen = 0;
10411             blank = 0;
10412         }
10413         if (blank) {
10414             fprintf(f, " ");
10415             linelen++;
10416         }
10417         fprintf(f, "%s", move_buffer);
10418         linelen += movelen;
10419
10420         /* [AS] Add PV info if present */
10421         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10422             /* [HGM] add time */
10423             char buf[MSG_SIZ]; int seconds;
10424
10425             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10426
10427             if( seconds <= 0) buf[0] = 0; else
10428             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10429                 seconds = (seconds + 4)/10; // round to full seconds
10430                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10431                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10432             }
10433
10434             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10435                 pvInfoList[i].score >= 0 ? "+" : "",
10436                 pvInfoList[i].score / 100.0,
10437                 pvInfoList[i].depth,
10438                 buf );
10439
10440             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10441
10442             /* Print score/depth */
10443             blank = linelen > 0 && movelen > 0;
10444             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10445                 fprintf(f, "\n");
10446                 linelen = 0;
10447                 blank = 0;
10448             }
10449             if (blank) {
10450                 fprintf(f, " ");
10451                 linelen++;
10452             }
10453             fprintf(f, "%s", move_buffer);
10454             linelen += movelen;
10455         }
10456
10457         i++;
10458     }
10459     
10460     /* Start a new line */
10461     if (linelen > 0) fprintf(f, "\n");
10462
10463     /* Print comments after last move */
10464     if (commentList[i] != NULL) {
10465         fprintf(f, "%s\n", commentList[i]);
10466     }
10467
10468     /* Print result */
10469     if (gameInfo.resultDetails != NULL &&
10470         gameInfo.resultDetails[0] != NULLCHAR) {
10471         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10472                 PGNResult(gameInfo.result));
10473     } else {
10474         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10475     }
10476
10477     fclose(f);
10478     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10479     return TRUE;
10480 }
10481
10482 /* Save game in old style and close the file */
10483 int
10484 SaveGameOldStyle(f)
10485      FILE *f;
10486 {
10487     int i, offset;
10488     time_t tm;
10489     
10490     tm = time((time_t *) NULL);
10491     
10492     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10493     PrintOpponents(f);
10494     
10495     if (backwardMostMove > 0 || startedFromSetupPosition) {
10496         fprintf(f, "\n[--------------\n");
10497         PrintPosition(f, backwardMostMove);
10498         fprintf(f, "--------------]\n");
10499     } else {
10500         fprintf(f, "\n");
10501     }
10502
10503     i = backwardMostMove;
10504     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10505
10506     while (i < forwardMostMove) {
10507         if (commentList[i] != NULL) {
10508             fprintf(f, "[%s]\n", commentList[i]);
10509         }
10510
10511         if ((i % 2) == 1) {
10512             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10513             i++;
10514         } else {
10515             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10516             i++;
10517             if (commentList[i] != NULL) {
10518                 fprintf(f, "\n");
10519                 continue;
10520             }
10521             if (i >= forwardMostMove) {
10522                 fprintf(f, "\n");
10523                 break;
10524             }
10525             fprintf(f, "%s\n", parseList[i]);
10526             i++;
10527         }
10528     }
10529     
10530     if (commentList[i] != NULL) {
10531         fprintf(f, "[%s]\n", commentList[i]);
10532     }
10533
10534     /* This isn't really the old style, but it's close enough */
10535     if (gameInfo.resultDetails != NULL &&
10536         gameInfo.resultDetails[0] != NULLCHAR) {
10537         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10538                 gameInfo.resultDetails);
10539     } else {
10540         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10541     }
10542
10543     fclose(f);
10544     return TRUE;
10545 }
10546
10547 /* Save the current game to open file f and close the file */
10548 int
10549 SaveGame(f, dummy, dummy2)
10550      FILE *f;
10551      int dummy;
10552      char *dummy2;
10553 {
10554     if (gameMode == EditPosition) EditPositionDone(TRUE);
10555     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10556     if (appData.oldSaveStyle)
10557       return SaveGameOldStyle(f);
10558     else
10559       return SaveGamePGN(f);
10560 }
10561
10562 /* Save the current position to the given file */
10563 int
10564 SavePositionToFile(filename)
10565      char *filename;
10566 {
10567     FILE *f;
10568     char buf[MSG_SIZ];
10569
10570     if (strcmp(filename, "-") == 0) {
10571         return SavePosition(stdout, 0, NULL);
10572     } else {
10573         f = fopen(filename, "a");
10574         if (f == NULL) {
10575             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10576             DisplayError(buf, errno);
10577             return FALSE;
10578         } else {
10579             SavePosition(f, 0, NULL);
10580             return TRUE;
10581         }
10582     }
10583 }
10584
10585 /* Save the current position to the given open file and close the file */
10586 int
10587 SavePosition(f, dummy, dummy2)
10588      FILE *f;
10589      int dummy;
10590      char *dummy2;
10591 {
10592     time_t tm;
10593     char *fen;
10594     
10595     if (gameMode == EditPosition) EditPositionDone(TRUE);
10596     if (appData.oldSaveStyle) {
10597         tm = time((time_t *) NULL);
10598     
10599         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10600         PrintOpponents(f);
10601         fprintf(f, "[--------------\n");
10602         PrintPosition(f, currentMove);
10603         fprintf(f, "--------------]\n");
10604     } else {
10605         fen = PositionToFEN(currentMove, NULL);
10606         fprintf(f, "%s\n", fen);
10607         free(fen);
10608     }
10609     fclose(f);
10610     return TRUE;
10611 }
10612
10613 void
10614 ReloadCmailMsgEvent(unregister)
10615      int unregister;
10616 {
10617 #if !WIN32
10618     static char *inFilename = NULL;
10619     static char *outFilename;
10620     int i;
10621     struct stat inbuf, outbuf;
10622     int status;
10623     
10624     /* Any registered moves are unregistered if unregister is set, */
10625     /* i.e. invoked by the signal handler */
10626     if (unregister) {
10627         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10628             cmailMoveRegistered[i] = FALSE;
10629             if (cmailCommentList[i] != NULL) {
10630                 free(cmailCommentList[i]);
10631                 cmailCommentList[i] = NULL;
10632             }
10633         }
10634         nCmailMovesRegistered = 0;
10635     }
10636
10637     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10638         cmailResult[i] = CMAIL_NOT_RESULT;
10639     }
10640     nCmailResults = 0;
10641
10642     if (inFilename == NULL) {
10643         /* Because the filenames are static they only get malloced once  */
10644         /* and they never get freed                                      */
10645         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10646         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10647
10648         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10649         sprintf(outFilename, "%s.out", appData.cmailGameName);
10650     }
10651     
10652     status = stat(outFilename, &outbuf);
10653     if (status < 0) {
10654         cmailMailedMove = FALSE;
10655     } else {
10656         status = stat(inFilename, &inbuf);
10657         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10658     }
10659     
10660     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10661        counts the games, notes how each one terminated, etc.
10662        
10663        It would be nice to remove this kludge and instead gather all
10664        the information while building the game list.  (And to keep it
10665        in the game list nodes instead of having a bunch of fixed-size
10666        parallel arrays.)  Note this will require getting each game's
10667        termination from the PGN tags, as the game list builder does
10668        not process the game moves.  --mann
10669        */
10670     cmailMsgLoaded = TRUE;
10671     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10672     
10673     /* Load first game in the file or popup game menu */
10674     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10675
10676 #endif /* !WIN32 */
10677     return;
10678 }
10679
10680 int
10681 RegisterMove()
10682 {
10683     FILE *f;
10684     char string[MSG_SIZ];
10685
10686     if (   cmailMailedMove
10687         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10688         return TRUE;            /* Allow free viewing  */
10689     }
10690
10691     /* Unregister move to ensure that we don't leave RegisterMove        */
10692     /* with the move registered when the conditions for registering no   */
10693     /* longer hold                                                       */
10694     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10695         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10696         nCmailMovesRegistered --;
10697
10698         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10699           {
10700               free(cmailCommentList[lastLoadGameNumber - 1]);
10701               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10702           }
10703     }
10704
10705     if (cmailOldMove == -1) {
10706         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10707         return FALSE;
10708     }
10709
10710     if (currentMove > cmailOldMove + 1) {
10711         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10712         return FALSE;
10713     }
10714
10715     if (currentMove < cmailOldMove) {
10716         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10717         return FALSE;
10718     }
10719
10720     if (forwardMostMove > currentMove) {
10721         /* Silently truncate extra moves */
10722         TruncateGame();
10723     }
10724
10725     if (   (currentMove == cmailOldMove + 1)
10726         || (   (currentMove == cmailOldMove)
10727             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10728                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10729         if (gameInfo.result != GameUnfinished) {
10730             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10731         }
10732
10733         if (commentList[currentMove] != NULL) {
10734             cmailCommentList[lastLoadGameNumber - 1]
10735               = StrSave(commentList[currentMove]);
10736         }
10737         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10738
10739         if (appData.debugMode)
10740           fprintf(debugFP, "Saving %s for game %d\n",
10741                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10742
10743         sprintf(string,
10744                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10745         
10746         f = fopen(string, "w");
10747         if (appData.oldSaveStyle) {
10748             SaveGameOldStyle(f); /* also closes the file */
10749             
10750             sprintf(string, "%s.pos.out", appData.cmailGameName);
10751             f = fopen(string, "w");
10752             SavePosition(f, 0, NULL); /* also closes the file */
10753         } else {
10754             fprintf(f, "{--------------\n");
10755             PrintPosition(f, currentMove);
10756             fprintf(f, "--------------}\n\n");
10757             
10758             SaveGame(f, 0, NULL); /* also closes the file*/
10759         }
10760         
10761         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10762         nCmailMovesRegistered ++;
10763     } else if (nCmailGames == 1) {
10764         DisplayError(_("You have not made a move yet"), 0);
10765         return FALSE;
10766     }
10767
10768     return TRUE;
10769 }
10770
10771 void
10772 MailMoveEvent()
10773 {
10774 #if !WIN32
10775     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10776     FILE *commandOutput;
10777     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10778     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10779     int nBuffers;
10780     int i;
10781     int archived;
10782     char *arcDir;
10783
10784     if (! cmailMsgLoaded) {
10785         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10786         return;
10787     }
10788
10789     if (nCmailGames == nCmailResults) {
10790         DisplayError(_("No unfinished games"), 0);
10791         return;
10792     }
10793
10794 #if CMAIL_PROHIBIT_REMAIL
10795     if (cmailMailedMove) {
10796         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);
10797         DisplayError(msg, 0);
10798         return;
10799     }
10800 #endif
10801
10802     if (! (cmailMailedMove || RegisterMove())) return;
10803     
10804     if (   cmailMailedMove
10805         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10806         sprintf(string, partCommandString,
10807                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10808         commandOutput = popen(string, "r");
10809
10810         if (commandOutput == NULL) {
10811             DisplayError(_("Failed to invoke cmail"), 0);
10812         } else {
10813             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10814                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10815             }
10816             if (nBuffers > 1) {
10817                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10818                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10819                 nBytes = MSG_SIZ - 1;
10820             } else {
10821                 (void) memcpy(msg, buffer, nBytes);
10822             }
10823             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10824
10825             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10826                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10827
10828                 archived = TRUE;
10829                 for (i = 0; i < nCmailGames; i ++) {
10830                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10831                         archived = FALSE;
10832                     }
10833                 }
10834                 if (   archived
10835                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10836                         != NULL)) {
10837                     sprintf(buffer, "%s/%s.%s.archive",
10838                             arcDir,
10839                             appData.cmailGameName,
10840                             gameInfo.date);
10841                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10842                     cmailMsgLoaded = FALSE;
10843                 }
10844             }
10845
10846             DisplayInformation(msg);
10847             pclose(commandOutput);
10848         }
10849     } else {
10850         if ((*cmailMsg) != '\0') {
10851             DisplayInformation(cmailMsg);
10852         }
10853     }
10854
10855     return;
10856 #endif /* !WIN32 */
10857 }
10858
10859 char *
10860 CmailMsg()
10861 {
10862 #if WIN32
10863     return NULL;
10864 #else
10865     int  prependComma = 0;
10866     char number[5];
10867     char string[MSG_SIZ];       /* Space for game-list */
10868     int  i;
10869     
10870     if (!cmailMsgLoaded) return "";
10871
10872     if (cmailMailedMove) {
10873         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10874     } else {
10875         /* Create a list of games left */
10876         sprintf(string, "[");
10877         for (i = 0; i < nCmailGames; i ++) {
10878             if (! (   cmailMoveRegistered[i]
10879                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10880                 if (prependComma) {
10881                     sprintf(number, ",%d", i + 1);
10882                 } else {
10883                     sprintf(number, "%d", i + 1);
10884                     prependComma = 1;
10885                 }
10886                 
10887                 strcat(string, number);
10888             }
10889         }
10890         strcat(string, "]");
10891
10892         if (nCmailMovesRegistered + nCmailResults == 0) {
10893             switch (nCmailGames) {
10894               case 1:
10895                 sprintf(cmailMsg,
10896                         _("Still need to make move for game\n"));
10897                 break;
10898                 
10899               case 2:
10900                 sprintf(cmailMsg,
10901                         _("Still need to make moves for both games\n"));
10902                 break;
10903                 
10904               default:
10905                 sprintf(cmailMsg,
10906                         _("Still need to make moves for all %d games\n"),
10907                         nCmailGames);
10908                 break;
10909             }
10910         } else {
10911             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10912               case 1:
10913                 sprintf(cmailMsg,
10914                         _("Still need to make a move for game %s\n"),
10915                         string);
10916                 break;
10917                 
10918               case 0:
10919                 if (nCmailResults == nCmailGames) {
10920                     sprintf(cmailMsg, _("No unfinished games\n"));
10921                 } else {
10922                     sprintf(cmailMsg, _("Ready to send mail\n"));
10923                 }
10924                 break;
10925                 
10926               default:
10927                 sprintf(cmailMsg,
10928                         _("Still need to make moves for games %s\n"),
10929                         string);
10930             }
10931         }
10932     }
10933     return cmailMsg;
10934 #endif /* WIN32 */
10935 }
10936
10937 void
10938 ResetGameEvent()
10939 {
10940     if (gameMode == Training)
10941       SetTrainingModeOff();
10942
10943     Reset(TRUE, TRUE);
10944     cmailMsgLoaded = FALSE;
10945     if (appData.icsActive) {
10946       SendToICS(ics_prefix);
10947       SendToICS("refresh\n");
10948     }
10949 }
10950
10951 void
10952 ExitEvent(status)
10953      int status;
10954 {
10955     exiting++;
10956     if (exiting > 2) {
10957       /* Give up on clean exit */
10958       exit(status);
10959     }
10960     if (exiting > 1) {
10961       /* Keep trying for clean exit */
10962       return;
10963     }
10964
10965     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10966
10967     if (telnetISR != NULL) {
10968       RemoveInputSource(telnetISR);
10969     }
10970     if (icsPR != NoProc) {
10971       DestroyChildProcess(icsPR, TRUE);
10972     }
10973
10974     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10975     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10976
10977     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10978     /* make sure this other one finishes before killing it!                  */
10979     if(endingGame) { int count = 0;
10980         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10981         while(endingGame && count++ < 10) DoSleep(1);
10982         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10983     }
10984
10985     /* Kill off chess programs */
10986     if (first.pr != NoProc) {
10987         ExitAnalyzeMode();
10988         
10989         DoSleep( appData.delayBeforeQuit );
10990         SendToProgram("quit\n", &first);
10991         DoSleep( appData.delayAfterQuit );
10992         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10993     }
10994     if (second.pr != NoProc) {
10995         DoSleep( appData.delayBeforeQuit );
10996         SendToProgram("quit\n", &second);
10997         DoSleep( appData.delayAfterQuit );
10998         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10999     }
11000     if (first.isr != NULL) {
11001         RemoveInputSource(first.isr);
11002     }
11003     if (second.isr != NULL) {
11004         RemoveInputSource(second.isr);
11005     }
11006
11007     ShutDownFrontEnd();
11008     exit(status);
11009 }
11010
11011 void
11012 PauseEvent()
11013 {
11014     if (appData.debugMode)
11015         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11016     if (pausing) {
11017         pausing = FALSE;
11018         ModeHighlight();
11019         if (gameMode == MachinePlaysWhite ||
11020             gameMode == MachinePlaysBlack) {
11021             StartClocks();
11022         } else {
11023             DisplayBothClocks();
11024         }
11025         if (gameMode == PlayFromGameFile) {
11026             if (appData.timeDelay >= 0) 
11027                 AutoPlayGameLoop();
11028         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11029             Reset(FALSE, TRUE);
11030             SendToICS(ics_prefix);
11031             SendToICS("refresh\n");
11032         } else if (currentMove < forwardMostMove) {
11033             ForwardInner(forwardMostMove);
11034         }
11035         pauseExamInvalid = FALSE;
11036     } else {
11037         switch (gameMode) {
11038           default:
11039             return;
11040           case IcsExamining:
11041             pauseExamForwardMostMove = forwardMostMove;
11042             pauseExamInvalid = FALSE;
11043             /* fall through */
11044           case IcsObserving:
11045           case IcsPlayingWhite:
11046           case IcsPlayingBlack:
11047             pausing = TRUE;
11048             ModeHighlight();
11049             return;
11050           case PlayFromGameFile:
11051             (void) StopLoadGameTimer();
11052             pausing = TRUE;
11053             ModeHighlight();
11054             break;
11055           case BeginningOfGame:
11056             if (appData.icsActive) return;
11057             /* else fall through */
11058           case MachinePlaysWhite:
11059           case MachinePlaysBlack:
11060           case TwoMachinesPlay:
11061             if (forwardMostMove == 0)
11062               return;           /* don't pause if no one has moved */
11063             if ((gameMode == MachinePlaysWhite &&
11064                  !WhiteOnMove(forwardMostMove)) ||
11065                 (gameMode == MachinePlaysBlack &&
11066                  WhiteOnMove(forwardMostMove))) {
11067                 StopClocks();
11068             }
11069             pausing = TRUE;
11070             ModeHighlight();
11071             break;
11072         }
11073     }
11074 }
11075
11076 void
11077 EditCommentEvent()
11078 {
11079     char title[MSG_SIZ];
11080
11081     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11082         strcpy(title, _("Edit comment"));
11083     } else {
11084         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11085                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11086                 parseList[currentMove - 1]);
11087     }
11088
11089     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11090 }
11091
11092
11093 void
11094 EditTagsEvent()
11095 {
11096     char *tags = PGNTags(&gameInfo);
11097     EditTagsPopUp(tags);
11098     free(tags);
11099 }
11100
11101 void
11102 AnalyzeModeEvent()
11103 {
11104     if (appData.noChessProgram || gameMode == AnalyzeMode)
11105       return;
11106
11107     if (gameMode != AnalyzeFile) {
11108         if (!appData.icsEngineAnalyze) {
11109                EditGameEvent();
11110                if (gameMode != EditGame) return;
11111         }
11112         ResurrectChessProgram();
11113         SendToProgram("analyze\n", &first);
11114         first.analyzing = TRUE;
11115         /*first.maybeThinking = TRUE;*/
11116         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11117         EngineOutputPopUp();
11118     }
11119     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11120     pausing = FALSE;
11121     ModeHighlight();
11122     SetGameInfo();
11123
11124     StartAnalysisClock();
11125     GetTimeMark(&lastNodeCountTime);
11126     lastNodeCount = 0;
11127 }
11128
11129 void
11130 AnalyzeFileEvent()
11131 {
11132     if (appData.noChessProgram || gameMode == AnalyzeFile)
11133       return;
11134
11135     if (gameMode != AnalyzeMode) {
11136         EditGameEvent();
11137         if (gameMode != EditGame) return;
11138         ResurrectChessProgram();
11139         SendToProgram("analyze\n", &first);
11140         first.analyzing = TRUE;
11141         /*first.maybeThinking = TRUE;*/
11142         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11143         EngineOutputPopUp();
11144     }
11145     gameMode = AnalyzeFile;
11146     pausing = FALSE;
11147     ModeHighlight();
11148     SetGameInfo();
11149
11150     StartAnalysisClock();
11151     GetTimeMark(&lastNodeCountTime);
11152     lastNodeCount = 0;
11153 }
11154
11155 void
11156 MachineWhiteEvent()
11157 {
11158     char buf[MSG_SIZ];
11159     char *bookHit = NULL;
11160
11161     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11162       return;
11163
11164
11165     if (gameMode == PlayFromGameFile || 
11166         gameMode == TwoMachinesPlay  || 
11167         gameMode == Training         || 
11168         gameMode == AnalyzeMode      || 
11169         gameMode == EndOfGame)
11170         EditGameEvent();
11171
11172     if (gameMode == EditPosition) 
11173         EditPositionDone(TRUE);
11174
11175     if (!WhiteOnMove(currentMove)) {
11176         DisplayError(_("It is not White's turn"), 0);
11177         return;
11178     }
11179   
11180     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11181       ExitAnalyzeMode();
11182
11183     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11184         gameMode == AnalyzeFile)
11185         TruncateGame();
11186
11187     ResurrectChessProgram();    /* in case it isn't running */
11188     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11189         gameMode = MachinePlaysWhite;
11190         ResetClocks();
11191     } else
11192     gameMode = MachinePlaysWhite;
11193     pausing = FALSE;
11194     ModeHighlight();
11195     SetGameInfo();
11196     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11197     DisplayTitle(buf);
11198     if (first.sendName) {
11199       sprintf(buf, "name %s\n", gameInfo.black);
11200       SendToProgram(buf, &first);
11201     }
11202     if (first.sendTime) {
11203       if (first.useColors) {
11204         SendToProgram("black\n", &first); /*gnu kludge*/
11205       }
11206       SendTimeRemaining(&first, TRUE);
11207     }
11208     if (first.useColors) {
11209       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11210     }
11211     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11212     SetMachineThinkingEnables();
11213     first.maybeThinking = TRUE;
11214     StartClocks();
11215     firstMove = FALSE;
11216
11217     if (appData.autoFlipView && !flipView) {
11218       flipView = !flipView;
11219       DrawPosition(FALSE, NULL);
11220       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11221     }
11222
11223     if(bookHit) { // [HGM] book: simulate book reply
11224         static char bookMove[MSG_SIZ]; // a bit generous?
11225
11226         programStats.nodes = programStats.depth = programStats.time = 
11227         programStats.score = programStats.got_only_move = 0;
11228         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11229
11230         strcpy(bookMove, "move ");
11231         strcat(bookMove, bookHit);
11232         HandleMachineMove(bookMove, &first);
11233     }
11234 }
11235
11236 void
11237 MachineBlackEvent()
11238 {
11239     char buf[MSG_SIZ];
11240    char *bookHit = NULL;
11241
11242     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11243         return;
11244
11245
11246     if (gameMode == PlayFromGameFile || 
11247         gameMode == TwoMachinesPlay  || 
11248         gameMode == Training         || 
11249         gameMode == AnalyzeMode      || 
11250         gameMode == EndOfGame)
11251         EditGameEvent();
11252
11253     if (gameMode == EditPosition) 
11254         EditPositionDone(TRUE);
11255
11256     if (WhiteOnMove(currentMove)) {
11257         DisplayError(_("It is not Black's turn"), 0);
11258         return;
11259     }
11260     
11261     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11262       ExitAnalyzeMode();
11263
11264     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11265         gameMode == AnalyzeFile)
11266         TruncateGame();
11267
11268     ResurrectChessProgram();    /* in case it isn't running */
11269     gameMode = MachinePlaysBlack;
11270     pausing = FALSE;
11271     ModeHighlight();
11272     SetGameInfo();
11273     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11274     DisplayTitle(buf);
11275     if (first.sendName) {
11276       sprintf(buf, "name %s\n", gameInfo.white);
11277       SendToProgram(buf, &first);
11278     }
11279     if (first.sendTime) {
11280       if (first.useColors) {
11281         SendToProgram("white\n", &first); /*gnu kludge*/
11282       }
11283       SendTimeRemaining(&first, FALSE);
11284     }
11285     if (first.useColors) {
11286       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11287     }
11288     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11289     SetMachineThinkingEnables();
11290     first.maybeThinking = TRUE;
11291     StartClocks();
11292
11293     if (appData.autoFlipView && flipView) {
11294       flipView = !flipView;
11295       DrawPosition(FALSE, NULL);
11296       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11297     }
11298     if(bookHit) { // [HGM] book: simulate book reply
11299         static char bookMove[MSG_SIZ]; // a bit generous?
11300
11301         programStats.nodes = programStats.depth = programStats.time = 
11302         programStats.score = programStats.got_only_move = 0;
11303         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11304
11305         strcpy(bookMove, "move ");
11306         strcat(bookMove, bookHit);
11307         HandleMachineMove(bookMove, &first);
11308     }
11309 }
11310
11311
11312 void
11313 DisplayTwoMachinesTitle()
11314 {
11315     char buf[MSG_SIZ];
11316     if (appData.matchGames > 0) {
11317         if (first.twoMachinesColor[0] == 'w') {
11318             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11319                     gameInfo.white, gameInfo.black,
11320                     first.matchWins, second.matchWins,
11321                     matchGame - 1 - (first.matchWins + second.matchWins));
11322         } else {
11323             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11324                     gameInfo.white, gameInfo.black,
11325                     second.matchWins, first.matchWins,
11326                     matchGame - 1 - (first.matchWins + second.matchWins));
11327         }
11328     } else {
11329         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11330     }
11331     DisplayTitle(buf);
11332 }
11333
11334 void
11335 TwoMachinesEvent P((void))
11336 {
11337     int i;
11338     char buf[MSG_SIZ];
11339     ChessProgramState *onmove;
11340     char *bookHit = NULL;
11341     
11342     if (appData.noChessProgram) return;
11343
11344     switch (gameMode) {
11345       case TwoMachinesPlay:
11346         return;
11347       case MachinePlaysWhite:
11348       case MachinePlaysBlack:
11349         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11350             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11351             return;
11352         }
11353         /* fall through */
11354       case BeginningOfGame:
11355       case PlayFromGameFile:
11356       case EndOfGame:
11357         EditGameEvent();
11358         if (gameMode != EditGame) return;
11359         break;
11360       case EditPosition:
11361         EditPositionDone(TRUE);
11362         break;
11363       case AnalyzeMode:
11364       case AnalyzeFile:
11365         ExitAnalyzeMode();
11366         break;
11367       case EditGame:
11368       default:
11369         break;
11370     }
11371
11372 //    forwardMostMove = currentMove;
11373     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11374     ResurrectChessProgram();    /* in case first program isn't running */
11375
11376     if (second.pr == NULL) {
11377         StartChessProgram(&second);
11378         if (second.protocolVersion == 1) {
11379           TwoMachinesEventIfReady();
11380         } else {
11381           /* kludge: allow timeout for initial "feature" command */
11382           FreezeUI();
11383           DisplayMessage("", _("Starting second chess program"));
11384           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11385         }
11386         return;
11387     }
11388     DisplayMessage("", "");
11389     InitChessProgram(&second, FALSE);
11390     SendToProgram("force\n", &second);
11391     if (startedFromSetupPosition) {
11392         SendBoard(&second, backwardMostMove);
11393     if (appData.debugMode) {
11394         fprintf(debugFP, "Two Machines\n");
11395     }
11396     }
11397     for (i = backwardMostMove; i < forwardMostMove; i++) {
11398         SendMoveToProgram(i, &second);
11399     }
11400
11401     gameMode = TwoMachinesPlay;
11402     pausing = FALSE;
11403     ModeHighlight();
11404     SetGameInfo();
11405     DisplayTwoMachinesTitle();
11406     firstMove = TRUE;
11407     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11408         onmove = &first;
11409     } else {
11410         onmove = &second;
11411     }
11412
11413     SendToProgram(first.computerString, &first);
11414     if (first.sendName) {
11415       sprintf(buf, "name %s\n", second.tidy);
11416       SendToProgram(buf, &first);
11417     }
11418     SendToProgram(second.computerString, &second);
11419     if (second.sendName) {
11420       sprintf(buf, "name %s\n", first.tidy);
11421       SendToProgram(buf, &second);
11422     }
11423
11424     ResetClocks();
11425     if (!first.sendTime || !second.sendTime) {
11426         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11427         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11428     }
11429     if (onmove->sendTime) {
11430       if (onmove->useColors) {
11431         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11432       }
11433       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11434     }
11435     if (onmove->useColors) {
11436       SendToProgram(onmove->twoMachinesColor, onmove);
11437     }
11438     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11439 //    SendToProgram("go\n", onmove);
11440     onmove->maybeThinking = TRUE;
11441     SetMachineThinkingEnables();
11442
11443     StartClocks();
11444
11445     if(bookHit) { // [HGM] book: simulate book reply
11446         static char bookMove[MSG_SIZ]; // a bit generous?
11447
11448         programStats.nodes = programStats.depth = programStats.time = 
11449         programStats.score = programStats.got_only_move = 0;
11450         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11451
11452         strcpy(bookMove, "move ");
11453         strcat(bookMove, bookHit);
11454         savedMessage = bookMove; // args for deferred call
11455         savedState = onmove;
11456         ScheduleDelayedEvent(DeferredBookMove, 1);
11457     }
11458 }
11459
11460 void
11461 TrainingEvent()
11462 {
11463     if (gameMode == Training) {
11464       SetTrainingModeOff();
11465       gameMode = PlayFromGameFile;
11466       DisplayMessage("", _("Training mode off"));
11467     } else {
11468       gameMode = Training;
11469       animateTraining = appData.animate;
11470
11471       /* make sure we are not already at the end of the game */
11472       if (currentMove < forwardMostMove) {
11473         SetTrainingModeOn();
11474         DisplayMessage("", _("Training mode on"));
11475       } else {
11476         gameMode = PlayFromGameFile;
11477         DisplayError(_("Already at end of game"), 0);
11478       }
11479     }
11480     ModeHighlight();
11481 }
11482
11483 void
11484 IcsClientEvent()
11485 {
11486     if (!appData.icsActive) return;
11487     switch (gameMode) {
11488       case IcsPlayingWhite:
11489       case IcsPlayingBlack:
11490       case IcsObserving:
11491       case IcsIdle:
11492       case BeginningOfGame:
11493       case IcsExamining:
11494         return;
11495
11496       case EditGame:
11497         break;
11498
11499       case EditPosition:
11500         EditPositionDone(TRUE);
11501         break;
11502
11503       case AnalyzeMode:
11504       case AnalyzeFile:
11505         ExitAnalyzeMode();
11506         break;
11507         
11508       default:
11509         EditGameEvent();
11510         break;
11511     }
11512
11513     gameMode = IcsIdle;
11514     ModeHighlight();
11515     return;
11516 }
11517
11518
11519 void
11520 EditGameEvent()
11521 {
11522     int i;
11523
11524     switch (gameMode) {
11525       case Training:
11526         SetTrainingModeOff();
11527         break;
11528       case MachinePlaysWhite:
11529       case MachinePlaysBlack:
11530       case BeginningOfGame:
11531         SendToProgram("force\n", &first);
11532         SetUserThinkingEnables();
11533         break;
11534       case PlayFromGameFile:
11535         (void) StopLoadGameTimer();
11536         if (gameFileFP != NULL) {
11537             gameFileFP = NULL;
11538         }
11539         break;
11540       case EditPosition:
11541         EditPositionDone(TRUE);
11542         break;
11543       case AnalyzeMode:
11544       case AnalyzeFile:
11545         ExitAnalyzeMode();
11546         SendToProgram("force\n", &first);
11547         break;
11548       case TwoMachinesPlay:
11549         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11550         ResurrectChessProgram();
11551         SetUserThinkingEnables();
11552         break;
11553       case EndOfGame:
11554         ResurrectChessProgram();
11555         break;
11556       case IcsPlayingBlack:
11557       case IcsPlayingWhite:
11558         DisplayError(_("Warning: You are still playing a game"), 0);
11559         break;
11560       case IcsObserving:
11561         DisplayError(_("Warning: You are still observing a game"), 0);
11562         break;
11563       case IcsExamining:
11564         DisplayError(_("Warning: You are still examining a game"), 0);
11565         break;
11566       case IcsIdle:
11567         break;
11568       case EditGame:
11569       default:
11570         return;
11571     }
11572     
11573     pausing = FALSE;
11574     StopClocks();
11575     first.offeredDraw = second.offeredDraw = 0;
11576
11577     if (gameMode == PlayFromGameFile) {
11578         whiteTimeRemaining = timeRemaining[0][currentMove];
11579         blackTimeRemaining = timeRemaining[1][currentMove];
11580         DisplayTitle("");
11581     }
11582
11583     if (gameMode == MachinePlaysWhite ||
11584         gameMode == MachinePlaysBlack ||
11585         gameMode == TwoMachinesPlay ||
11586         gameMode == EndOfGame) {
11587         i = forwardMostMove;
11588         while (i > currentMove) {
11589             SendToProgram("undo\n", &first);
11590             i--;
11591         }
11592         whiteTimeRemaining = timeRemaining[0][currentMove];
11593         blackTimeRemaining = timeRemaining[1][currentMove];
11594         DisplayBothClocks();
11595         if (whiteFlag || blackFlag) {
11596             whiteFlag = blackFlag = 0;
11597         }
11598         DisplayTitle("");
11599     }           
11600     
11601     gameMode = EditGame;
11602     ModeHighlight();
11603     SetGameInfo();
11604 }
11605
11606
11607 void
11608 EditPositionEvent()
11609 {
11610     if (gameMode == EditPosition) {
11611         EditGameEvent();
11612         return;
11613     }
11614     
11615     EditGameEvent();
11616     if (gameMode != EditGame) return;
11617     
11618     gameMode = EditPosition;
11619     ModeHighlight();
11620     SetGameInfo();
11621     if (currentMove > 0)
11622       CopyBoard(boards[0], boards[currentMove]);
11623     
11624     blackPlaysFirst = !WhiteOnMove(currentMove);
11625     ResetClocks();
11626     currentMove = forwardMostMove = backwardMostMove = 0;
11627     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11628     DisplayMove(-1);
11629 }
11630
11631 void
11632 ExitAnalyzeMode()
11633 {
11634     /* [DM] icsEngineAnalyze - possible call from other functions */
11635     if (appData.icsEngineAnalyze) {
11636         appData.icsEngineAnalyze = FALSE;
11637
11638         DisplayMessage("",_("Close ICS engine analyze..."));
11639     }
11640     if (first.analysisSupport && first.analyzing) {
11641       SendToProgram("exit\n", &first);
11642       first.analyzing = FALSE;
11643     }
11644     thinkOutput[0] = NULLCHAR;
11645 }
11646
11647 void
11648 EditPositionDone(Boolean fakeRights)
11649 {
11650     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11651
11652     startedFromSetupPosition = TRUE;
11653     InitChessProgram(&first, FALSE);
11654     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11655       boards[0][EP_STATUS] = EP_NONE;
11656       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11657     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11658         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11659         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11660       } else boards[0][CASTLING][2] = NoRights;
11661     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11662         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11663         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11664       } else boards[0][CASTLING][5] = NoRights;
11665     }
11666     SendToProgram("force\n", &first);
11667     if (blackPlaysFirst) {
11668         strcpy(moveList[0], "");
11669         strcpy(parseList[0], "");
11670         currentMove = forwardMostMove = backwardMostMove = 1;
11671         CopyBoard(boards[1], boards[0]);
11672     } else {
11673         currentMove = forwardMostMove = backwardMostMove = 0;
11674     }
11675     SendBoard(&first, forwardMostMove);
11676     if (appData.debugMode) {
11677         fprintf(debugFP, "EditPosDone\n");
11678     }
11679     DisplayTitle("");
11680     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11681     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11682     gameMode = EditGame;
11683     ModeHighlight();
11684     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11685     ClearHighlights(); /* [AS] */
11686 }
11687
11688 /* Pause for `ms' milliseconds */
11689 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11690 void
11691 TimeDelay(ms)
11692      long ms;
11693 {
11694     TimeMark m1, m2;
11695
11696     GetTimeMark(&m1);
11697     do {
11698         GetTimeMark(&m2);
11699     } while (SubtractTimeMarks(&m2, &m1) < ms);
11700 }
11701
11702 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11703 void
11704 SendMultiLineToICS(buf)
11705      char *buf;
11706 {
11707     char temp[MSG_SIZ+1], *p;
11708     int len;
11709
11710     len = strlen(buf);
11711     if (len > MSG_SIZ)
11712       len = MSG_SIZ;
11713   
11714     strncpy(temp, buf, len);
11715     temp[len] = 0;
11716
11717     p = temp;
11718     while (*p) {
11719         if (*p == '\n' || *p == '\r')
11720           *p = ' ';
11721         ++p;
11722     }
11723
11724     strcat(temp, "\n");
11725     SendToICS(temp);
11726     SendToPlayer(temp, strlen(temp));
11727 }
11728
11729 void
11730 SetWhiteToPlayEvent()
11731 {
11732     if (gameMode == EditPosition) {
11733         blackPlaysFirst = FALSE;
11734         DisplayBothClocks();    /* works because currentMove is 0 */
11735     } else if (gameMode == IcsExamining) {
11736         SendToICS(ics_prefix);
11737         SendToICS("tomove white\n");
11738     }
11739 }
11740
11741 void
11742 SetBlackToPlayEvent()
11743 {
11744     if (gameMode == EditPosition) {
11745         blackPlaysFirst = TRUE;
11746         currentMove = 1;        /* kludge */
11747         DisplayBothClocks();
11748         currentMove = 0;
11749     } else if (gameMode == IcsExamining) {
11750         SendToICS(ics_prefix);
11751         SendToICS("tomove black\n");
11752     }
11753 }
11754
11755 void
11756 EditPositionMenuEvent(selection, x, y)
11757      ChessSquare selection;
11758      int x, y;
11759 {
11760     char buf[MSG_SIZ];
11761     ChessSquare piece = boards[0][y][x];
11762
11763     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11764
11765     switch (selection) {
11766       case ClearBoard:
11767         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11768             SendToICS(ics_prefix);
11769             SendToICS("bsetup clear\n");
11770         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11771             SendToICS(ics_prefix);
11772             SendToICS("clearboard\n");
11773         } else {
11774             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11775                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11776                 for (y = 0; y < BOARD_HEIGHT; y++) {
11777                     if (gameMode == IcsExamining) {
11778                         if (boards[currentMove][y][x] != EmptySquare) {
11779                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11780                                     AAA + x, ONE + y);
11781                             SendToICS(buf);
11782                         }
11783                     } else {
11784                         boards[0][y][x] = p;
11785                     }
11786                 }
11787             }
11788         }
11789         if (gameMode == EditPosition) {
11790             DrawPosition(FALSE, boards[0]);
11791         }
11792         break;
11793
11794       case WhitePlay:
11795         SetWhiteToPlayEvent();
11796         break;
11797
11798       case BlackPlay:
11799         SetBlackToPlayEvent();
11800         break;
11801
11802       case EmptySquare:
11803         if (gameMode == IcsExamining) {
11804             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11805             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11806             SendToICS(buf);
11807         } else {
11808             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11809                 if(x == BOARD_LEFT-2) {
11810                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11811                     boards[0][y][1] = 0;
11812                 } else
11813                 if(x == BOARD_RGHT+1) {
11814                     if(y >= gameInfo.holdingsSize) break;
11815                     boards[0][y][BOARD_WIDTH-2] = 0;
11816                 } else break;
11817             }
11818             boards[0][y][x] = EmptySquare;
11819             DrawPosition(FALSE, boards[0]);
11820         }
11821         break;
11822
11823       case PromotePiece:
11824         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11825            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11826             selection = (ChessSquare) (PROMOTED piece);
11827         } else if(piece == EmptySquare) selection = WhiteSilver;
11828         else selection = (ChessSquare)((int)piece - 1);
11829         goto defaultlabel;
11830
11831       case DemotePiece:
11832         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11833            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11834             selection = (ChessSquare) (DEMOTED piece);
11835         } else if(piece == EmptySquare) selection = BlackSilver;
11836         else selection = (ChessSquare)((int)piece + 1);       
11837         goto defaultlabel;
11838
11839       case WhiteQueen:
11840       case BlackQueen:
11841         if(gameInfo.variant == VariantShatranj ||
11842            gameInfo.variant == VariantXiangqi  ||
11843            gameInfo.variant == VariantCourier  ||
11844            gameInfo.variant == VariantMakruk     )
11845             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11846         goto defaultlabel;
11847
11848       case WhiteKing:
11849       case BlackKing:
11850         if(gameInfo.variant == VariantXiangqi)
11851             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11852         if(gameInfo.variant == VariantKnightmate)
11853             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11854       default:
11855         defaultlabel:
11856         if (gameMode == IcsExamining) {
11857             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11858             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11859                     PieceToChar(selection), AAA + x, ONE + y);
11860             SendToICS(buf);
11861         } else {
11862             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11863                 int n;
11864                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11865                     n = PieceToNumber(selection - BlackPawn);
11866                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11867                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11868                     boards[0][BOARD_HEIGHT-1-n][1]++;
11869                 } else
11870                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11871                     n = PieceToNumber(selection);
11872                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11873                     boards[0][n][BOARD_WIDTH-1] = selection;
11874                     boards[0][n][BOARD_WIDTH-2]++;
11875                 }
11876             } else
11877             boards[0][y][x] = selection;
11878             DrawPosition(TRUE, boards[0]);
11879         }
11880         break;
11881     }
11882 }
11883
11884
11885 void
11886 DropMenuEvent(selection, x, y)
11887      ChessSquare selection;
11888      int x, y;
11889 {
11890     ChessMove moveType;
11891
11892     switch (gameMode) {
11893       case IcsPlayingWhite:
11894       case MachinePlaysBlack:
11895         if (!WhiteOnMove(currentMove)) {
11896             DisplayMoveError(_("It is Black's turn"));
11897             return;
11898         }
11899         moveType = WhiteDrop;
11900         break;
11901       case IcsPlayingBlack:
11902       case MachinePlaysWhite:
11903         if (WhiteOnMove(currentMove)) {
11904             DisplayMoveError(_("It is White's turn"));
11905             return;
11906         }
11907         moveType = BlackDrop;
11908         break;
11909       case EditGame:
11910         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11911         break;
11912       default:
11913         return;
11914     }
11915
11916     if (moveType == BlackDrop && selection < BlackPawn) {
11917       selection = (ChessSquare) ((int) selection
11918                                  + (int) BlackPawn - (int) WhitePawn);
11919     }
11920     if (boards[currentMove][y][x] != EmptySquare) {
11921         DisplayMoveError(_("That square is occupied"));
11922         return;
11923     }
11924
11925     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11926 }
11927
11928 void
11929 AcceptEvent()
11930 {
11931     /* Accept a pending offer of any kind from opponent */
11932     
11933     if (appData.icsActive) {
11934         SendToICS(ics_prefix);
11935         SendToICS("accept\n");
11936     } else if (cmailMsgLoaded) {
11937         if (currentMove == cmailOldMove &&
11938             commentList[cmailOldMove] != NULL &&
11939             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11940                    "Black offers a draw" : "White offers a draw")) {
11941             TruncateGame();
11942             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11943             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11944         } else {
11945             DisplayError(_("There is no pending offer on this move"), 0);
11946             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11947         }
11948     } else {
11949         /* Not used for offers from chess program */
11950     }
11951 }
11952
11953 void
11954 DeclineEvent()
11955 {
11956     /* Decline a pending offer of any kind from opponent */
11957     
11958     if (appData.icsActive) {
11959         SendToICS(ics_prefix);
11960         SendToICS("decline\n");
11961     } else if (cmailMsgLoaded) {
11962         if (currentMove == cmailOldMove &&
11963             commentList[cmailOldMove] != NULL &&
11964             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11965                    "Black offers a draw" : "White offers a draw")) {
11966 #ifdef NOTDEF
11967             AppendComment(cmailOldMove, "Draw declined", TRUE);
11968             DisplayComment(cmailOldMove - 1, "Draw declined");
11969 #endif /*NOTDEF*/
11970         } else {
11971             DisplayError(_("There is no pending offer on this move"), 0);
11972         }
11973     } else {
11974         /* Not used for offers from chess program */
11975     }
11976 }
11977
11978 void
11979 RematchEvent()
11980 {
11981     /* Issue ICS rematch command */
11982     if (appData.icsActive) {
11983         SendToICS(ics_prefix);
11984         SendToICS("rematch\n");
11985     }
11986 }
11987
11988 void
11989 CallFlagEvent()
11990 {
11991     /* Call your opponent's flag (claim a win on time) */
11992     if (appData.icsActive) {
11993         SendToICS(ics_prefix);
11994         SendToICS("flag\n");
11995     } else {
11996         switch (gameMode) {
11997           default:
11998             return;
11999           case MachinePlaysWhite:
12000             if (whiteFlag) {
12001                 if (blackFlag)
12002                   GameEnds(GameIsDrawn, "Both players ran out of time",
12003                            GE_PLAYER);
12004                 else
12005                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12006             } else {
12007                 DisplayError(_("Your opponent is not out of time"), 0);
12008             }
12009             break;
12010           case MachinePlaysBlack:
12011             if (blackFlag) {
12012                 if (whiteFlag)
12013                   GameEnds(GameIsDrawn, "Both players ran out of time",
12014                            GE_PLAYER);
12015                 else
12016                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12017             } else {
12018                 DisplayError(_("Your opponent is not out of time"), 0);
12019             }
12020             break;
12021         }
12022     }
12023 }
12024
12025 void
12026 DrawEvent()
12027 {
12028     /* Offer draw or accept pending draw offer from opponent */
12029     
12030     if (appData.icsActive) {
12031         /* Note: tournament rules require draw offers to be
12032            made after you make your move but before you punch
12033            your clock.  Currently ICS doesn't let you do that;
12034            instead, you immediately punch your clock after making
12035            a move, but you can offer a draw at any time. */
12036         
12037         SendToICS(ics_prefix);
12038         SendToICS("draw\n");
12039         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12040     } else if (cmailMsgLoaded) {
12041         if (currentMove == cmailOldMove &&
12042             commentList[cmailOldMove] != NULL &&
12043             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12044                    "Black offers a draw" : "White offers a draw")) {
12045             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12046             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12047         } else if (currentMove == cmailOldMove + 1) {
12048             char *offer = WhiteOnMove(cmailOldMove) ?
12049               "White offers a draw" : "Black offers a draw";
12050             AppendComment(currentMove, offer, TRUE);
12051             DisplayComment(currentMove - 1, offer);
12052             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12053         } else {
12054             DisplayError(_("You must make your move before offering a draw"), 0);
12055             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12056         }
12057     } else if (first.offeredDraw) {
12058         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12059     } else {
12060         if (first.sendDrawOffers) {
12061             SendToProgram("draw\n", &first);
12062             userOfferedDraw = TRUE;
12063         }
12064     }
12065 }
12066
12067 void
12068 AdjournEvent()
12069 {
12070     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12071     
12072     if (appData.icsActive) {
12073         SendToICS(ics_prefix);
12074         SendToICS("adjourn\n");
12075     } else {
12076         /* Currently GNU Chess doesn't offer or accept Adjourns */
12077     }
12078 }
12079
12080
12081 void
12082 AbortEvent()
12083 {
12084     /* Offer Abort or accept pending Abort offer from opponent */
12085     
12086     if (appData.icsActive) {
12087         SendToICS(ics_prefix);
12088         SendToICS("abort\n");
12089     } else {
12090         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12091     }
12092 }
12093
12094 void
12095 ResignEvent()
12096 {
12097     /* Resign.  You can do this even if it's not your turn. */
12098     
12099     if (appData.icsActive) {
12100         SendToICS(ics_prefix);
12101         SendToICS("resign\n");
12102     } else {
12103         switch (gameMode) {
12104           case MachinePlaysWhite:
12105             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12106             break;
12107           case MachinePlaysBlack:
12108             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12109             break;
12110           case EditGame:
12111             if (cmailMsgLoaded) {
12112                 TruncateGame();
12113                 if (WhiteOnMove(cmailOldMove)) {
12114                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12115                 } else {
12116                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12117                 }
12118                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12119             }
12120             break;
12121           default:
12122             break;
12123         }
12124     }
12125 }
12126
12127
12128 void
12129 StopObservingEvent()
12130 {
12131     /* Stop observing current games */
12132     SendToICS(ics_prefix);
12133     SendToICS("unobserve\n");
12134 }
12135
12136 void
12137 StopExaminingEvent()
12138 {
12139     /* Stop observing current game */
12140     SendToICS(ics_prefix);
12141     SendToICS("unexamine\n");
12142 }
12143
12144 void
12145 ForwardInner(target)
12146      int target;
12147 {
12148     int limit;
12149
12150     if (appData.debugMode)
12151         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12152                 target, currentMove, forwardMostMove);
12153
12154     if (gameMode == EditPosition)
12155       return;
12156
12157     if (gameMode == PlayFromGameFile && !pausing)
12158       PauseEvent();
12159     
12160     if (gameMode == IcsExamining && pausing)
12161       limit = pauseExamForwardMostMove;
12162     else
12163       limit = forwardMostMove;
12164     
12165     if (target > limit) target = limit;
12166
12167     if (target > 0 && moveList[target - 1][0]) {
12168         int fromX, fromY, toX, toY;
12169         toX = moveList[target - 1][2] - AAA;
12170         toY = moveList[target - 1][3] - ONE;
12171         if (moveList[target - 1][1] == '@') {
12172             if (appData.highlightLastMove) {
12173                 SetHighlights(-1, -1, toX, toY);
12174             }
12175         } else {
12176             fromX = moveList[target - 1][0] - AAA;
12177             fromY = moveList[target - 1][1] - ONE;
12178             if (target == currentMove + 1) {
12179                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12180             }
12181             if (appData.highlightLastMove) {
12182                 SetHighlights(fromX, fromY, toX, toY);
12183             }
12184         }
12185     }
12186     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12187         gameMode == Training || gameMode == PlayFromGameFile || 
12188         gameMode == AnalyzeFile) {
12189         while (currentMove < target) {
12190             SendMoveToProgram(currentMove++, &first);
12191         }
12192     } else {
12193         currentMove = target;
12194     }
12195     
12196     if (gameMode == EditGame || gameMode == EndOfGame) {
12197         whiteTimeRemaining = timeRemaining[0][currentMove];
12198         blackTimeRemaining = timeRemaining[1][currentMove];
12199     }
12200     DisplayBothClocks();
12201     DisplayMove(currentMove - 1);
12202     DrawPosition(FALSE, boards[currentMove]);
12203     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12204     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12205         DisplayComment(currentMove - 1, commentList[currentMove]);
12206     }
12207 }
12208
12209
12210 void
12211 ForwardEvent()
12212 {
12213     if (gameMode == IcsExamining && !pausing) {
12214         SendToICS(ics_prefix);
12215         SendToICS("forward\n");
12216     } else {
12217         ForwardInner(currentMove + 1);
12218     }
12219 }
12220
12221 void
12222 ToEndEvent()
12223 {
12224     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12225         /* to optimze, we temporarily turn off analysis mode while we feed
12226          * the remaining moves to the engine. Otherwise we get analysis output
12227          * after each move.
12228          */ 
12229         if (first.analysisSupport) {
12230           SendToProgram("exit\nforce\n", &first);
12231           first.analyzing = FALSE;
12232         }
12233     }
12234         
12235     if (gameMode == IcsExamining && !pausing) {
12236         SendToICS(ics_prefix);
12237         SendToICS("forward 999999\n");
12238     } else {
12239         ForwardInner(forwardMostMove);
12240     }
12241
12242     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12243         /* we have fed all the moves, so reactivate analysis mode */
12244         SendToProgram("analyze\n", &first);
12245         first.analyzing = TRUE;
12246         /*first.maybeThinking = TRUE;*/
12247         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12248     }
12249 }
12250
12251 void
12252 BackwardInner(target)
12253      int target;
12254 {
12255     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12256
12257     if (appData.debugMode)
12258         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12259                 target, currentMove, forwardMostMove);
12260
12261     if (gameMode == EditPosition) return;
12262     if (currentMove <= backwardMostMove) {
12263         ClearHighlights();
12264         DrawPosition(full_redraw, boards[currentMove]);
12265         return;
12266     }
12267     if (gameMode == PlayFromGameFile && !pausing)
12268       PauseEvent();
12269     
12270     if (moveList[target][0]) {
12271         int fromX, fromY, toX, toY;
12272         toX = moveList[target][2] - AAA;
12273         toY = moveList[target][3] - ONE;
12274         if (moveList[target][1] == '@') {
12275             if (appData.highlightLastMove) {
12276                 SetHighlights(-1, -1, toX, toY);
12277             }
12278         } else {
12279             fromX = moveList[target][0] - AAA;
12280             fromY = moveList[target][1] - ONE;
12281             if (target == currentMove - 1) {
12282                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12283             }
12284             if (appData.highlightLastMove) {
12285                 SetHighlights(fromX, fromY, toX, toY);
12286             }
12287         }
12288     }
12289     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12290         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12291         while (currentMove > target) {
12292             SendToProgram("undo\n", &first);
12293             currentMove--;
12294         }
12295     } else {
12296         currentMove = target;
12297     }
12298     
12299     if (gameMode == EditGame || gameMode == EndOfGame) {
12300         whiteTimeRemaining = timeRemaining[0][currentMove];
12301         blackTimeRemaining = timeRemaining[1][currentMove];
12302     }
12303     DisplayBothClocks();
12304     DisplayMove(currentMove - 1);
12305     DrawPosition(full_redraw, boards[currentMove]);
12306     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12307     // [HGM] PV info: routine tests if comment empty
12308     DisplayComment(currentMove - 1, commentList[currentMove]);
12309 }
12310
12311 void
12312 BackwardEvent()
12313 {
12314     if (gameMode == IcsExamining && !pausing) {
12315         SendToICS(ics_prefix);
12316         SendToICS("backward\n");
12317     } else {
12318         BackwardInner(currentMove - 1);
12319     }
12320 }
12321
12322 void
12323 ToStartEvent()
12324 {
12325     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12326         /* to optimize, we temporarily turn off analysis mode while we undo
12327          * all the moves. Otherwise we get analysis output after each undo.
12328          */ 
12329         if (first.analysisSupport) {
12330           SendToProgram("exit\nforce\n", &first);
12331           first.analyzing = FALSE;
12332         }
12333     }
12334
12335     if (gameMode == IcsExamining && !pausing) {
12336         SendToICS(ics_prefix);
12337         SendToICS("backward 999999\n");
12338     } else {
12339         BackwardInner(backwardMostMove);
12340     }
12341
12342     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12343         /* we have fed all the moves, so reactivate analysis mode */
12344         SendToProgram("analyze\n", &first);
12345         first.analyzing = TRUE;
12346         /*first.maybeThinking = TRUE;*/
12347         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12348     }
12349 }
12350
12351 void
12352 ToNrEvent(int to)
12353 {
12354   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12355   if (to >= forwardMostMove) to = forwardMostMove;
12356   if (to <= backwardMostMove) to = backwardMostMove;
12357   if (to < currentMove) {
12358     BackwardInner(to);
12359   } else {
12360     ForwardInner(to);
12361   }
12362 }
12363
12364 void
12365 RevertEvent()
12366 {
12367     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12368         return;
12369     }
12370     if (gameMode != IcsExamining) {
12371         DisplayError(_("You are not examining a game"), 0);
12372         return;
12373     }
12374     if (pausing) {
12375         DisplayError(_("You can't revert while pausing"), 0);
12376         return;
12377     }
12378     SendToICS(ics_prefix);
12379     SendToICS("revert\n");
12380 }
12381
12382 void
12383 RetractMoveEvent()
12384 {
12385     switch (gameMode) {
12386       case MachinePlaysWhite:
12387       case MachinePlaysBlack:
12388         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12389             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12390             return;
12391         }
12392         if (forwardMostMove < 2) return;
12393         currentMove = forwardMostMove = forwardMostMove - 2;
12394         whiteTimeRemaining = timeRemaining[0][currentMove];
12395         blackTimeRemaining = timeRemaining[1][currentMove];
12396         DisplayBothClocks();
12397         DisplayMove(currentMove - 1);
12398         ClearHighlights();/*!! could figure this out*/
12399         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12400         SendToProgram("remove\n", &first);
12401         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12402         break;
12403
12404       case BeginningOfGame:
12405       default:
12406         break;
12407
12408       case IcsPlayingWhite:
12409       case IcsPlayingBlack:
12410         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12411             SendToICS(ics_prefix);
12412             SendToICS("takeback 2\n");
12413         } else {
12414             SendToICS(ics_prefix);
12415             SendToICS("takeback 1\n");
12416         }
12417         break;
12418     }
12419 }
12420
12421 void
12422 MoveNowEvent()
12423 {
12424     ChessProgramState *cps;
12425
12426     switch (gameMode) {
12427       case MachinePlaysWhite:
12428         if (!WhiteOnMove(forwardMostMove)) {
12429             DisplayError(_("It is your turn"), 0);
12430             return;
12431         }
12432         cps = &first;
12433         break;
12434       case MachinePlaysBlack:
12435         if (WhiteOnMove(forwardMostMove)) {
12436             DisplayError(_("It is your turn"), 0);
12437             return;
12438         }
12439         cps = &first;
12440         break;
12441       case TwoMachinesPlay:
12442         if (WhiteOnMove(forwardMostMove) ==
12443             (first.twoMachinesColor[0] == 'w')) {
12444             cps = &first;
12445         } else {
12446             cps = &second;
12447         }
12448         break;
12449       case BeginningOfGame:
12450       default:
12451         return;
12452     }
12453     SendToProgram("?\n", cps);
12454 }
12455
12456 void
12457 TruncateGameEvent()
12458 {
12459     EditGameEvent();
12460     if (gameMode != EditGame) return;
12461     TruncateGame();
12462 }
12463
12464 void
12465 TruncateGame()
12466 {
12467     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12468     if (forwardMostMove > currentMove) {
12469         if (gameInfo.resultDetails != NULL) {
12470             free(gameInfo.resultDetails);
12471             gameInfo.resultDetails = NULL;
12472             gameInfo.result = GameUnfinished;
12473         }
12474         forwardMostMove = currentMove;
12475         HistorySet(parseList, backwardMostMove, forwardMostMove,
12476                    currentMove-1);
12477     }
12478 }
12479
12480 void
12481 HintEvent()
12482 {
12483     if (appData.noChessProgram) return;
12484     switch (gameMode) {
12485       case MachinePlaysWhite:
12486         if (WhiteOnMove(forwardMostMove)) {
12487             DisplayError(_("Wait until your turn"), 0);
12488             return;
12489         }
12490         break;
12491       case BeginningOfGame:
12492       case MachinePlaysBlack:
12493         if (!WhiteOnMove(forwardMostMove)) {
12494             DisplayError(_("Wait until your turn"), 0);
12495             return;
12496         }
12497         break;
12498       default:
12499         DisplayError(_("No hint available"), 0);
12500         return;
12501     }
12502     SendToProgram("hint\n", &first);
12503     hintRequested = TRUE;
12504 }
12505
12506 void
12507 BookEvent()
12508 {
12509     if (appData.noChessProgram) return;
12510     switch (gameMode) {
12511       case MachinePlaysWhite:
12512         if (WhiteOnMove(forwardMostMove)) {
12513             DisplayError(_("Wait until your turn"), 0);
12514             return;
12515         }
12516         break;
12517       case BeginningOfGame:
12518       case MachinePlaysBlack:
12519         if (!WhiteOnMove(forwardMostMove)) {
12520             DisplayError(_("Wait until your turn"), 0);
12521             return;
12522         }
12523         break;
12524       case EditPosition:
12525         EditPositionDone(TRUE);
12526         break;
12527       case TwoMachinesPlay:
12528         return;
12529       default:
12530         break;
12531     }
12532     SendToProgram("bk\n", &first);
12533     bookOutput[0] = NULLCHAR;
12534     bookRequested = TRUE;
12535 }
12536
12537 void
12538 AboutGameEvent()
12539 {
12540     char *tags = PGNTags(&gameInfo);
12541     TagsPopUp(tags, CmailMsg());
12542     free(tags);
12543 }
12544
12545 /* end button procedures */
12546
12547 void
12548 PrintPosition(fp, move)
12549      FILE *fp;
12550      int move;
12551 {
12552     int i, j;
12553     
12554     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12555         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12556             char c = PieceToChar(boards[move][i][j]);
12557             fputc(c == 'x' ? '.' : c, fp);
12558             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12559         }
12560     }
12561     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12562       fprintf(fp, "white to play\n");
12563     else
12564       fprintf(fp, "black to play\n");
12565 }
12566
12567 void
12568 PrintOpponents(fp)
12569      FILE *fp;
12570 {
12571     if (gameInfo.white != NULL) {
12572         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12573     } else {
12574         fprintf(fp, "\n");
12575     }
12576 }
12577
12578 /* Find last component of program's own name, using some heuristics */
12579 void
12580 TidyProgramName(prog, host, buf)
12581      char *prog, *host, buf[MSG_SIZ];
12582 {
12583     char *p, *q;
12584     int local = (strcmp(host, "localhost") == 0);
12585     while (!local && (p = strchr(prog, ';')) != NULL) {
12586         p++;
12587         while (*p == ' ') p++;
12588         prog = p;
12589     }
12590     if (*prog == '"' || *prog == '\'') {
12591         q = strchr(prog + 1, *prog);
12592     } else {
12593         q = strchr(prog, ' ');
12594     }
12595     if (q == NULL) q = prog + strlen(prog);
12596     p = q;
12597     while (p >= prog && *p != '/' && *p != '\\') p--;
12598     p++;
12599     if(p == prog && *p == '"') p++;
12600     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12601     memcpy(buf, p, q - p);
12602     buf[q - p] = NULLCHAR;
12603     if (!local) {
12604         strcat(buf, "@");
12605         strcat(buf, host);
12606     }
12607 }
12608
12609 char *
12610 TimeControlTagValue()
12611 {
12612     char buf[MSG_SIZ];
12613     if (!appData.clockMode) {
12614         strcpy(buf, "-");
12615     } else if (movesPerSession > 0) {
12616         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12617     } else if (timeIncrement == 0) {
12618         sprintf(buf, "%ld", timeControl/1000);
12619     } else {
12620         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12621     }
12622     return StrSave(buf);
12623 }
12624
12625 void
12626 SetGameInfo()
12627 {
12628     /* This routine is used only for certain modes */
12629     VariantClass v = gameInfo.variant;
12630     ChessMove r = GameUnfinished;
12631     char *p = NULL;
12632
12633     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12634         r = gameInfo.result; 
12635         p = gameInfo.resultDetails; 
12636         gameInfo.resultDetails = NULL;
12637     }
12638     ClearGameInfo(&gameInfo);
12639     gameInfo.variant = v;
12640
12641     switch (gameMode) {
12642       case MachinePlaysWhite:
12643         gameInfo.event = StrSave( appData.pgnEventHeader );
12644         gameInfo.site = StrSave(HostName());
12645         gameInfo.date = PGNDate();
12646         gameInfo.round = StrSave("-");
12647         gameInfo.white = StrSave(first.tidy);
12648         gameInfo.black = StrSave(UserName());
12649         gameInfo.timeControl = TimeControlTagValue();
12650         break;
12651
12652       case MachinePlaysBlack:
12653         gameInfo.event = StrSave( appData.pgnEventHeader );
12654         gameInfo.site = StrSave(HostName());
12655         gameInfo.date = PGNDate();
12656         gameInfo.round = StrSave("-");
12657         gameInfo.white = StrSave(UserName());
12658         gameInfo.black = StrSave(first.tidy);
12659         gameInfo.timeControl = TimeControlTagValue();
12660         break;
12661
12662       case TwoMachinesPlay:
12663         gameInfo.event = StrSave( appData.pgnEventHeader );
12664         gameInfo.site = StrSave(HostName());
12665         gameInfo.date = PGNDate();
12666         if (matchGame > 0) {
12667             char buf[MSG_SIZ];
12668             sprintf(buf, "%d", matchGame);
12669             gameInfo.round = StrSave(buf);
12670         } else {
12671             gameInfo.round = StrSave("-");
12672         }
12673         if (first.twoMachinesColor[0] == 'w') {
12674             gameInfo.white = StrSave(first.tidy);
12675             gameInfo.black = StrSave(second.tidy);
12676         } else {
12677             gameInfo.white = StrSave(second.tidy);
12678             gameInfo.black = StrSave(first.tidy);
12679         }
12680         gameInfo.timeControl = TimeControlTagValue();
12681         break;
12682
12683       case EditGame:
12684         gameInfo.event = StrSave("Edited game");
12685         gameInfo.site = StrSave(HostName());
12686         gameInfo.date = PGNDate();
12687         gameInfo.round = StrSave("-");
12688         gameInfo.white = StrSave("-");
12689         gameInfo.black = StrSave("-");
12690         gameInfo.result = r;
12691         gameInfo.resultDetails = p;
12692         break;
12693
12694       case EditPosition:
12695         gameInfo.event = StrSave("Edited position");
12696         gameInfo.site = StrSave(HostName());
12697         gameInfo.date = PGNDate();
12698         gameInfo.round = StrSave("-");
12699         gameInfo.white = StrSave("-");
12700         gameInfo.black = StrSave("-");
12701         break;
12702
12703       case IcsPlayingWhite:
12704       case IcsPlayingBlack:
12705       case IcsObserving:
12706       case IcsExamining:
12707         break;
12708
12709       case PlayFromGameFile:
12710         gameInfo.event = StrSave("Game from non-PGN file");
12711         gameInfo.site = StrSave(HostName());
12712         gameInfo.date = PGNDate();
12713         gameInfo.round = StrSave("-");
12714         gameInfo.white = StrSave("?");
12715         gameInfo.black = StrSave("?");
12716         break;
12717
12718       default:
12719         break;
12720     }
12721 }
12722
12723 void
12724 ReplaceComment(index, text)
12725      int index;
12726      char *text;
12727 {
12728     int len;
12729
12730     while (*text == '\n') text++;
12731     len = strlen(text);
12732     while (len > 0 && text[len - 1] == '\n') len--;
12733
12734     if (commentList[index] != NULL)
12735       free(commentList[index]);
12736
12737     if (len == 0) {
12738         commentList[index] = NULL;
12739         return;
12740     }
12741   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12742       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12743       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12744     commentList[index] = (char *) malloc(len + 2);
12745     strncpy(commentList[index], text, len);
12746     commentList[index][len] = '\n';
12747     commentList[index][len + 1] = NULLCHAR;
12748   } else { 
12749     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12750     char *p;
12751     commentList[index] = (char *) malloc(len + 6);
12752     strcpy(commentList[index], "{\n");
12753     strncpy(commentList[index]+2, text, len);
12754     commentList[index][len+2] = NULLCHAR;
12755     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12756     strcat(commentList[index], "\n}\n");
12757   }
12758 }
12759
12760 void
12761 CrushCRs(text)
12762      char *text;
12763 {
12764   char *p = text;
12765   char *q = text;
12766   char ch;
12767
12768   do {
12769     ch = *p++;
12770     if (ch == '\r') continue;
12771     *q++ = ch;
12772   } while (ch != '\0');
12773 }
12774
12775 void
12776 AppendComment(index, text, addBraces)
12777      int index;
12778      char *text;
12779      Boolean addBraces; // [HGM] braces: tells if we should add {}
12780 {
12781     int oldlen, len;
12782     char *old;
12783
12784 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12785     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12786
12787     CrushCRs(text);
12788     while (*text == '\n') text++;
12789     len = strlen(text);
12790     while (len > 0 && text[len - 1] == '\n') len--;
12791
12792     if (len == 0) return;
12793
12794     if (commentList[index] != NULL) {
12795         old = commentList[index];
12796         oldlen = strlen(old);
12797         while(commentList[index][oldlen-1] ==  '\n')
12798           commentList[index][--oldlen] = NULLCHAR;
12799         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12800         strcpy(commentList[index], old);
12801         free(old);
12802         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12803         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12804           if(addBraces) addBraces = FALSE; else { text++; len--; }
12805           while (*text == '\n') { text++; len--; }
12806           commentList[index][--oldlen] = NULLCHAR;
12807       }
12808         if(addBraces) strcat(commentList[index], "\n{\n");
12809         else          strcat(commentList[index], "\n");
12810         strcat(commentList[index], text);
12811         if(addBraces) strcat(commentList[index], "\n}\n");
12812         else          strcat(commentList[index], "\n");
12813     } else {
12814         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12815         if(addBraces)
12816              strcpy(commentList[index], "{\n");
12817         else commentList[index][0] = NULLCHAR;
12818         strcat(commentList[index], text);
12819         strcat(commentList[index], "\n");
12820         if(addBraces) strcat(commentList[index], "}\n");
12821     }
12822 }
12823
12824 static char * FindStr( char * text, char * sub_text )
12825 {
12826     char * result = strstr( text, sub_text );
12827
12828     if( result != NULL ) {
12829         result += strlen( sub_text );
12830     }
12831
12832     return result;
12833 }
12834
12835 /* [AS] Try to extract PV info from PGN comment */
12836 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12837 char *GetInfoFromComment( int index, char * text )
12838 {
12839     char * sep = text;
12840
12841     if( text != NULL && index > 0 ) {
12842         int score = 0;
12843         int depth = 0;
12844         int time = -1, sec = 0, deci;
12845         char * s_eval = FindStr( text, "[%eval " );
12846         char * s_emt = FindStr( text, "[%emt " );
12847
12848         if( s_eval != NULL || s_emt != NULL ) {
12849             /* New style */
12850             char delim;
12851
12852             if( s_eval != NULL ) {
12853                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12854                     return text;
12855                 }
12856
12857                 if( delim != ']' ) {
12858                     return text;
12859                 }
12860             }
12861
12862             if( s_emt != NULL ) {
12863             }
12864                 return text;
12865         }
12866         else {
12867             /* We expect something like: [+|-]nnn.nn/dd */
12868             int score_lo = 0;
12869
12870             if(*text != '{') return text; // [HGM] braces: must be normal comment
12871
12872             sep = strchr( text, '/' );
12873             if( sep == NULL || sep < (text+4) ) {
12874                 return text;
12875             }
12876
12877             time = -1; sec = -1; deci = -1;
12878             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12879                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12880                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12881                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12882                 return text;
12883             }
12884
12885             if( score_lo < 0 || score_lo >= 100 ) {
12886                 return text;
12887             }
12888
12889             if(sec >= 0) time = 600*time + 10*sec; else
12890             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12891
12892             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12893
12894             /* [HGM] PV time: now locate end of PV info */
12895             while( *++sep >= '0' && *sep <= '9'); // strip depth
12896             if(time >= 0)
12897             while( *++sep >= '0' && *sep <= '9'); // strip time
12898             if(sec >= 0)
12899             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12900             if(deci >= 0)
12901             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12902             while(*sep == ' ') sep++;
12903         }
12904
12905         if( depth <= 0 ) {
12906             return text;
12907         }
12908
12909         if( time < 0 ) {
12910             time = -1;
12911         }
12912
12913         pvInfoList[index-1].depth = depth;
12914         pvInfoList[index-1].score = score;
12915         pvInfoList[index-1].time  = 10*time; // centi-sec
12916         if(*sep == '}') *sep = 0; else *--sep = '{';
12917     }
12918     return sep;
12919 }
12920
12921 void
12922 SendToProgram(message, cps)
12923      char *message;
12924      ChessProgramState *cps;
12925 {
12926     int count, outCount, error;
12927     char buf[MSG_SIZ];
12928
12929     if (cps->pr == NULL) return;
12930     Attention(cps);
12931     
12932     if (appData.debugMode) {
12933         TimeMark now;
12934         GetTimeMark(&now);
12935         fprintf(debugFP, "%ld >%-6s: %s", 
12936                 SubtractTimeMarks(&now, &programStartTime),
12937                 cps->which, message);
12938     }
12939     
12940     count = strlen(message);
12941     outCount = OutputToProcess(cps->pr, message, count, &error);
12942     if (outCount < count && !exiting 
12943                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12944         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12945         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12946             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12947                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12948                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12949             } else {
12950                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12951             }
12952             gameInfo.resultDetails = StrSave(buf);
12953         }
12954         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12955     }
12956 }
12957
12958 void
12959 ReceiveFromProgram(isr, closure, message, count, error)
12960      InputSourceRef isr;
12961      VOIDSTAR closure;
12962      char *message;
12963      int count;
12964      int error;
12965 {
12966     char *end_str;
12967     char buf[MSG_SIZ];
12968     ChessProgramState *cps = (ChessProgramState *)closure;
12969
12970     if (isr != cps->isr) return; /* Killed intentionally */
12971     if (count <= 0) {
12972         if (count == 0) {
12973             sprintf(buf,
12974                     _("Error: %s chess program (%s) exited unexpectedly"),
12975                     cps->which, cps->program);
12976         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12977                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12978                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12979                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12980                 } else {
12981                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12982                 }
12983                 gameInfo.resultDetails = StrSave(buf);
12984             }
12985             RemoveInputSource(cps->isr);
12986             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12987         } else {
12988             sprintf(buf,
12989                     _("Error reading from %s chess program (%s)"),
12990                     cps->which, cps->program);
12991             RemoveInputSource(cps->isr);
12992
12993             /* [AS] Program is misbehaving badly... kill it */
12994             if( count == -2 ) {
12995                 DestroyChildProcess( cps->pr, 9 );
12996                 cps->pr = NoProc;
12997             }
12998
12999             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13000         }
13001         return;
13002     }
13003     
13004     if ((end_str = strchr(message, '\r')) != NULL)
13005       *end_str = NULLCHAR;
13006     if ((end_str = strchr(message, '\n')) != NULL)
13007       *end_str = NULLCHAR;
13008     
13009     if (appData.debugMode) {
13010         TimeMark now; int print = 1;
13011         char *quote = ""; char c; int i;
13012
13013         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13014                 char start = message[0];
13015                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13016                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13017                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13018                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13019                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13020                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13021                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13022                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13023                         { quote = "# "; print = (appData.engineComments == 2); }
13024                 message[0] = start; // restore original message
13025         }
13026         if(print) {
13027                 GetTimeMark(&now);
13028                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13029                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13030                         quote,
13031                         message);
13032         }
13033     }
13034
13035     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13036     if (appData.icsEngineAnalyze) {
13037         if (strstr(message, "whisper") != NULL ||
13038              strstr(message, "kibitz") != NULL || 
13039             strstr(message, "tellics") != NULL) return;
13040     }
13041
13042     HandleMachineMove(message, cps);
13043 }
13044
13045
13046 void
13047 SendTimeControl(cps, mps, tc, inc, sd, st)
13048      ChessProgramState *cps;
13049      int mps, inc, sd, st;
13050      long tc;
13051 {
13052     char buf[MSG_SIZ];
13053     int seconds;
13054
13055     if( timeControl_2 > 0 ) {
13056         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13057             tc = timeControl_2;
13058         }
13059     }
13060     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13061     inc /= cps->timeOdds;
13062     st  /= cps->timeOdds;
13063
13064     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13065
13066     if (st > 0) {
13067       /* Set exact time per move, normally using st command */
13068       if (cps->stKludge) {
13069         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13070         seconds = st % 60;
13071         if (seconds == 0) {
13072           sprintf(buf, "level 1 %d\n", st/60);
13073         } else {
13074           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13075         }
13076       } else {
13077         sprintf(buf, "st %d\n", st);
13078       }
13079     } else {
13080       /* Set conventional or incremental time control, using level command */
13081       if (seconds == 0) {
13082         /* Note old gnuchess bug -- minutes:seconds used to not work.
13083            Fixed in later versions, but still avoid :seconds
13084            when seconds is 0. */
13085         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13086       } else {
13087         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13088                 seconds, inc/1000);
13089       }
13090     }
13091     SendToProgram(buf, cps);
13092
13093     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13094     /* Orthogonally, limit search to given depth */
13095     if (sd > 0) {
13096       if (cps->sdKludge) {
13097         sprintf(buf, "depth\n%d\n", sd);
13098       } else {
13099         sprintf(buf, "sd %d\n", sd);
13100       }
13101       SendToProgram(buf, cps);
13102     }
13103
13104     if(cps->nps > 0) { /* [HGM] nps */
13105         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13106         else {
13107                 sprintf(buf, "nps %d\n", cps->nps);
13108               SendToProgram(buf, cps);
13109         }
13110     }
13111 }
13112
13113 ChessProgramState *WhitePlayer()
13114 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13115 {
13116     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13117        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13118         return &second;
13119     return &first;
13120 }
13121
13122 void
13123 SendTimeRemaining(cps, machineWhite)
13124      ChessProgramState *cps;
13125      int /*boolean*/ machineWhite;
13126 {
13127     char message[MSG_SIZ];
13128     long time, otime;
13129
13130     /* Note: this routine must be called when the clocks are stopped
13131        or when they have *just* been set or switched; otherwise
13132        it will be off by the time since the current tick started.
13133     */
13134     if (machineWhite) {
13135         time = whiteTimeRemaining / 10;
13136         otime = blackTimeRemaining / 10;
13137     } else {
13138         time = blackTimeRemaining / 10;
13139         otime = whiteTimeRemaining / 10;
13140     }
13141     /* [HGM] translate opponent's time by time-odds factor */
13142     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13143     if (appData.debugMode) {
13144         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13145     }
13146
13147     if (time <= 0) time = 1;
13148     if (otime <= 0) otime = 1;
13149     
13150     sprintf(message, "time %ld\n", time);
13151     SendToProgram(message, cps);
13152
13153     sprintf(message, "otim %ld\n", otime);
13154     SendToProgram(message, cps);
13155 }
13156
13157 int
13158 BoolFeature(p, name, loc, cps)
13159      char **p;
13160      char *name;
13161      int *loc;
13162      ChessProgramState *cps;
13163 {
13164   char buf[MSG_SIZ];
13165   int len = strlen(name);
13166   int val;
13167   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13168     (*p) += len + 1;
13169     sscanf(*p, "%d", &val);
13170     *loc = (val != 0);
13171     while (**p && **p != ' ') (*p)++;
13172     sprintf(buf, "accepted %s\n", name);
13173     SendToProgram(buf, cps);
13174     return TRUE;
13175   }
13176   return FALSE;
13177 }
13178
13179 int
13180 IntFeature(p, name, loc, cps)
13181      char **p;
13182      char *name;
13183      int *loc;
13184      ChessProgramState *cps;
13185 {
13186   char buf[MSG_SIZ];
13187   int len = strlen(name);
13188   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13189     (*p) += len + 1;
13190     sscanf(*p, "%d", loc);
13191     while (**p && **p != ' ') (*p)++;
13192     sprintf(buf, "accepted %s\n", name);
13193     SendToProgram(buf, cps);
13194     return TRUE;
13195   }
13196   return FALSE;
13197 }
13198
13199 int
13200 StringFeature(p, name, loc, cps)
13201      char **p;
13202      char *name;
13203      char loc[];
13204      ChessProgramState *cps;
13205 {
13206   char buf[MSG_SIZ];
13207   int len = strlen(name);
13208   if (strncmp((*p), name, len) == 0
13209       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13210     (*p) += len + 2;
13211     sscanf(*p, "%[^\"]", loc);
13212     while (**p && **p != '\"') (*p)++;
13213     if (**p == '\"') (*p)++;
13214     sprintf(buf, "accepted %s\n", name);
13215     SendToProgram(buf, cps);
13216     return TRUE;
13217   }
13218   return FALSE;
13219 }
13220
13221 int 
13222 ParseOption(Option *opt, ChessProgramState *cps)
13223 // [HGM] options: process the string that defines an engine option, and determine
13224 // name, type, default value, and allowed value range
13225 {
13226         char *p, *q, buf[MSG_SIZ];
13227         int n, min = (-1)<<31, max = 1<<31, def;
13228
13229         if(p = strstr(opt->name, " -spin ")) {
13230             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13231             if(max < min) max = min; // enforce consistency
13232             if(def < min) def = min;
13233             if(def > max) def = max;
13234             opt->value = def;
13235             opt->min = min;
13236             opt->max = max;
13237             opt->type = Spin;
13238         } else if((p = strstr(opt->name, " -slider "))) {
13239             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13240             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13241             if(max < min) max = min; // enforce consistency
13242             if(def < min) def = min;
13243             if(def > max) def = max;
13244             opt->value = def;
13245             opt->min = min;
13246             opt->max = max;
13247             opt->type = Spin; // Slider;
13248         } else if((p = strstr(opt->name, " -string "))) {
13249             opt->textValue = p+9;
13250             opt->type = TextBox;
13251         } else if((p = strstr(opt->name, " -file "))) {
13252             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13253             opt->textValue = p+7;
13254             opt->type = TextBox; // FileName;
13255         } else if((p = strstr(opt->name, " -path "))) {
13256             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13257             opt->textValue = p+7;
13258             opt->type = TextBox; // PathName;
13259         } else if(p = strstr(opt->name, " -check ")) {
13260             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13261             opt->value = (def != 0);
13262             opt->type = CheckBox;
13263         } else if(p = strstr(opt->name, " -combo ")) {
13264             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13265             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13266             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13267             opt->value = n = 0;
13268             while(q = StrStr(q, " /// ")) {
13269                 n++; *q = 0;    // count choices, and null-terminate each of them
13270                 q += 5;
13271                 if(*q == '*') { // remember default, which is marked with * prefix
13272                     q++;
13273                     opt->value = n;
13274                 }
13275                 cps->comboList[cps->comboCnt++] = q;
13276             }
13277             cps->comboList[cps->comboCnt++] = NULL;
13278             opt->max = n + 1;
13279             opt->type = ComboBox;
13280         } else if(p = strstr(opt->name, " -button")) {
13281             opt->type = Button;
13282         } else if(p = strstr(opt->name, " -save")) {
13283             opt->type = SaveButton;
13284         } else return FALSE;
13285         *p = 0; // terminate option name
13286         // now look if the command-line options define a setting for this engine option.
13287         if(cps->optionSettings && cps->optionSettings[0])
13288             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13289         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13290                 sprintf(buf, "option %s", p);
13291                 if(p = strstr(buf, ",")) *p = 0;
13292                 strcat(buf, "\n");
13293                 SendToProgram(buf, cps);
13294         }
13295         return TRUE;
13296 }
13297
13298 void
13299 FeatureDone(cps, val)
13300      ChessProgramState* cps;
13301      int val;
13302 {
13303   DelayedEventCallback cb = GetDelayedEvent();
13304   if ((cb == InitBackEnd3 && cps == &first) ||
13305       (cb == TwoMachinesEventIfReady && cps == &second)) {
13306     CancelDelayedEvent();
13307     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13308   }
13309   cps->initDone = val;
13310 }
13311
13312 /* Parse feature command from engine */
13313 void
13314 ParseFeatures(args, cps)
13315      char* args;
13316      ChessProgramState *cps;  
13317 {
13318   char *p = args;
13319   char *q;
13320   int val;
13321   char buf[MSG_SIZ];
13322
13323   for (;;) {
13324     while (*p == ' ') p++;
13325     if (*p == NULLCHAR) return;
13326
13327     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13328     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13329     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13330     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13331     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13332     if (BoolFeature(&p, "reuse", &val, cps)) {
13333       /* Engine can disable reuse, but can't enable it if user said no */
13334       if (!val) cps->reuse = FALSE;
13335       continue;
13336     }
13337     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13338     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13339       if (gameMode == TwoMachinesPlay) {
13340         DisplayTwoMachinesTitle();
13341       } else {
13342         DisplayTitle("");
13343       }
13344       continue;
13345     }
13346     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13347     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13348     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13349     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13350     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13351     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13352     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13353     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13354     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13355     if (IntFeature(&p, "done", &val, cps)) {
13356       FeatureDone(cps, val);
13357       continue;
13358     }
13359     /* Added by Tord: */
13360     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13361     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13362     /* End of additions by Tord */
13363
13364     /* [HGM] added features: */
13365     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13366     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13367     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13368     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13369     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13370     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13371     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13372         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13373             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13374             SendToProgram(buf, cps);
13375             continue;
13376         }
13377         if(cps->nrOptions >= MAX_OPTIONS) {
13378             cps->nrOptions--;
13379             sprintf(buf, "%s engine has too many options\n", cps->which);
13380             DisplayError(buf, 0);
13381         }
13382         continue;
13383     }
13384     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13385     /* End of additions by HGM */
13386
13387     /* unknown feature: complain and skip */
13388     q = p;
13389     while (*q && *q != '=') q++;
13390     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13391     SendToProgram(buf, cps);
13392     p = q;
13393     if (*p == '=') {
13394       p++;
13395       if (*p == '\"') {
13396         p++;
13397         while (*p && *p != '\"') p++;
13398         if (*p == '\"') p++;
13399       } else {
13400         while (*p && *p != ' ') p++;
13401       }
13402     }
13403   }
13404
13405 }
13406
13407 void
13408 PeriodicUpdatesEvent(newState)
13409      int newState;
13410 {
13411     if (newState == appData.periodicUpdates)
13412       return;
13413
13414     appData.periodicUpdates=newState;
13415
13416     /* Display type changes, so update it now */
13417 //    DisplayAnalysis();
13418
13419     /* Get the ball rolling again... */
13420     if (newState) {
13421         AnalysisPeriodicEvent(1);
13422         StartAnalysisClock();
13423     }
13424 }
13425
13426 void
13427 PonderNextMoveEvent(newState)
13428      int newState;
13429 {
13430     if (newState == appData.ponderNextMove) return;
13431     if (gameMode == EditPosition) EditPositionDone(TRUE);
13432     if (newState) {
13433         SendToProgram("hard\n", &first);
13434         if (gameMode == TwoMachinesPlay) {
13435             SendToProgram("hard\n", &second);
13436         }
13437     } else {
13438         SendToProgram("easy\n", &first);
13439         thinkOutput[0] = NULLCHAR;
13440         if (gameMode == TwoMachinesPlay) {
13441             SendToProgram("easy\n", &second);
13442         }
13443     }
13444     appData.ponderNextMove = newState;
13445 }
13446
13447 void
13448 NewSettingEvent(option, command, value)
13449      char *command;
13450      int option, value;
13451 {
13452     char buf[MSG_SIZ];
13453
13454     if (gameMode == EditPosition) EditPositionDone(TRUE);
13455     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13456     SendToProgram(buf, &first);
13457     if (gameMode == TwoMachinesPlay) {
13458         SendToProgram(buf, &second);
13459     }
13460 }
13461
13462 void
13463 ShowThinkingEvent()
13464 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13465 {
13466     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13467     int newState = appData.showThinking
13468         // [HGM] thinking: other features now need thinking output as well
13469         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13470     
13471     if (oldState == newState) return;
13472     oldState = newState;
13473     if (gameMode == EditPosition) EditPositionDone(TRUE);
13474     if (oldState) {
13475         SendToProgram("post\n", &first);
13476         if (gameMode == TwoMachinesPlay) {
13477             SendToProgram("post\n", &second);
13478         }
13479     } else {
13480         SendToProgram("nopost\n", &first);
13481         thinkOutput[0] = NULLCHAR;
13482         if (gameMode == TwoMachinesPlay) {
13483             SendToProgram("nopost\n", &second);
13484         }
13485     }
13486 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13487 }
13488
13489 void
13490 AskQuestionEvent(title, question, replyPrefix, which)
13491      char *title; char *question; char *replyPrefix; char *which;
13492 {
13493   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13494   if (pr == NoProc) return;
13495   AskQuestion(title, question, replyPrefix, pr);
13496 }
13497
13498 void
13499 DisplayMove(moveNumber)
13500      int moveNumber;
13501 {
13502     char message[MSG_SIZ];
13503     char res[MSG_SIZ];
13504     char cpThinkOutput[MSG_SIZ];
13505
13506     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13507     
13508     if (moveNumber == forwardMostMove - 1 || 
13509         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13510
13511         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13512
13513         if (strchr(cpThinkOutput, '\n')) {
13514             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13515         }
13516     } else {
13517         *cpThinkOutput = NULLCHAR;
13518     }
13519
13520     /* [AS] Hide thinking from human user */
13521     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13522         *cpThinkOutput = NULLCHAR;
13523         if( thinkOutput[0] != NULLCHAR ) {
13524             int i;
13525
13526             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13527                 cpThinkOutput[i] = '.';
13528             }
13529             cpThinkOutput[i] = NULLCHAR;
13530             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13531         }
13532     }
13533
13534     if (moveNumber == forwardMostMove - 1 &&
13535         gameInfo.resultDetails != NULL) {
13536         if (gameInfo.resultDetails[0] == NULLCHAR) {
13537             sprintf(res, " %s", PGNResult(gameInfo.result));
13538         } else {
13539             sprintf(res, " {%s} %s",
13540                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13541         }
13542     } else {
13543         res[0] = NULLCHAR;
13544     }
13545
13546     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13547         DisplayMessage(res, cpThinkOutput);
13548     } else {
13549         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13550                 WhiteOnMove(moveNumber) ? " " : ".. ",
13551                 parseList[moveNumber], res);
13552         DisplayMessage(message, cpThinkOutput);
13553     }
13554 }
13555
13556 void
13557 DisplayComment(moveNumber, text)
13558      int moveNumber;
13559      char *text;
13560 {
13561     char title[MSG_SIZ];
13562     char buf[8000]; // comment can be long!
13563     int score, depth;
13564     
13565     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13566       strcpy(title, "Comment");
13567     } else {
13568       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13569               WhiteOnMove(moveNumber) ? " " : ".. ",
13570               parseList[moveNumber]);
13571     }
13572     // [HGM] PV info: display PV info together with (or as) comment
13573     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13574       if(text == NULL) text = "";                                           
13575       score = pvInfoList[moveNumber].score;
13576       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13577               depth, (pvInfoList[moveNumber].time+50)/100, text);
13578       text = buf;
13579     }
13580     if (text != NULL && (appData.autoDisplayComment || commentUp))
13581         CommentPopUp(title, text);
13582 }
13583
13584 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13585  * might be busy thinking or pondering.  It can be omitted if your
13586  * gnuchess is configured to stop thinking immediately on any user
13587  * input.  However, that gnuchess feature depends on the FIONREAD
13588  * ioctl, which does not work properly on some flavors of Unix.
13589  */
13590 void
13591 Attention(cps)
13592      ChessProgramState *cps;
13593 {
13594 #if ATTENTION
13595     if (!cps->useSigint) return;
13596     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13597     switch (gameMode) {
13598       case MachinePlaysWhite:
13599       case MachinePlaysBlack:
13600       case TwoMachinesPlay:
13601       case IcsPlayingWhite:
13602       case IcsPlayingBlack:
13603       case AnalyzeMode:
13604       case AnalyzeFile:
13605         /* Skip if we know it isn't thinking */
13606         if (!cps->maybeThinking) return;
13607         if (appData.debugMode)
13608           fprintf(debugFP, "Interrupting %s\n", cps->which);
13609         InterruptChildProcess(cps->pr);
13610         cps->maybeThinking = FALSE;
13611         break;
13612       default:
13613         break;
13614     }
13615 #endif /*ATTENTION*/
13616 }
13617
13618 int
13619 CheckFlags()
13620 {
13621     if (whiteTimeRemaining <= 0) {
13622         if (!whiteFlag) {
13623             whiteFlag = TRUE;
13624             if (appData.icsActive) {
13625                 if (appData.autoCallFlag &&
13626                     gameMode == IcsPlayingBlack && !blackFlag) {
13627                   SendToICS(ics_prefix);
13628                   SendToICS("flag\n");
13629                 }
13630             } else {
13631                 if (blackFlag) {
13632                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13633                 } else {
13634                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13635                     if (appData.autoCallFlag) {
13636                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13637                         return TRUE;
13638                     }
13639                 }
13640             }
13641         }
13642     }
13643     if (blackTimeRemaining <= 0) {
13644         if (!blackFlag) {
13645             blackFlag = TRUE;
13646             if (appData.icsActive) {
13647                 if (appData.autoCallFlag &&
13648                     gameMode == IcsPlayingWhite && !whiteFlag) {
13649                   SendToICS(ics_prefix);
13650                   SendToICS("flag\n");
13651                 }
13652             } else {
13653                 if (whiteFlag) {
13654                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13655                 } else {
13656                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13657                     if (appData.autoCallFlag) {
13658                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13659                         return TRUE;
13660                     }
13661                 }
13662             }
13663         }
13664     }
13665     return FALSE;
13666 }
13667
13668 void
13669 CheckTimeControl()
13670 {
13671     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13672         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13673
13674     /*
13675      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13676      */
13677     if ( !WhiteOnMove(forwardMostMove) )
13678         /* White made time control */
13679         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13680         /* [HGM] time odds: correct new time quota for time odds! */
13681                                             / WhitePlayer()->timeOdds;
13682       else
13683         /* Black made time control */
13684         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13685                                             / WhitePlayer()->other->timeOdds;
13686 }
13687
13688 void
13689 DisplayBothClocks()
13690 {
13691     int wom = gameMode == EditPosition ?
13692       !blackPlaysFirst : WhiteOnMove(currentMove);
13693     DisplayWhiteClock(whiteTimeRemaining, wom);
13694     DisplayBlackClock(blackTimeRemaining, !wom);
13695 }
13696
13697
13698 /* Timekeeping seems to be a portability nightmare.  I think everyone
13699    has ftime(), but I'm really not sure, so I'm including some ifdefs
13700    to use other calls if you don't.  Clocks will be less accurate if
13701    you have neither ftime nor gettimeofday.
13702 */
13703
13704 /* VS 2008 requires the #include outside of the function */
13705 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13706 #include <sys/timeb.h>
13707 #endif
13708
13709 /* Get the current time as a TimeMark */
13710 void
13711 GetTimeMark(tm)
13712      TimeMark *tm;
13713 {
13714 #if HAVE_GETTIMEOFDAY
13715
13716     struct timeval timeVal;
13717     struct timezone timeZone;
13718
13719     gettimeofday(&timeVal, &timeZone);
13720     tm->sec = (long) timeVal.tv_sec; 
13721     tm->ms = (int) (timeVal.tv_usec / 1000L);
13722
13723 #else /*!HAVE_GETTIMEOFDAY*/
13724 #if HAVE_FTIME
13725
13726 // include <sys/timeb.h> / moved to just above start of function
13727     struct timeb timeB;
13728
13729     ftime(&timeB);
13730     tm->sec = (long) timeB.time;
13731     tm->ms = (int) timeB.millitm;
13732
13733 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13734     tm->sec = (long) time(NULL);
13735     tm->ms = 0;
13736 #endif
13737 #endif
13738 }
13739
13740 /* Return the difference in milliseconds between two
13741    time marks.  We assume the difference will fit in a long!
13742 */
13743 long
13744 SubtractTimeMarks(tm2, tm1)
13745      TimeMark *tm2, *tm1;
13746 {
13747     return 1000L*(tm2->sec - tm1->sec) +
13748            (long) (tm2->ms - tm1->ms);
13749 }
13750
13751
13752 /*
13753  * Code to manage the game clocks.
13754  *
13755  * In tournament play, black starts the clock and then white makes a move.
13756  * We give the human user a slight advantage if he is playing white---the
13757  * clocks don't run until he makes his first move, so it takes zero time.
13758  * Also, we don't account for network lag, so we could get out of sync
13759  * with GNU Chess's clock -- but then, referees are always right.  
13760  */
13761
13762 static TimeMark tickStartTM;
13763 static long intendedTickLength;
13764
13765 long
13766 NextTickLength(timeRemaining)
13767      long timeRemaining;
13768 {
13769     long nominalTickLength, nextTickLength;
13770
13771     if (timeRemaining > 0L && timeRemaining <= 10000L)
13772       nominalTickLength = 100L;
13773     else
13774       nominalTickLength = 1000L;
13775     nextTickLength = timeRemaining % nominalTickLength;
13776     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13777
13778     return nextTickLength;
13779 }
13780
13781 /* Adjust clock one minute up or down */
13782 void
13783 AdjustClock(Boolean which, int dir)
13784 {
13785     if(which) blackTimeRemaining += 60000*dir;
13786     else      whiteTimeRemaining += 60000*dir;
13787     DisplayBothClocks();
13788 }
13789
13790 /* Stop clocks and reset to a fresh time control */
13791 void
13792 ResetClocks() 
13793 {
13794     (void) StopClockTimer();
13795     if (appData.icsActive) {
13796         whiteTimeRemaining = blackTimeRemaining = 0;
13797     } else if (searchTime) {
13798         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13799         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13800     } else { /* [HGM] correct new time quote for time odds */
13801         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13802         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13803     }
13804     if (whiteFlag || blackFlag) {
13805         DisplayTitle("");
13806         whiteFlag = blackFlag = FALSE;
13807     }
13808     DisplayBothClocks();
13809 }
13810
13811 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13812
13813 /* Decrement running clock by amount of time that has passed */
13814 void
13815 DecrementClocks()
13816 {
13817     long timeRemaining;
13818     long lastTickLength, fudge;
13819     TimeMark now;
13820
13821     if (!appData.clockMode) return;
13822     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13823         
13824     GetTimeMark(&now);
13825
13826     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13827
13828     /* Fudge if we woke up a little too soon */
13829     fudge = intendedTickLength - lastTickLength;
13830     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13831
13832     if (WhiteOnMove(forwardMostMove)) {
13833         if(whiteNPS >= 0) lastTickLength = 0;
13834         timeRemaining = whiteTimeRemaining -= lastTickLength;
13835         DisplayWhiteClock(whiteTimeRemaining - fudge,
13836                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13837     } else {
13838         if(blackNPS >= 0) lastTickLength = 0;
13839         timeRemaining = blackTimeRemaining -= lastTickLength;
13840         DisplayBlackClock(blackTimeRemaining - fudge,
13841                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13842     }
13843
13844     if (CheckFlags()) return;
13845         
13846     tickStartTM = now;
13847     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13848     StartClockTimer(intendedTickLength);
13849
13850     /* if the time remaining has fallen below the alarm threshold, sound the
13851      * alarm. if the alarm has sounded and (due to a takeback or time control
13852      * with increment) the time remaining has increased to a level above the
13853      * threshold, reset the alarm so it can sound again. 
13854      */
13855     
13856     if (appData.icsActive && appData.icsAlarm) {
13857
13858         /* make sure we are dealing with the user's clock */
13859         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13860                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13861            )) return;
13862
13863         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13864             alarmSounded = FALSE;
13865         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13866             PlayAlarmSound();
13867             alarmSounded = TRUE;
13868         }
13869     }
13870 }
13871
13872
13873 /* A player has just moved, so stop the previously running
13874    clock and (if in clock mode) start the other one.
13875    We redisplay both clocks in case we're in ICS mode, because
13876    ICS gives us an update to both clocks after every move.
13877    Note that this routine is called *after* forwardMostMove
13878    is updated, so the last fractional tick must be subtracted
13879    from the color that is *not* on move now.
13880 */
13881 void
13882 SwitchClocks()
13883 {
13884     long lastTickLength;
13885     TimeMark now;
13886     int flagged = FALSE;
13887
13888     GetTimeMark(&now);
13889
13890     if (StopClockTimer() && appData.clockMode) {
13891         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13892         if (WhiteOnMove(forwardMostMove)) {
13893             if(blackNPS >= 0) lastTickLength = 0;
13894             blackTimeRemaining -= lastTickLength;
13895            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13896 //         if(pvInfoList[forwardMostMove-1].time == -1)
13897                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13898                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13899         } else {
13900            if(whiteNPS >= 0) lastTickLength = 0;
13901            whiteTimeRemaining -= lastTickLength;
13902            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13903 //         if(pvInfoList[forwardMostMove-1].time == -1)
13904                  pvInfoList[forwardMostMove-1].time = 
13905                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13906         }
13907         flagged = CheckFlags();
13908     }
13909     CheckTimeControl();
13910
13911     if (flagged || !appData.clockMode) return;
13912
13913     switch (gameMode) {
13914       case MachinePlaysBlack:
13915       case MachinePlaysWhite:
13916       case BeginningOfGame:
13917         if (pausing) return;
13918         break;
13919
13920       case EditGame:
13921       case PlayFromGameFile:
13922       case IcsExamining:
13923         return;
13924
13925       default:
13926         break;
13927     }
13928
13929     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13930         if(WhiteOnMove(forwardMostMove))
13931              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13932         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13933     }
13934
13935     tickStartTM = now;
13936     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13937       whiteTimeRemaining : blackTimeRemaining);
13938     StartClockTimer(intendedTickLength);
13939 }
13940         
13941
13942 /* Stop both clocks */
13943 void
13944 StopClocks()
13945 {       
13946     long lastTickLength;
13947     TimeMark now;
13948
13949     if (!StopClockTimer()) return;
13950     if (!appData.clockMode) return;
13951
13952     GetTimeMark(&now);
13953
13954     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13955     if (WhiteOnMove(forwardMostMove)) {
13956         if(whiteNPS >= 0) lastTickLength = 0;
13957         whiteTimeRemaining -= lastTickLength;
13958         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13959     } else {
13960         if(blackNPS >= 0) lastTickLength = 0;
13961         blackTimeRemaining -= lastTickLength;
13962         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13963     }
13964     CheckFlags();
13965 }
13966         
13967 /* Start clock of player on move.  Time may have been reset, so
13968    if clock is already running, stop and restart it. */
13969 void
13970 StartClocks()
13971 {
13972     (void) StopClockTimer(); /* in case it was running already */
13973     DisplayBothClocks();
13974     if (CheckFlags()) return;
13975
13976     if (!appData.clockMode) return;
13977     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13978
13979     GetTimeMark(&tickStartTM);
13980     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13981       whiteTimeRemaining : blackTimeRemaining);
13982
13983    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13984     whiteNPS = blackNPS = -1; 
13985     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13986        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13987         whiteNPS = first.nps;
13988     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13989        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13990         blackNPS = first.nps;
13991     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13992         whiteNPS = second.nps;
13993     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13994         blackNPS = second.nps;
13995     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13996
13997     StartClockTimer(intendedTickLength);
13998 }
13999
14000 char *
14001 TimeString(ms)
14002      long ms;
14003 {
14004     long second, minute, hour, day;
14005     char *sign = "";
14006     static char buf[32];
14007     
14008     if (ms > 0 && ms <= 9900) {
14009       /* convert milliseconds to tenths, rounding up */
14010       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14011
14012       sprintf(buf, " %03.1f ", tenths/10.0);
14013       return buf;
14014     }
14015
14016     /* convert milliseconds to seconds, rounding up */
14017     /* use floating point to avoid strangeness of integer division
14018        with negative dividends on many machines */
14019     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14020
14021     if (second < 0) {
14022         sign = "-";
14023         second = -second;
14024     }
14025     
14026     day = second / (60 * 60 * 24);
14027     second = second % (60 * 60 * 24);
14028     hour = second / (60 * 60);
14029     second = second % (60 * 60);
14030     minute = second / 60;
14031     second = second % 60;
14032     
14033     if (day > 0)
14034       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14035               sign, day, hour, minute, second);
14036     else if (hour > 0)
14037       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14038     else
14039       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14040     
14041     return buf;
14042 }
14043
14044
14045 /*
14046  * This is necessary because some C libraries aren't ANSI C compliant yet.
14047  */
14048 char *
14049 StrStr(string, match)
14050      char *string, *match;
14051 {
14052     int i, length;
14053     
14054     length = strlen(match);
14055     
14056     for (i = strlen(string) - length; i >= 0; i--, string++)
14057       if (!strncmp(match, string, length))
14058         return string;
14059     
14060     return NULL;
14061 }
14062
14063 char *
14064 StrCaseStr(string, match)
14065      char *string, *match;
14066 {
14067     int i, j, length;
14068     
14069     length = strlen(match);
14070     
14071     for (i = strlen(string) - length; i >= 0; i--, string++) {
14072         for (j = 0; j < length; j++) {
14073             if (ToLower(match[j]) != ToLower(string[j]))
14074               break;
14075         }
14076         if (j == length) return string;
14077     }
14078
14079     return NULL;
14080 }
14081
14082 #ifndef _amigados
14083 int
14084 StrCaseCmp(s1, s2)
14085      char *s1, *s2;
14086 {
14087     char c1, c2;
14088     
14089     for (;;) {
14090         c1 = ToLower(*s1++);
14091         c2 = ToLower(*s2++);
14092         if (c1 > c2) return 1;
14093         if (c1 < c2) return -1;
14094         if (c1 == NULLCHAR) return 0;
14095     }
14096 }
14097
14098
14099 int
14100 ToLower(c)
14101      int c;
14102 {
14103     return isupper(c) ? tolower(c) : c;
14104 }
14105
14106
14107 int
14108 ToUpper(c)
14109      int c;
14110 {
14111     return islower(c) ? toupper(c) : c;
14112 }
14113 #endif /* !_amigados    */
14114
14115 char *
14116 StrSave(s)
14117      char *s;
14118 {
14119     char *ret;
14120
14121     if ((ret = (char *) malloc(strlen(s) + 1))) {
14122         strcpy(ret, s);
14123     }
14124     return ret;
14125 }
14126
14127 char *
14128 StrSavePtr(s, savePtr)
14129      char *s, **savePtr;
14130 {
14131     if (*savePtr) {
14132         free(*savePtr);
14133     }
14134     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14135         strcpy(*savePtr, s);
14136     }
14137     return(*savePtr);
14138 }
14139
14140 char *
14141 PGNDate()
14142 {
14143     time_t clock;
14144     struct tm *tm;
14145     char buf[MSG_SIZ];
14146
14147     clock = time((time_t *)NULL);
14148     tm = localtime(&clock);
14149     sprintf(buf, "%04d.%02d.%02d",
14150             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14151     return StrSave(buf);
14152 }
14153
14154
14155 char *
14156 PositionToFEN(move, overrideCastling)
14157      int move;
14158      char *overrideCastling;
14159 {
14160     int i, j, fromX, fromY, toX, toY;
14161     int whiteToPlay;
14162     char buf[128];
14163     char *p, *q;
14164     int emptycount;
14165     ChessSquare piece;
14166
14167     whiteToPlay = (gameMode == EditPosition) ?
14168       !blackPlaysFirst : (move % 2 == 0);
14169     p = buf;
14170
14171     /* Piece placement data */
14172     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14173         emptycount = 0;
14174         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14175             if (boards[move][i][j] == EmptySquare) {
14176                 emptycount++;
14177             } else { ChessSquare piece = boards[move][i][j];
14178                 if (emptycount > 0) {
14179                     if(emptycount<10) /* [HGM] can be >= 10 */
14180                         *p++ = '0' + emptycount;
14181                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14182                     emptycount = 0;
14183                 }
14184                 if(PieceToChar(piece) == '+') {
14185                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14186                     *p++ = '+';
14187                     piece = (ChessSquare)(DEMOTED piece);
14188                 } 
14189                 *p++ = PieceToChar(piece);
14190                 if(p[-1] == '~') {
14191                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14192                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14193                     *p++ = '~';
14194                 }
14195             }
14196         }
14197         if (emptycount > 0) {
14198             if(emptycount<10) /* [HGM] can be >= 10 */
14199                 *p++ = '0' + emptycount;
14200             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14201             emptycount = 0;
14202         }
14203         *p++ = '/';
14204     }
14205     *(p - 1) = ' ';
14206
14207     /* [HGM] print Crazyhouse or Shogi holdings */
14208     if( gameInfo.holdingsWidth ) {
14209         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14210         q = p;
14211         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14212             piece = boards[move][i][BOARD_WIDTH-1];
14213             if( piece != EmptySquare )
14214               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14215                   *p++ = PieceToChar(piece);
14216         }
14217         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14218             piece = boards[move][BOARD_HEIGHT-i-1][0];
14219             if( piece != EmptySquare )
14220               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14221                   *p++ = PieceToChar(piece);
14222         }
14223
14224         if( q == p ) *p++ = '-';
14225         *p++ = ']';
14226         *p++ = ' ';
14227     }
14228
14229     /* Active color */
14230     *p++ = whiteToPlay ? 'w' : 'b';
14231     *p++ = ' ';
14232
14233   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14234     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14235   } else {
14236   if(nrCastlingRights) {
14237      q = p;
14238      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14239        /* [HGM] write directly from rights */
14240            if(boards[move][CASTLING][2] != NoRights &&
14241               boards[move][CASTLING][0] != NoRights   )
14242                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14243            if(boards[move][CASTLING][2] != NoRights &&
14244               boards[move][CASTLING][1] != NoRights   )
14245                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14246            if(boards[move][CASTLING][5] != NoRights &&
14247               boards[move][CASTLING][3] != NoRights   )
14248                 *p++ = boards[move][CASTLING][3] + AAA;
14249            if(boards[move][CASTLING][5] != NoRights &&
14250               boards[move][CASTLING][4] != NoRights   )
14251                 *p++ = boards[move][CASTLING][4] + AAA;
14252      } else {
14253
14254         /* [HGM] write true castling rights */
14255         if( nrCastlingRights == 6 ) {
14256             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14257                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14258             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14259                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14260             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14261                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14262             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14263                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14264         }
14265      }
14266      if (q == p) *p++ = '-'; /* No castling rights */
14267      *p++ = ' ';
14268   }
14269
14270   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14271      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14272     /* En passant target square */
14273     if (move > backwardMostMove) {
14274         fromX = moveList[move - 1][0] - AAA;
14275         fromY = moveList[move - 1][1] - ONE;
14276         toX = moveList[move - 1][2] - AAA;
14277         toY = moveList[move - 1][3] - ONE;
14278         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14279             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14280             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14281             fromX == toX) {
14282             /* 2-square pawn move just happened */
14283             *p++ = toX + AAA;
14284             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14285         } else {
14286             *p++ = '-';
14287         }
14288     } else if(move == backwardMostMove) {
14289         // [HGM] perhaps we should always do it like this, and forget the above?
14290         if((signed char)boards[move][EP_STATUS] >= 0) {
14291             *p++ = boards[move][EP_STATUS] + AAA;
14292             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14293         } else {
14294             *p++ = '-';
14295         }
14296     } else {
14297         *p++ = '-';
14298     }
14299     *p++ = ' ';
14300   }
14301   }
14302
14303     /* [HGM] find reversible plies */
14304     {   int i = 0, j=move;
14305
14306         if (appData.debugMode) { int k;
14307             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14308             for(k=backwardMostMove; k<=forwardMostMove; k++)
14309                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14310
14311         }
14312
14313         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14314         if( j == backwardMostMove ) i += initialRulePlies;
14315         sprintf(p, "%d ", i);
14316         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14317     }
14318     /* Fullmove number */
14319     sprintf(p, "%d", (move / 2) + 1);
14320     
14321     return StrSave(buf);
14322 }
14323
14324 Boolean
14325 ParseFEN(board, blackPlaysFirst, fen)
14326     Board board;
14327      int *blackPlaysFirst;
14328      char *fen;
14329 {
14330     int i, j;
14331     char *p;
14332     int emptycount;
14333     ChessSquare piece;
14334
14335     p = fen;
14336
14337     /* [HGM] by default clear Crazyhouse holdings, if present */
14338     if(gameInfo.holdingsWidth) {
14339        for(i=0; i<BOARD_HEIGHT; i++) {
14340            board[i][0]             = EmptySquare; /* black holdings */
14341            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14342            board[i][1]             = (ChessSquare) 0; /* black counts */
14343            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14344        }
14345     }
14346
14347     /* Piece placement data */
14348     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14349         j = 0;
14350         for (;;) {
14351             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14352                 if (*p == '/') p++;
14353                 emptycount = gameInfo.boardWidth - j;
14354                 while (emptycount--)
14355                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14356                 break;
14357 #if(BOARD_FILES >= 10)
14358             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14359                 p++; emptycount=10;
14360                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14361                 while (emptycount--)
14362                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14363 #endif
14364             } else if (isdigit(*p)) {
14365                 emptycount = *p++ - '0';
14366                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14367                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14368                 while (emptycount--)
14369                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14370             } else if (*p == '+' || isalpha(*p)) {
14371                 if (j >= gameInfo.boardWidth) return FALSE;
14372                 if(*p=='+') {
14373                     piece = CharToPiece(*++p);
14374                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14375                     piece = (ChessSquare) (PROMOTED piece ); p++;
14376                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14377                 } else piece = CharToPiece(*p++);
14378
14379                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14380                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14381                     piece = (ChessSquare) (PROMOTED piece);
14382                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14383                     p++;
14384                 }
14385                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14386             } else {
14387                 return FALSE;
14388             }
14389         }
14390     }
14391     while (*p == '/' || *p == ' ') p++;
14392
14393     /* [HGM] look for Crazyhouse holdings here */
14394     while(*p==' ') p++;
14395     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14396         if(*p == '[') p++;
14397         if(*p == '-' ) *p++; /* empty holdings */ else {
14398             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14399             /* if we would allow FEN reading to set board size, we would   */
14400             /* have to add holdings and shift the board read so far here   */
14401             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14402                 *p++;
14403                 if((int) piece >= (int) BlackPawn ) {
14404                     i = (int)piece - (int)BlackPawn;
14405                     i = PieceToNumber((ChessSquare)i);
14406                     if( i >= gameInfo.holdingsSize ) return FALSE;
14407                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14408                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14409                 } else {
14410                     i = (int)piece - (int)WhitePawn;
14411                     i = PieceToNumber((ChessSquare)i);
14412                     if( i >= gameInfo.holdingsSize ) return FALSE;
14413                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14414                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14415                 }
14416             }
14417         }
14418         if(*p == ']') *p++;
14419     }
14420
14421     while(*p == ' ') p++;
14422
14423     /* Active color */
14424     switch (*p++) {
14425       case 'w':
14426         *blackPlaysFirst = FALSE;
14427         break;
14428       case 'b': 
14429         *blackPlaysFirst = TRUE;
14430         break;
14431       default:
14432         return FALSE;
14433     }
14434
14435     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14436     /* return the extra info in global variiables             */
14437
14438     /* set defaults in case FEN is incomplete */
14439     board[EP_STATUS] = EP_UNKNOWN;
14440     for(i=0; i<nrCastlingRights; i++ ) {
14441         board[CASTLING][i] =
14442             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14443     }   /* assume possible unless obviously impossible */
14444     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14445     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14446     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14447                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14448     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14449     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14450     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14451                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14452     FENrulePlies = 0;
14453
14454     while(*p==' ') p++;
14455     if(nrCastlingRights) {
14456       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14457           /* castling indicator present, so default becomes no castlings */
14458           for(i=0; i<nrCastlingRights; i++ ) {
14459                  board[CASTLING][i] = NoRights;
14460           }
14461       }
14462       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14463              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14464              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14465              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14466         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14467
14468         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14469             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14470             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14471         }
14472         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14473             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14474         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14475                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14476         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14477                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14478         switch(c) {
14479           case'K':
14480               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14481               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14482               board[CASTLING][2] = whiteKingFile;
14483               break;
14484           case'Q':
14485               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14486               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14487               board[CASTLING][2] = whiteKingFile;
14488               break;
14489           case'k':
14490               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14491               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14492               board[CASTLING][5] = blackKingFile;
14493               break;
14494           case'q':
14495               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14496               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14497               board[CASTLING][5] = blackKingFile;
14498           case '-':
14499               break;
14500           default: /* FRC castlings */
14501               if(c >= 'a') { /* black rights */
14502                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14503                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14504                   if(i == BOARD_RGHT) break;
14505                   board[CASTLING][5] = i;
14506                   c -= AAA;
14507                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14508                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14509                   if(c > i)
14510                       board[CASTLING][3] = c;
14511                   else
14512                       board[CASTLING][4] = c;
14513               } else { /* white rights */
14514                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14515                     if(board[0][i] == WhiteKing) break;
14516                   if(i == BOARD_RGHT) break;
14517                   board[CASTLING][2] = i;
14518                   c -= AAA - 'a' + 'A';
14519                   if(board[0][c] >= WhiteKing) break;
14520                   if(c > i)
14521                       board[CASTLING][0] = c;
14522                   else
14523                       board[CASTLING][1] = c;
14524               }
14525         }
14526       }
14527       for(i=0; i<nrCastlingRights; i++)
14528         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14529     if (appData.debugMode) {
14530         fprintf(debugFP, "FEN castling rights:");
14531         for(i=0; i<nrCastlingRights; i++)
14532         fprintf(debugFP, " %d", board[CASTLING][i]);
14533         fprintf(debugFP, "\n");
14534     }
14535
14536       while(*p==' ') p++;
14537     }
14538
14539     /* read e.p. field in games that know e.p. capture */
14540     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14541        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14542       if(*p=='-') {
14543         p++; board[EP_STATUS] = EP_NONE;
14544       } else {
14545          char c = *p++ - AAA;
14546
14547          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14548          if(*p >= '0' && *p <='9') *p++;
14549          board[EP_STATUS] = c;
14550       }
14551     }
14552
14553
14554     if(sscanf(p, "%d", &i) == 1) {
14555         FENrulePlies = i; /* 50-move ply counter */
14556         /* (The move number is still ignored)    */
14557     }
14558
14559     return TRUE;
14560 }
14561       
14562 void
14563 EditPositionPasteFEN(char *fen)
14564 {
14565   if (fen != NULL) {
14566     Board initial_position;
14567
14568     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14569       DisplayError(_("Bad FEN position in clipboard"), 0);
14570       return ;
14571     } else {
14572       int savedBlackPlaysFirst = blackPlaysFirst;
14573       EditPositionEvent();
14574       blackPlaysFirst = savedBlackPlaysFirst;
14575       CopyBoard(boards[0], initial_position);
14576       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14577       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14578       DisplayBothClocks();
14579       DrawPosition(FALSE, boards[currentMove]);
14580     }
14581   }
14582 }
14583
14584 static char cseq[12] = "\\   ";
14585
14586 Boolean set_cont_sequence(char *new_seq)
14587 {
14588     int len;
14589     Boolean ret;
14590
14591     // handle bad attempts to set the sequence
14592         if (!new_seq)
14593                 return 0; // acceptable error - no debug
14594
14595     len = strlen(new_seq);
14596     ret = (len > 0) && (len < sizeof(cseq));
14597     if (ret)
14598         strcpy(cseq, new_seq);
14599     else if (appData.debugMode)
14600         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14601     return ret;
14602 }
14603
14604 /*
14605     reformat a source message so words don't cross the width boundary.  internal
14606     newlines are not removed.  returns the wrapped size (no null character unless
14607     included in source message).  If dest is NULL, only calculate the size required
14608     for the dest buffer.  lp argument indicats line position upon entry, and it's
14609     passed back upon exit.
14610 */
14611 int wrap(char *dest, char *src, int count, int width, int *lp)
14612 {
14613     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14614
14615     cseq_len = strlen(cseq);
14616     old_line = line = *lp;
14617     ansi = len = clen = 0;
14618
14619     for (i=0; i < count; i++)
14620     {
14621         if (src[i] == '\033')
14622             ansi = 1;
14623
14624         // if we hit the width, back up
14625         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14626         {
14627             // store i & len in case the word is too long
14628             old_i = i, old_len = len;
14629
14630             // find the end of the last word
14631             while (i && src[i] != ' ' && src[i] != '\n')
14632             {
14633                 i--;
14634                 len--;
14635             }
14636
14637             // word too long?  restore i & len before splitting it
14638             if ((old_i-i+clen) >= width)
14639             {
14640                 i = old_i;
14641                 len = old_len;
14642             }
14643
14644             // extra space?
14645             if (i && src[i-1] == ' ')
14646                 len--;
14647
14648             if (src[i] != ' ' && src[i] != '\n')
14649             {
14650                 i--;
14651                 if (len)
14652                     len--;
14653             }
14654
14655             // now append the newline and continuation sequence
14656             if (dest)
14657                 dest[len] = '\n';
14658             len++;
14659             if (dest)
14660                 strncpy(dest+len, cseq, cseq_len);
14661             len += cseq_len;
14662             line = cseq_len;
14663             clen = cseq_len;
14664             continue;
14665         }
14666
14667         if (dest)
14668             dest[len] = src[i];
14669         len++;
14670         if (!ansi)
14671             line++;
14672         if (src[i] == '\n')
14673             line = 0;
14674         if (src[i] == 'm')
14675             ansi = 0;
14676     }
14677     if (dest && appData.debugMode)
14678     {
14679         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14680             count, width, line, len, *lp);
14681         show_bytes(debugFP, src, count);
14682         fprintf(debugFP, "\ndest: ");
14683         show_bytes(debugFP, dest, len);
14684         fprintf(debugFP, "\n");
14685     }
14686     *lp = dest ? line : old_line;
14687
14688     return len;
14689 }
14690
14691 // [HGM] vari: routines for shelving variations
14692
14693 void 
14694 PushTail(int firstMove, int lastMove)
14695 {
14696         int i, j, nrMoves = lastMove - firstMove;
14697
14698         if(appData.icsActive) { // only in local mode
14699                 forwardMostMove = currentMove; // mimic old ICS behavior
14700                 return;
14701         }
14702         if(storedGames >= MAX_VARIATIONS-1) return;
14703
14704         // push current tail of game on stack
14705         savedResult[storedGames] = gameInfo.result;
14706         savedDetails[storedGames] = gameInfo.resultDetails;
14707         gameInfo.resultDetails = NULL;
14708         savedFirst[storedGames] = firstMove;
14709         savedLast [storedGames] = lastMove;
14710         savedFramePtr[storedGames] = framePtr;
14711         framePtr -= nrMoves; // reserve space for the boards
14712         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14713             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14714             for(j=0; j<MOVE_LEN; j++)
14715                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14716             for(j=0; j<2*MOVE_LEN; j++)
14717                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14718             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14719             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14720             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14721             pvInfoList[firstMove+i-1].depth = 0;
14722             commentList[framePtr+i] = commentList[firstMove+i];
14723             commentList[firstMove+i] = NULL;
14724         }
14725
14726         storedGames++;
14727         forwardMostMove = currentMove; // truncte game so we can start variation
14728         if(storedGames == 1) GreyRevert(FALSE);
14729 }
14730
14731 Boolean
14732 PopTail(Boolean annotate)
14733 {
14734         int i, j, nrMoves;
14735         char buf[8000], moveBuf[20];
14736
14737         if(appData.icsActive) return FALSE; // only in local mode
14738         if(!storedGames) return FALSE; // sanity
14739
14740         storedGames--;
14741         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14742         nrMoves = savedLast[storedGames] - currentMove;
14743         if(annotate) {
14744                 int cnt = 10;
14745                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14746                 else strcpy(buf, "(");
14747                 for(i=currentMove; i<forwardMostMove; i++) {
14748                         if(WhiteOnMove(i))
14749                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14750                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14751                         strcat(buf, moveBuf);
14752                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14753                 }
14754                 strcat(buf, ")");
14755         }
14756         for(i=1; i<nrMoves; i++) { // copy last variation back
14757             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14758             for(j=0; j<MOVE_LEN; j++)
14759                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14760             for(j=0; j<2*MOVE_LEN; j++)
14761                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14762             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14763             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14764             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14765             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14766             commentList[currentMove+i] = commentList[framePtr+i];
14767             commentList[framePtr+i] = NULL;
14768         }
14769         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14770         framePtr = savedFramePtr[storedGames];
14771         gameInfo.result = savedResult[storedGames];
14772         if(gameInfo.resultDetails != NULL) {
14773             free(gameInfo.resultDetails);
14774       }
14775         gameInfo.resultDetails = savedDetails[storedGames];
14776         forwardMostMove = currentMove + nrMoves;
14777         if(storedGames == 0) GreyRevert(TRUE);
14778         return TRUE;
14779 }
14780
14781 void 
14782 CleanupTail()
14783 {       // remove all shelved variations
14784         int i;
14785         for(i=0; i<storedGames; i++) {
14786             if(savedDetails[i])
14787                 free(savedDetails[i]);
14788             savedDetails[i] = NULL;
14789         }
14790         for(i=framePtr; i<MAX_MOVES; i++) {
14791                 if(commentList[i]) free(commentList[i]);
14792                 commentList[i] = NULL;
14793         }
14794         framePtr = MAX_MOVES-1;
14795         storedGames = 0;
14796 }